mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-25 01:15:14 +00:00
Merge branch 'develop' into staging-new
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
8ff3b210d6
@ -321,7 +321,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
boardModel,
|
||||
boardId,
|
||||
{
|
||||
label: board.string.ConfigLabel,
|
||||
label: undefined, // board.string.ConfigLabel,
|
||||
description: board.string.ConfigDescription,
|
||||
enabled: false,
|
||||
beta: true,
|
||||
@ -333,7 +333,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
bitrixModel,
|
||||
bitrixId,
|
||||
{
|
||||
label: bitrix.string.ConfigLabel,
|
||||
label: undefined, // bitrix.string.ConfigLabel,
|
||||
description: bitrix.string.ConfigDescription,
|
||||
enabled: false,
|
||||
beta: true,
|
||||
|
@ -32,6 +32,7 @@ import tracker from '@hcengineering/model-tracker'
|
||||
import view, { classPresenter, createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import contacts from '@hcengineering/model-contact'
|
||||
import setting from '@hcengineering/setting'
|
||||
import tags from '@hcengineering/tags'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
@ -266,7 +267,7 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
{
|
||||
key: '$lookup.owner',
|
||||
label: documents.string.Owner,
|
||||
label: documents.string.Author,
|
||||
presenter: documents.component.OwnerPresenter,
|
||||
props: { shouldShowLabel: true, isEditable: false },
|
||||
sortingKey: '$lookup.owner.name'
|
||||
@ -329,7 +330,7 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
{
|
||||
key: '$lookup.owner',
|
||||
label: documents.string.Owner,
|
||||
label: documents.string.Author,
|
||||
presenter: documents.component.OwnerPresenter,
|
||||
props: { shouldShowLabel: true, isEditable: false },
|
||||
sortingKey: '$lookup.owner.name'
|
||||
@ -630,13 +631,23 @@ export function createModel (builder: Builder): void {
|
||||
'state',
|
||||
'space',
|
||||
'template',
|
||||
'owner',
|
||||
{
|
||||
_class: documents.class.Document,
|
||||
component: contacts.component.EmployeeFilter,
|
||||
key: 'owner',
|
||||
label: documents.string.Author
|
||||
},
|
||||
'category',
|
||||
'modifiedOn',
|
||||
'labels',
|
||||
'major',
|
||||
'minor',
|
||||
'author'
|
||||
{
|
||||
_class: documents.class.Document,
|
||||
component: contacts.component.EmployeeFilter,
|
||||
key: 'author',
|
||||
label: documents.string.Creator
|
||||
}
|
||||
],
|
||||
getVisibleFilters: documents.function.GetVisibleFilters
|
||||
})
|
||||
|
@ -289,8 +289,7 @@ export function createModel (builder: Builder): void {
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.Configure,
|
||||
order: 1200,
|
||||
role: AccountRole.Owner,
|
||||
adminOnly: true
|
||||
role: AccountRole.Owner
|
||||
},
|
||||
setting.ids.Configure
|
||||
)
|
||||
|
@ -268,6 +268,7 @@ export interface PluginConfiguration extends Doc {
|
||||
pluginId: Plugin
|
||||
transactions: Ref<Doc>[]
|
||||
|
||||
// If not set will not be shown in configuration UI
|
||||
label?: IntlString
|
||||
icon?: Asset
|
||||
description?: IntlString
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Vedlejší",
|
||||
"Patch": "Oprava",
|
||||
"ValidationWorkflow": "Ověřovací pracovní postup",
|
||||
"ChangeOwner": "Změnit vlastníka dokumentu",
|
||||
"ChangeOwnerHintBeginning": "Převést vlastnictví",
|
||||
"Creator": "Tvůrce",
|
||||
"ChangeOwner": "Změnit autora dokumentu",
|
||||
"ChangeOwnerHintBeginning": "Převést autorství dokumentu",
|
||||
"ChangeOwnerHintEnd": "na jinou osobu.",
|
||||
"ChangeOwnerWarning": "Po této akci nebudete moci dokument upravovat.",
|
||||
"SelectOwner": "Vyberte nového vlastníka",
|
||||
"SelectOwner": "Vyberte nového autora",
|
||||
"CreateDocument": "Vytvořit nový dokument",
|
||||
"CreateTemplate": "Vytvořit novou šablonu",
|
||||
"Documents": "Dokumenty",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Nebenversion",
|
||||
"Patch": "Patch",
|
||||
"ValidationWorkflow": "Validierungsworkflow",
|
||||
"ChangeOwner": "Dokumentenbesitzer ändern",
|
||||
"ChangeOwnerHintBeginning": "Übertragen Sie den Besitz des",
|
||||
"Creator": "Ersteller",
|
||||
"ChangeOwner": "Dokumentautor ändern",
|
||||
"ChangeOwnerHintBeginning": "Autorschaft des Dokuments übertragen",
|
||||
"ChangeOwnerHintEnd": "an eine andere Person.",
|
||||
"ChangeOwnerWarning": "Sie können dieses Dokument nach dieser Aktion nicht mehr bearbeiten.",
|
||||
"SelectOwner": "Neuen Besitzer auswählen",
|
||||
"SelectOwner": "Neuen Autor auswählen",
|
||||
"CreateDocument": "Neues Dokument erstellen",
|
||||
"CreateTemplate": "Neue Vorlage erstellen",
|
||||
"Documents": "Dokumente",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Minor",
|
||||
"Patch": "Patch",
|
||||
"ValidationWorkflow": "Validation workflow",
|
||||
"ChangeOwner": "Change document owner",
|
||||
"ChangeOwnerHintBeginning": "Transfer ownership of the",
|
||||
"Creator": "Creator",
|
||||
"ChangeOwner": "Change document author",
|
||||
"ChangeOwnerHintBeginning": "Transfer authorship of the",
|
||||
"ChangeOwnerHintEnd": "to another person.",
|
||||
"ChangeOwnerWarning": "You will not be able to edit this document after this action.",
|
||||
"SelectOwner": "Select new owner",
|
||||
"SelectOwner": "Select new author",
|
||||
"CreateDocument": "Create new document",
|
||||
"CreateTemplate": "Create new template",
|
||||
"Documents": "Documents",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Mineur",
|
||||
"Patch": "Correctif",
|
||||
"ValidationWorkflow": "Flux de validation",
|
||||
"ChangeOwner": "Changer le propriétaire du document",
|
||||
"ChangeOwnerHintBeginning": "Transférer la propriété du",
|
||||
"Creator": "Créateur",
|
||||
"ChangeOwner": "Changer l’auteur du document",
|
||||
"ChangeOwnerHintBeginning": "Transférer l’auteur du document",
|
||||
"ChangeOwnerHintEnd": "à une autre personne.",
|
||||
"ChangeOwnerWarning": "Vous ne pourrez plus modifier ce document après cette action.",
|
||||
"SelectOwner": "Sélectionner un nouveau propriétaire",
|
||||
"SelectOwner": "Sélectionner un nouvel auteur",
|
||||
"CreateDocument": "Créer un nouveau document",
|
||||
"CreateTemplate": "Créer un nouveau modèle",
|
||||
"Documents": "Documents",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Minore",
|
||||
"Patch": "Patch",
|
||||
"ValidationWorkflow": "Flusso di convalida",
|
||||
"ChangeOwner": "Cambia proprietario del documento",
|
||||
"ChangeOwnerHintBeginning": "Trasferisci la proprietà del",
|
||||
"Creator": "Creatore",
|
||||
"ChangeOwner": "Modifica autore del documento",
|
||||
"ChangeOwnerHintBeginning": "Trasferisci la paternità del documento",
|
||||
"ChangeOwnerHintEnd": "a un'altra persona.",
|
||||
"ChangeOwnerWarning": "Non potrai modificare questo documento dopo questa azione.",
|
||||
"SelectOwner": "Seleziona nuovo proprietario",
|
||||
"SelectOwner": "Seleziona un nuovo autore",
|
||||
"CreateDocument": "Crea nuovo documento",
|
||||
"CreateTemplate": "Crea nuovo modello",
|
||||
"Documents": "Documenti",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Secundária",
|
||||
"Patch": "Correção",
|
||||
"ValidationWorkflow": "Fluxo de validação",
|
||||
"ChangeOwner": "Alterar responsável pelo documento",
|
||||
"ChangeOwnerHintBeginning": "Transferir a propriedade do",
|
||||
"Creator": "Criador",
|
||||
"ChangeOwner": "Alterar autor do documento",
|
||||
"ChangeOwnerHintBeginning": "Transferir autoria do documento",
|
||||
"ChangeOwnerHintEnd": "para outra pessoa.",
|
||||
"ChangeOwnerWarning": "Você não poderá mais editar este documento após essa ação.",
|
||||
"SelectOwner": "Selecionar novo responsável",
|
||||
"SelectOwner": "Selecionar novo autor",
|
||||
"CreateDocument": "Criar novo documento",
|
||||
"CreateTemplate": "Criar novo modelo",
|
||||
"Documents": "Documentos",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "Минорная",
|
||||
"Patch": "Патч",
|
||||
"ValidationWorkflow": "Процесс валидации",
|
||||
"ChangeOwner": "Изменить владельца документа",
|
||||
"ChangeOwnerHintBeginning": "Передайте права владельца документа",
|
||||
"Creator": "Создатель",
|
||||
"ChangeOwner": "Сменить автора документа",
|
||||
"ChangeOwnerHintBeginning": "Передать авторство документа",
|
||||
"ChangeOwnerHintEnd": "другому лицу.",
|
||||
"ChangeOwnerWarning": "Вы не сможете редактировать документ после этой операции.",
|
||||
"SelectOwner": "Выберите нового владельца",
|
||||
"SelectOwner": "Выберите нового автора",
|
||||
"CreateDocument": "Создать документ",
|
||||
"CreateTemplate": "Создать шаблон",
|
||||
"Documents": "Документы",
|
||||
|
@ -22,11 +22,12 @@
|
||||
"Minor": "次要",
|
||||
"Patch": "补丁",
|
||||
"ValidationWorkflow": "验证工作流程",
|
||||
"ChangeOwner": "更改文档所有者",
|
||||
"ChangeOwnerHintBeginning": "转移所有权",
|
||||
"Creator": "创建者",
|
||||
"ChangeOwner": "更改文档作者",
|
||||
"ChangeOwnerHintBeginning": "转移文档的作者身份",
|
||||
"ChangeOwnerHintEnd": "给其他人。",
|
||||
"ChangeOwnerWarning": "此操作后您将无法编辑此文档。",
|
||||
"SelectOwner": "选择新所有者",
|
||||
"SelectOwner": "选择新的作者",
|
||||
"CreateDocument": "创建新文档",
|
||||
"CreateTemplate": "创建新模板",
|
||||
"Documents": "文档",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import documents from '@hcengineering/controlled-documents'
|
||||
import documents, { ControlledDocumentState } from '@hcengineering/controlled-documents'
|
||||
import { Label, Button, showPopup } from '@hcengineering/ui'
|
||||
|
||||
import TeamPopup from '../../TeamPopup.svelte'
|
||||
@ -15,9 +15,12 @@
|
||||
return
|
||||
}
|
||||
|
||||
const isReviewed = $controlledDocument.controlledState === ControlledDocumentState.Reviewed
|
||||
|
||||
const teamPopupData: TeamPopupData = {
|
||||
controlledDoc: $controlledDocument,
|
||||
requestClass: documents.class.DocumentApprovalRequest
|
||||
requestClass: documents.class.DocumentApprovalRequest,
|
||||
requireSignature: !isReviewed
|
||||
}
|
||||
|
||||
showPopup(TeamPopup, teamPopupData, 'center')
|
||||
|
@ -15,7 +15,8 @@
|
||||
import documentsRes from '../../../plugin'
|
||||
import {
|
||||
$controlledDocument as controlledDocument,
|
||||
$documentSnapshots as documentSnapshots
|
||||
$documentSnapshots as documentSnapshots,
|
||||
$isDocumentOwner as isDocumentOwner
|
||||
} from '../../../stores/editors/document'
|
||||
import DocumentApprovalGuideItem from './DocumentApprovalGuideItem.svelte'
|
||||
import DocumentApprovalItem from './DocumentApprovalItem.svelte'
|
||||
@ -61,7 +62,8 @@
|
||||
ControlledDocumentState.Rejected,
|
||||
ControlledDocumentState.InApproval
|
||||
]
|
||||
$: hasGuide = doc && doc.state === DocumentState.Draft && !noGuideStates.includes(doc.controlledState)
|
||||
$: hasGuide =
|
||||
doc && doc.state === DocumentState.Draft && !noGuideStates.includes(doc.controlledState) && $isDocumentOwner
|
||||
</script>
|
||||
|
||||
<RightPanelTabHeader>
|
||||
|
@ -143,7 +143,7 @@
|
||||
<StatePresenter value={$controlledDocument} showTag={false} />
|
||||
</DocumentInfo>
|
||||
|
||||
<DocumentInfo label={documentsRes.string.Owner}>
|
||||
<DocumentInfo label={documentsRes.string.Author}>
|
||||
<OwnerPresenter
|
||||
_id={$controlledDocument.owner}
|
||||
object={$controlledDocument}
|
||||
@ -153,7 +153,7 @@
|
||||
/>
|
||||
</DocumentInfo>
|
||||
|
||||
<DocumentInfo label={documentsRes.string.Author}>
|
||||
<DocumentInfo label={documentsRes.string.Creator}>
|
||||
<PersonPresenter value={$controlledDocument.author} disabled={true} />
|
||||
</DocumentInfo>
|
||||
</div>
|
||||
|
@ -17,7 +17,14 @@ import { ControlledDocumentState, DocumentState } from '@hcengineering/controlle
|
||||
import { TrainingState } from '@hcengineering/training'
|
||||
import { combine } from 'effector'
|
||||
import { $documentComments } from './documentComments'
|
||||
import { $controlledDocument, $documentState, $isLatestVersion, $reviewRequestHistory, $training } from './editor'
|
||||
import {
|
||||
$controlledDocument,
|
||||
$documentState,
|
||||
$isDocumentOwner,
|
||||
$isLatestVersion,
|
||||
$reviewRequestHistory,
|
||||
$training
|
||||
} from './editor'
|
||||
|
||||
export const $canSendForApproval = combine(
|
||||
$controlledDocument,
|
||||
@ -26,7 +33,10 @@ export const $canSendForApproval = combine(
|
||||
$documentComments,
|
||||
$training,
|
||||
$reviewRequestHistory,
|
||||
(document, isLatestVersion, state, comments, training, reviewHistory) => {
|
||||
$isDocumentOwner,
|
||||
(document, isLatestVersion, state, comments, training, reviewHistory, isDocumentOwner) => {
|
||||
if (!isDocumentOwner) return false
|
||||
|
||||
let haveBeenReviewedOnce = false
|
||||
if (document !== null) {
|
||||
const reviews = (reviewHistory ?? []).filter((review) => review.attachedTo === document._id)
|
||||
|
@ -15,10 +15,11 @@
|
||||
|
||||
import { DocumentState } from '@hcengineering/controlled-documents'
|
||||
import { combine } from 'effector'
|
||||
import { $documentState, $isLatestVersion } from './editor'
|
||||
import { $documentState, $isDocumentOwner, $isLatestVersion } from './editor'
|
||||
|
||||
export const $canSendForReview = combine(
|
||||
$isLatestVersion,
|
||||
$documentState,
|
||||
(isLatestVersion, state) => isLatestVersion && state === DocumentState.Draft
|
||||
$isDocumentOwner,
|
||||
(isLatestVersion, state, isDocumentOwner) => isLatestVersion && state === DocumentState.Draft && isDocumentOwner
|
||||
)
|
||||
|
@ -172,6 +172,7 @@ export const documentsPlugin = plugin(documentsId, {
|
||||
Category: '' as IntlString,
|
||||
Author: '' as IntlString,
|
||||
Owner: '' as IntlString,
|
||||
Creator: '' as IntlString,
|
||||
Status: '' as IntlString,
|
||||
Labels: '' as IntlString,
|
||||
Description: '' as IntlString,
|
||||
|
@ -80,6 +80,7 @@
|
||||
let showDeleted: boolean = false
|
||||
let showOther: boolean = true
|
||||
let showGrAttempts: boolean = true
|
||||
let showSelectedRegionOnly: boolean = false
|
||||
|
||||
$: sortedWorkspaces = workspaces
|
||||
.filter(
|
||||
@ -88,6 +89,7 @@
|
||||
(it.url?.includes(search) ?? false) ||
|
||||
it.uuid?.includes(search) ||
|
||||
it.createdBy?.includes(search)) &&
|
||||
(showSelectedRegionOnly ? it.region === selectedRegionId : true) &&
|
||||
((showActive && isActiveMode(it.mode)) ||
|
||||
(showArchived && isArchivingMode(it.mode)) ||
|
||||
(showDeleted && isDeletingMode(it.mode)) ||
|
||||
@ -180,9 +182,11 @@
|
||||
const dayRanges = {
|
||||
Today: [-1, 1],
|
||||
Week: [1, 7],
|
||||
Weeks: [7, 30],
|
||||
Month: [30, 90],
|
||||
Months: [90, 180],
|
||||
Weeks: [7, 14],
|
||||
Month: [14, 30],
|
||||
Months1: [30, 60],
|
||||
Months2: [60, 90],
|
||||
Months3: [90, 180],
|
||||
'Six Month': [180, 270],
|
||||
'Nine Months': [270, 365],
|
||||
Years: [365, 10000000]
|
||||
@ -291,6 +295,10 @@
|
||||
<span class="mr-2">Show attempts >=0 workspaces:</span>
|
||||
<CheckBox bind:checked={showGrAttempts} />
|
||||
</div>
|
||||
<div class="flex-row-center">
|
||||
<span class="mr-2">Show selected region only:</span>
|
||||
<CheckBox bind:checked={showSelectedRegionOnly} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fs-title p-3 flex-row-center">
|
||||
|
@ -133,13 +133,13 @@ test.describe('QMS. Documents tests', () => {
|
||||
await attachScreenshot('TESTS-125_create_child_document.png', page)
|
||||
})
|
||||
|
||||
test('TESTS-126. Change document owner', async ({ page }) => {
|
||||
await allure.description('Requirement\nUsers need to change document owner')
|
||||
test('TESTS-126. Change document author', async ({ page }) => {
|
||||
await allure.description('Requirement\nUsers need to change document author')
|
||||
await allure.tms('TESTS-126', 'https://front.hc.engineering/workbench/platform/tracker/TESTS-126')
|
||||
const changeDocument: NewDocument = {
|
||||
template: 'HR (HR)',
|
||||
title: `Change document owner Document-${generateId()}`,
|
||||
description: `Change document owner Document description-${generateId()}`
|
||||
title: `Change document author Document-${generateId()}`,
|
||||
description: `Change document author Document description-${generateId()}`
|
||||
}
|
||||
const documentDetails: DocumentDetails = {
|
||||
type: 'HR',
|
||||
@ -152,11 +152,11 @@ test.describe('QMS. Documents tests', () => {
|
||||
await prepareDocumentStep(page, changeDocument)
|
||||
|
||||
const documentContentPage = new DocumentContentPage(page)
|
||||
await test.step('2. Change document owner', async () => {
|
||||
await test.step('2. Change document author', async () => {
|
||||
await documentContentPage.checkDocumentTitle(changeDocument.title)
|
||||
await documentContentPage.checkDocument(documentDetails)
|
||||
await documentContentPage.executeMoreActions('Change document owner')
|
||||
await documentContentPage.fillChangeDocumentOwnerPopup('Dirak Kainin')
|
||||
await documentContentPage.executeMoreActions('Change document author')
|
||||
await documentContentPage.fillChangeDocumentAuthorPopup('Dirak Kainin')
|
||||
})
|
||||
|
||||
await test.step('3. Check the updated document information', async () => {
|
||||
@ -681,7 +681,7 @@ test.describe('QMS. Documents tests', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('TESTS-155. Change document owner. QARA user changes owner from one user to another', async ({
|
||||
test('TESTS-155. Change document author. QARA user changes owner from one user to another', async ({
|
||||
page,
|
||||
browser
|
||||
}) => {
|
||||
@ -691,8 +691,8 @@ test.describe('QMS. Documents tests', () => {
|
||||
const newDocumentOwner = 'Dirak Kainin'
|
||||
const changeQaraDocument: NewDocument = {
|
||||
template: 'HR (HR)',
|
||||
title: `Change document owner by QARA user Document-${generateId()}`,
|
||||
description: `Change document owner by QARA user Document description-${generateId()}`
|
||||
title: `Change document author by QARA user Document-${generateId()}`,
|
||||
description: `Change document author by QARA user Document description-${generateId()}`
|
||||
}
|
||||
const documentDetails: DocumentDetails = {
|
||||
type: 'HR',
|
||||
@ -720,8 +720,8 @@ test.describe('QMS. Documents tests', () => {
|
||||
const documentsPageQara = new DocumentsPage(qaraManagerPage)
|
||||
await documentsPageQara.openDocument(changeQaraDocument.title)
|
||||
|
||||
await documentContentPageQara.executeMoreActions('Change document owner')
|
||||
await documentContentPageQara.fillChangeDocumentOwnerPopupByQaraManager(newDocumentOwner)
|
||||
await documentContentPageQara.executeMoreActions('Change document author')
|
||||
await documentContentPageQara.fillChangeDocumentAuthorPopupByQaraManager(newDocumentOwner)
|
||||
})
|
||||
|
||||
await test.step('4. As QARA manager Check the updated document information', async () => {
|
||||
|
@ -14,10 +14,10 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
readonly textCategory: Locator
|
||||
readonly textVersion: Locator
|
||||
readonly textStatus: Locator
|
||||
readonly textOwner: Locator
|
||||
readonly textAuthor: Locator
|
||||
readonly buttonSelectNewOwner: Locator
|
||||
readonly buttonSelectNewOwnerChange: Locator
|
||||
readonly textCreator: Locator
|
||||
readonly buttonSelectNewAuthor: Locator
|
||||
readonly buttonSelectNewAuthorChange: Locator
|
||||
readonly buttonSendForReview: Locator
|
||||
readonly buttonSendForApproval: Locator
|
||||
readonly buttonAddMembers: Locator
|
||||
@ -41,7 +41,7 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
readonly buttonDocument: Locator
|
||||
readonly buttonDocumentApprovals: Locator
|
||||
readonly textPageHeader: Locator
|
||||
readonly buttonSelectNewOwnerChangeByQaraManager: Locator
|
||||
readonly buttonSelectNewAuthorChangeByQaraManager: Locator
|
||||
readonly textId: Locator
|
||||
readonly contentLocator: Locator
|
||||
readonly addSpaceButton: Locator
|
||||
@ -113,10 +113,10 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
this.textCategory = page.locator('div.flex:has(div.label:text("Category")) div.field')
|
||||
this.textVersion = page.locator('div.flex:has(div.label:text("Version")) div.field')
|
||||
this.textStatus = page.locator('div.flex:has(div.label:text("Status")) div.field')
|
||||
this.textOwner = page.locator('div.flex:has(div.label:text("Owner")) div.field')
|
||||
this.textAuthor = page.locator('div.flex:has(div.label:text("Author")) div.field')
|
||||
this.buttonSelectNewOwner = page.locator('div.popup button.small')
|
||||
this.buttonSelectNewOwnerChange = page.locator('div.popup button.dangerous')
|
||||
this.textCreator = page.locator('div.flex:has(div.label:text("Creator")) div.field')
|
||||
this.buttonSelectNewAuthor = page.locator('div.popup button.small')
|
||||
this.buttonSelectNewAuthorChange = page.locator('div.popup button.dangerous')
|
||||
this.buttonSendForReview = page.locator('div.hulyHeader-buttonsGroup.extra button[type="button"] > span', {
|
||||
hasText: 'Send for review'
|
||||
})
|
||||
@ -152,7 +152,7 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
this.buttonDocumentInformation = page.locator('button[id$="info"]')
|
||||
this.buttonDocumentApprovals = page.locator('button[id$="approvals"]')
|
||||
this.textPageHeader = page.locator('div.hulyNavPanel-header')
|
||||
this.buttonSelectNewOwnerChangeByQaraManager = page.locator('div.popup button[type="submit"]')
|
||||
this.buttonSelectNewAuthorChangeByQaraManager = page.locator('div.popup button[type="submit"]')
|
||||
this.textId = page.locator('div.flex:has(div.label:text("ID")) div.field')
|
||||
this.contentLocator = page.locator('div.textInput div.tiptap')
|
||||
this.addSpaceButton = page.locator('#tree-orgspaces')
|
||||
@ -707,20 +707,20 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
await expect(this.textStatus).toHaveText(data.status)
|
||||
}
|
||||
if (data.owner != null) {
|
||||
await expect(this.textOwner).toHaveText(data.owner)
|
||||
await expect(this.textAuthor).toHaveText(data.owner)
|
||||
}
|
||||
if (data.author != null) {
|
||||
await expect(this.textAuthor).toHaveText(data.author)
|
||||
await expect(this.textCreator).toHaveText(data.author)
|
||||
}
|
||||
if (data.id != null) {
|
||||
await expect(this.textId).toHaveText(data.id)
|
||||
}
|
||||
}
|
||||
|
||||
async fillChangeDocumentOwnerPopup (newOwner: string): Promise<void> {
|
||||
await this.buttonSelectNewOwner.click()
|
||||
await this.selectListItemWithSearch(this.page, newOwner)
|
||||
await this.buttonSelectNewOwnerChange.click()
|
||||
async fillChangeDocumentAuthorPopup (newAuthor: string): Promise<void> {
|
||||
await this.buttonSelectNewAuthor.click()
|
||||
await this.selectListItemWithSearch(this.page, newAuthor)
|
||||
await this.buttonSelectNewAuthorChange.click()
|
||||
}
|
||||
|
||||
async fillSelectReviewersForm (reviewers: Array<string>): Promise<void> {
|
||||
@ -858,9 +858,9 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
await this.buttonDocumentApprovals.click({ position: { x: 1, y: 1 }, force: true })
|
||||
}
|
||||
|
||||
async fillChangeDocumentOwnerPopupByQaraManager (newOwner: string): Promise<void> {
|
||||
await this.buttonSelectNewOwner.click()
|
||||
async fillChangeDocumentAuthorPopupByQaraManager (newOwner: string): Promise<void> {
|
||||
await this.buttonSelectNewAuthor.click()
|
||||
await this.selectListItemWithSearch(this.page, newOwner)
|
||||
await this.buttonSelectNewOwnerChangeByQaraManager.click()
|
||||
await this.buttonSelectNewAuthorChangeByQaraManager.click()
|
||||
}
|
||||
}
|
||||
|
@ -84,14 +84,6 @@ function updateMeta (doc: ControlledDocument, txFactory: TxFactory): Tx[] {
|
||||
]
|
||||
}
|
||||
|
||||
function updateAuthor (doc: ControlledDocument, txFactory: TxFactory): Tx[] {
|
||||
return [
|
||||
txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
||||
author: doc.owner
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
// TODO: Find a way to avoid duplicate logic and reuse createTrainingRequest() from `training-resources`
|
||||
async function createDocumentTrainingRequest (doc: ControlledDocument, control: TriggerControl): Promise<Tx[]> {
|
||||
if (!control.hierarchy.hasMixin(doc, documents.mixin.DocumentTraining)) {
|
||||
@ -281,7 +273,6 @@ export async function OnDocHasBecomeEffective (
|
||||
const olderEffective = await getDocsOlderThanDoc(doc, control, [DocumentState.Effective])
|
||||
|
||||
result.push(
|
||||
...updateAuthor(doc, control.txFactory),
|
||||
...archiveDocs(olderEffective, control.txFactory),
|
||||
...updateMeta(doc, control.txFactory),
|
||||
...updateTemplate(doc, olderEffective, control),
|
||||
|
@ -18,7 +18,7 @@ import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/acti
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import contact, { Employee, type Person } from '@hcengineering/contact'
|
||||
import core, {
|
||||
PersonId,
|
||||
AccountUuid,
|
||||
AnyAttribute,
|
||||
ArrOf,
|
||||
AttachedDoc,
|
||||
@ -30,6 +30,8 @@ import core, {
|
||||
DocumentUpdate,
|
||||
MeasureContext,
|
||||
MixinUpdate,
|
||||
notEmpty,
|
||||
PersonId,
|
||||
Ref,
|
||||
RefTo,
|
||||
SortingOrder,
|
||||
@ -41,11 +43,7 @@ import core, {
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc,
|
||||
AccountUuid,
|
||||
notEmpty,
|
||||
generateId,
|
||||
toIdMap
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import notification, {
|
||||
ActivityInboxNotification,
|
||||
@ -78,21 +76,21 @@ import {
|
||||
createPullCollaboratorsTx,
|
||||
createPushCollaboratorsTx,
|
||||
getHTMLPresenter,
|
||||
getNotificationContent,
|
||||
getNotificationLink,
|
||||
getNotificationProviderControl,
|
||||
getObjectSpace,
|
||||
getTextPresenter,
|
||||
getReceiversInfo,
|
||||
getSenderInfo,
|
||||
getTextPresenter,
|
||||
isAllowed,
|
||||
isMixinTx,
|
||||
isShouldNotifyTx,
|
||||
isUserEmployeeInFieldValueTypeMatch,
|
||||
messageToMarkup,
|
||||
replaceAll,
|
||||
updateNotifyContextsSpace,
|
||||
type NotificationProviderControl,
|
||||
getNotificationContent,
|
||||
getSenderInfo
|
||||
replaceAll,
|
||||
updateNotifyContextsSpace
|
||||
} from './utils'
|
||||
import { PushNotificationsHandler } from './push'
|
||||
|
||||
@ -564,11 +562,6 @@ async function createNotifyContext (
|
||||
contextsCache.contexts.set(cacheKey, createTx.objectId)
|
||||
control.cache.set(ContextsCacheKey, contextsCache)
|
||||
await ctx.with('apply', {}, () => control.apply(control.ctx, [createTx]))
|
||||
control.ctx.contextData.broadcast.targets['docNotifyContext' + createTx._id] = (it) => {
|
||||
if (it._id === createTx._id) {
|
||||
return [receiver.account]
|
||||
}
|
||||
}
|
||||
return createTx.objectId
|
||||
}
|
||||
|
||||
@ -668,12 +661,6 @@ async function updateContextsTimestamp (
|
||||
})
|
||||
|
||||
res.push(updateTx)
|
||||
|
||||
control.ctx.contextData.broadcast.targets['docNotifyContext' + updateTx._id] = (it) => {
|
||||
if (it._id === updateTx._id) {
|
||||
return [context.user]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.length > 0) {
|
||||
@ -700,12 +687,6 @@ async function removeContexts (
|
||||
const removeTx = control.txFactory.createTxRemoveDoc(context._class, context.space, context._id)
|
||||
|
||||
res.push(removeTx)
|
||||
|
||||
control.ctx.contextData.broadcast.targets['docNotifyContext' + removeTx._id] = (it) => {
|
||||
if (it._id === removeTx._id) {
|
||||
return [context.user]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await control.apply(control.ctx, res)
|
||||
@ -815,13 +796,7 @@ export async function createCollabDocInfo (
|
||||
docMessages,
|
||||
settings
|
||||
)
|
||||
const ids = new Set(targetRes.map((it) => it._id))
|
||||
const id = generateId() as string
|
||||
control.ctx.contextData.broadcast.targets[id] = (it) => {
|
||||
if (ids.has(it._id)) {
|
||||
return [receiver.account]
|
||||
}
|
||||
}
|
||||
|
||||
res = res.concat(targetRes)
|
||||
}
|
||||
return res
|
||||
@ -1448,41 +1423,6 @@ export async function OnAttributeUpdate (txes: Tx[], control: TriggerControl): P
|
||||
return result
|
||||
}
|
||||
|
||||
async function applyUserTxes (ctx: MeasureContext, control: TriggerControl, txes: Tx[]): Promise<Tx[]> {
|
||||
const map: Map<AccountUuid, Tx[]> = new Map<AccountUuid, Tx[]>()
|
||||
const res: Tx[] = []
|
||||
|
||||
for (const tx of txes) {
|
||||
const ttx = tx as TxCUD<Doc>
|
||||
if (
|
||||
control.hierarchy.isDerived(ttx.objectClass, notification.class.InboxNotification) &&
|
||||
ttx._class === core.class.TxCreateDoc
|
||||
) {
|
||||
const notification = TxProcessor.createDoc2Doc(ttx as TxCreateDoc<InboxNotification>)
|
||||
|
||||
if (map.has(notification.user)) {
|
||||
map.get(notification.user)?.push(tx)
|
||||
} else {
|
||||
map.set(notification.user, [tx])
|
||||
}
|
||||
} else {
|
||||
res.push(tx)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [user, txs] of map.entries()) {
|
||||
await control.apply(ctx, txs)
|
||||
const m1 = toIdMap(txs)
|
||||
control.ctx.contextData.broadcast.targets.docNotifyContext = (it) => {
|
||||
if (m1.has(it._id)) {
|
||||
return [user]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function updateCollaborators (
|
||||
ctx: MeasureContext,
|
||||
control: TriggerControl,
|
||||
@ -1565,19 +1505,16 @@ export async function createCollaboratorNotifications (
|
||||
}
|
||||
|
||||
if (tx.attachedTo !== undefined && !ignoreCollection) {
|
||||
const res = await ctx.with('collectionCollabDoc', {}, (ctx) =>
|
||||
return await ctx.with('collectionCollabDoc', {}, (ctx) =>
|
||||
collectionCollabDoc(ctx, tx as TxCUD<AttachedDoc>, control, activityMessages, cache, true)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const res = await ctx.with('createCollaboratorDoc', {}, (ctx) =>
|
||||
return await ctx.with('createCollaboratorDoc', {}, (ctx) =>
|
||||
createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, cache)
|
||||
)
|
||||
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
case core.class.TxUpdateDoc:
|
||||
case core.class.TxMixin: {
|
||||
@ -1596,7 +1533,7 @@ export async function createCollaboratorNotifications (
|
||||
)
|
||||
)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,6 @@
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-preference": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/query": "^0.6.12",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"fast-equals": "^5.2.2"
|
||||
|
@ -28,7 +28,6 @@ export * from './lookup'
|
||||
export * from './lowLevel'
|
||||
export * from './model'
|
||||
export * from './modified'
|
||||
export * from './notifications'
|
||||
export * from './private'
|
||||
export * from './queryJoin'
|
||||
export * from './spacePermissions'
|
||||
@ -37,3 +36,4 @@ export * from './triggers'
|
||||
export * from './txPush'
|
||||
export * from './queue'
|
||||
export * from './identity'
|
||||
export * from './pluginConfig'
|
||||
|
@ -1,99 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
Doc,
|
||||
MeasureContext,
|
||||
Tx,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
type SessionData,
|
||||
TxApplyIf,
|
||||
systemAccountUuid,
|
||||
AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import { BaseMiddleware, Middleware, TxMiddlewareResult, type PipelineContext } from '@hcengineering/server-core'
|
||||
import { DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class NotificationsMiddleware extends BaseMiddleware implements Middleware {
|
||||
private readonly targetDomains = [DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY]
|
||||
|
||||
private constructor (context: PipelineContext, next?: Middleware) {
|
||||
super(context, next)
|
||||
}
|
||||
|
||||
static async create (
|
||||
ctx: MeasureContext,
|
||||
context: PipelineContext,
|
||||
next: Middleware | undefined
|
||||
): Promise<NotificationsMiddleware> {
|
||||
return new NotificationsMiddleware(context, next)
|
||||
}
|
||||
|
||||
isTargetDomain (tx: Tx): boolean {
|
||||
if (TxProcessor.isExtendsCUD(tx._class)) {
|
||||
const txCUD = tx as TxCUD<Doc>
|
||||
const domain = this.context.hierarchy.getDomain(txCUD.objectClass)
|
||||
return this.targetDomains.includes(domain)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
processTx (ctx: MeasureContext<SessionData>, tx: Tx): void {
|
||||
let target: AccountUuid[] | undefined
|
||||
if (this.isTargetDomain(tx)) {
|
||||
const account = ctx.contextData.account
|
||||
if (!account.socialIds.includes(tx.modifiedBy) && account.uuid !== systemAccountUuid) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
const modifiedByAccount = ctx.contextData.socialStringsToUsers.get(tx.modifiedBy)
|
||||
target = [account.uuid, systemAccountUuid]
|
||||
if (modifiedByAccount !== undefined && !target.includes(modifiedByAccount)) {
|
||||
target.push(modifiedByAccount)
|
||||
}
|
||||
ctx.contextData.broadcast.targets['checkDomain' + account.uuid] = (tx) => {
|
||||
if (this.isTargetDomain(tx)) {
|
||||
return target
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<TxMiddlewareResult> {
|
||||
for (const tx of txes) {
|
||||
if (this.context.hierarchy.isDerived(tx._class, core.class.TxApplyIf)) {
|
||||
for (const ttx of (tx as TxApplyIf).txes) {
|
||||
this.processTx(ctx, ttx)
|
||||
}
|
||||
} else {
|
||||
this.processTx(ctx, tx)
|
||||
}
|
||||
}
|
||||
|
||||
return this.provideTx(ctx, txes)
|
||||
}
|
||||
|
||||
isAvailable (ctx: MeasureContext<SessionData>, doc: Doc): boolean {
|
||||
const domain = this.context.hierarchy.getDomain(doc._class)
|
||||
if (!this.targetDomains.includes(domain)) return true
|
||||
const account = ctx.contextData.account
|
||||
const socialStrings = account.socialIds
|
||||
return (doc.createdBy !== undefined && socialStrings.includes(doc.createdBy)) || account.uuid === systemAccountUuid
|
||||
}
|
||||
}
|
73
server/middleware/src/pluginConfig.ts
Normal file
73
server/middleware/src/pluginConfig.ts
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { aiBotAccountEmail } from '@hcengineering/ai-bot'
|
||||
import core, {
|
||||
AccountRole,
|
||||
MeasureContext,
|
||||
Tx,
|
||||
TxProcessor,
|
||||
systemAccountUuid,
|
||||
type Doc,
|
||||
type SessionData,
|
||||
type TxApplyIf,
|
||||
type TxCUD
|
||||
} from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import { BaseMiddleware, Middleware, TxMiddlewareResult, type PipelineContext } from '@hcengineering/server-core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class PluginConfigurationMiddleware extends BaseMiddleware implements Middleware {
|
||||
private constructor (context: PipelineContext, next?: Middleware) {
|
||||
super(context, next)
|
||||
}
|
||||
|
||||
static async create (
|
||||
ctx: MeasureContext,
|
||||
context: PipelineContext,
|
||||
next: Middleware | undefined
|
||||
): Promise<PluginConfigurationMiddleware> {
|
||||
return new PluginConfigurationMiddleware(context, next)
|
||||
}
|
||||
|
||||
tx (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<TxMiddlewareResult> {
|
||||
const account = ctx.contextData.account
|
||||
if (account.uuid === systemAccountUuid || account.fullSocialIds.some((it) => it.value === aiBotAccountEmail)) {
|
||||
// We pass for system accounts and services.
|
||||
return this.provideTx(ctx, txes)
|
||||
}
|
||||
function checkTx (tx: Tx): void {
|
||||
if (TxProcessor.isExtendsCUD(tx._class)) {
|
||||
const cud = tx as TxCUD<Doc>
|
||||
if (cud.objectClass === core.class.PluginConfiguration && ctx.contextData.account.role !== AccountRole.Owner) {
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.Forbidden, {
|
||||
account: account.uuid
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const tx of txes) {
|
||||
checkTx(tx)
|
||||
if (tx._class === core.class.TxApplyIf) {
|
||||
const atx = tx as TxApplyIf
|
||||
atx.txes.forEach(checkTx)
|
||||
}
|
||||
}
|
||||
return this.provideTx(ctx, txes)
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ import {
|
||||
MarkDerivedEntryMiddleware,
|
||||
ModelMiddleware,
|
||||
ModifiedMiddleware,
|
||||
NotificationsMiddleware,
|
||||
PluginConfigurationMiddleware,
|
||||
PrivateMiddleware,
|
||||
QueryJoinMiddleware,
|
||||
QueueMiddleware,
|
||||
@ -119,8 +119,8 @@ export function createServerPipeline (
|
||||
LookupMiddleware.create,
|
||||
IdentityMiddleware.create,
|
||||
ModifiedMiddleware.create,
|
||||
PluginConfigurationMiddleware.create,
|
||||
PrivateMiddleware.create,
|
||||
NotificationsMiddleware.create,
|
||||
(ctx: MeasureContext, context: PipelineContext, next?: Middleware) =>
|
||||
SpaceSecurityMiddleware.create(opt.adapterSecurity ?? false, ctx, context, next),
|
||||
SpacePermissionsMiddleware.create,
|
||||
|
@ -798,6 +798,8 @@ export class PlatformWorker {
|
||||
return this.integrations.map((it) => it.workspace)
|
||||
}
|
||||
|
||||
checkedWorkspaces = new Set<string>()
|
||||
|
||||
async checkWorkspaceIsActive (
|
||||
token: string,
|
||||
workspace: WorkspaceUuid
|
||||
@ -825,7 +827,10 @@ export class PlatformWorker {
|
||||
const lastVisit = (Date.now() - (workspaceInfo.lastVisit ?? 0)) / (3600 * 24 * 1000) // In days
|
||||
|
||||
if (config.WorkspaceInactivityInterval > 0 && lastVisit > config.WorkspaceInactivityInterval) {
|
||||
this.ctx.warn('Workspace is inactive for too long, skipping for now.', { workspace })
|
||||
if (!this.checkedWorkspaces.has(workspace)) {
|
||||
this.checkedWorkspaces.add(workspace)
|
||||
this.ctx.warn('Workspace is inactive for too long, skipping for now.', { workspace })
|
||||
}
|
||||
return { workspaceInfo: undefined, needRecheck: true }
|
||||
}
|
||||
return { workspaceInfo, needRecheck: true }
|
||||
|
Loading…
Reference in New Issue
Block a user