From 6c969cb194e44a8eca3e8b67af64da4c8bd0487d Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Fri, 7 Feb 2025 18:01:33 +0700 Subject: [PATCH 1/6] fix: cleanup DocumentEmbeddings in database (#7958) Signed-off-by: Alexander Onnikov --- models/document/src/migration.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/models/document/src/migration.ts b/models/document/src/migration.ts index dffaeb7a44..647d7ed4ba 100644 --- a/models/document/src/migration.ts +++ b/models/document/src/migration.ts @@ -355,6 +355,10 @@ export const documentOperation: MigrateOperation = { { state: 'migrateEmbeddings', func: migrateEmbeddings + }, + { + state: 'migrateEmbeddingsRefs', + func: migrateEmbeddingsRefs } ]) }, @@ -370,3 +374,13 @@ async function migrateEmbeddings (client: MigrationClient): Promise { ) await client.move(DOMAIN_DOCUMENT, { _class: attachment.class.Embedding }, DOMAIN_ATTACHMENT) } + +async function migrateEmbeddingsRefs (client: MigrationClient): Promise { + const _class = 'document:class:DocumentEmbedding' + + await client.update(DOMAIN_ACTIVITY, { attachedToClass: _class }, { attachedToClass: attachment.class.Embedding }) + await client.update(DOMAIN_ACTIVITY, { objectClass: _class }, { objectClass: attachment.class.Embedding }) + await client.update(DOMAIN_NOTIFICATION, { attachedToClass: _class }, { attachedToClass: attachment.class.Embedding }) + await client.update(DOMAIN_TX, { objectClass: _class }, { objectClass: attachment.class.Embedding }) + await client.update(DOMAIN_TX, { 'tx.objectClass': _class }, { 'tx.objectClass': attachment.class.Embedding }) +} From 4de6b1c35796d86f683058685ce29a2d3a10b0c6 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Fri, 7 Feb 2025 19:18:01 +0500 Subject: [PATCH 2/6] add microphone and camera permission messages (#7960) Signed-off-by: Denis Bykhov --- plugins/love-assets/lang/cs.json | 4 +++- plugins/love-assets/lang/de.json | 4 +++- plugins/love-assets/lang/en.json | 4 +++- plugins/love-assets/lang/es.json | 4 +++- plugins/love-assets/lang/fr.json | 4 +++- plugins/love-assets/lang/it.json | 4 +++- plugins/love-assets/lang/pt.json | 4 +++- plugins/love-assets/lang/ru.json | 4 +++- plugins/love-assets/lang/zh.json | 4 +++- .../src/components/ControlBar.svelte | 20 ++++++++++++++++--- .../src/components/RoomPopup.svelte | 17 +++++++++++++--- .../src/components/VideoPopup.svelte | 14 +++++++++---- plugins/love-resources/src/plugin.ts | 4 +++- plugins/love-resources/src/utils.ts | 10 ++++++++-- 14 files changed, 79 insertions(+), 22 deletions(-) diff --git a/plugins/love-assets/lang/cs.json b/plugins/love-assets/lang/cs.json index 7756d49dbc..7d3340b1b1 100644 --- a/plugins/love-assets/lang/cs.json +++ b/plugins/love-assets/lang/cs.json @@ -83,6 +83,8 @@ "Language": "Jazyk", "Kick": "Vyhodit", "WithAudio": "Zahrnout systémový zvuk", - "ShareWithAudioTooltip": "Sdílejte obrazovku se systémovým zvukem. Restartujte sdílení obrazovky, aby se změny projevily." + "ShareWithAudioTooltip": "Sdílejte obrazovku se systémovým zvukem. Restartujte sdílení obrazovky, aby se změny projevily.", + "MicPermission": "Mikrofon nebyl nalezen, zkontrolujte oprávnění prohlížeče", + "CamPermission": "Kamera nebyla nalezena, zkontrolujte oprávnění prohlížeče" } } \ No newline at end of file diff --git a/plugins/love-assets/lang/de.json b/plugins/love-assets/lang/de.json index 2d71acac4a..402810eadd 100644 --- a/plugins/love-assets/lang/de.json +++ b/plugins/love-assets/lang/de.json @@ -81,6 +81,8 @@ "StartWithRecording": "Mit Aufnahme starten", "Language": "Sprache", "WithAudio": "Systemaudio einschließen", - "ShareWithAudioTooltip": "Teile deinen Bildschirm mit Systemaudio. Starte die Bildschirmfreigabe neu, um Änderungen anzuwenden." + "ShareWithAudioTooltip": "Teile deinen Bildschirm mit Systemaudio. Starte die Bildschirmfreigabe neu, um Änderungen anzuwenden.", + "MicPermission": "Mikrofon nicht gefunden, überprüfen Sie die Browserberechtigungen", + "CamPermission": "Kamera nicht gefunden, überprüfen Sie die Browserberechtigungen" } } diff --git a/plugins/love-assets/lang/en.json b/plugins/love-assets/lang/en.json index 0953eda9b8..bc60f1472a 100644 --- a/plugins/love-assets/lang/en.json +++ b/plugins/love-assets/lang/en.json @@ -83,6 +83,8 @@ "Language": "Language", "Kick": "Kick", "WithAudio": "Include system audio", - "ShareWithAudioTooltip": "Share your screen with system audio. Restart screen share to apply changes." + "ShareWithAudioTooltip": "Share your screen with system audio. Restart screen share to apply changes.", + "MicPermission": "Microphone not found, check browser permissions", + "CamPermission": "Camera not found, check browser permissions" } } diff --git a/plugins/love-assets/lang/es.json b/plugins/love-assets/lang/es.json index 6fbc67bb8e..ecdcb4ce17 100644 --- a/plugins/love-assets/lang/es.json +++ b/plugins/love-assets/lang/es.json @@ -83,6 +83,8 @@ "Language": "Idioma", "Kick": "Expulsar", "WithAudio": "Incluir audio del sistema", - "ShareWithAudioTooltip": "Comparte tu pantalla con el audio del sistema. Reinicia la pantalla compartida para aplicar los cambios." + "ShareWithAudioTooltip": "Comparte tu pantalla con el audio del sistema. Reinicia la pantalla compartida para aplicar los cambios.", + "MicPermission": "Micrófono no encontrado, comprueba los permisos del navegador", + "CamPermission": "Cámara no encontrada, comprueba los permisos del navegador" } } diff --git a/plugins/love-assets/lang/fr.json b/plugins/love-assets/lang/fr.json index 45096de21e..d4e4c7af87 100644 --- a/plugins/love-assets/lang/fr.json +++ b/plugins/love-assets/lang/fr.json @@ -83,6 +83,8 @@ "Language": "Langue", "Kick": "Expulser", "WithAudio": "Inclure l'audio du système", - "ShareWithAudioTooltip": "Partagez votre écran avec l'audio du système. Redémarrez le partage d’écran pour appliquer les modifications." + "ShareWithAudioTooltip": "Partagez votre écran avec l'audio du système. Redémarrez le partage d’écran pour appliquer les modifications.", + "MicPermission": "Microphone non trouvé, vérifiez les autorisations du navigateur", + "CamPermission": "Caméra non trouvée, vérifiez les autorisations du navigateur" } } \ No newline at end of file diff --git a/plugins/love-assets/lang/it.json b/plugins/love-assets/lang/it.json index 362d036b18..e06c19dc5b 100644 --- a/plugins/love-assets/lang/it.json +++ b/plugins/love-assets/lang/it.json @@ -83,6 +83,8 @@ "Language": "Lingua", "Kick": "Espellere", "WithAudio": "Includi l'audio di sistema", - "ShareWithAudioTooltip": "Condividi lo schermo con l'audio di sistema. Riavvia la condivisione dello schermo per applicare le modifiche." + "ShareWithAudioTooltip": "Condividi lo schermo con l'audio di sistema. Riavvia la condivisione dello schermo per applicare le modifiche.", + "MicPermission": "Microfono non trovato, controlla le autorizzazioni del browser", + "CamPermission": "Camera non trovata, controlla le autorizzazioni del browser" } } diff --git a/plugins/love-assets/lang/pt.json b/plugins/love-assets/lang/pt.json index dbd8dd2249..820fb40bbe 100644 --- a/plugins/love-assets/lang/pt.json +++ b/plugins/love-assets/lang/pt.json @@ -83,6 +83,8 @@ "Language": "Idioma", "Kick": "Expulsar", "WithAudio": "Incluir áudio do sistema", - "ShareWithAudioTooltip": "Compartilhe sua tela com o áudio do sistema. Reinicie o compartilhamento de tela para aplicar as alterações." + "ShareWithAudioTooltip": "Compartilhe sua tela com o áudio do sistema. Reinicie o compartilhamento de tela para aplicar as alterações.", + "MicPermission": "Microfone não encontrado, verifique as permissões do navegador", + "CamPermission": "Câmera não encontrada, verifique as permissões do navegador" } } diff --git a/plugins/love-assets/lang/ru.json b/plugins/love-assets/lang/ru.json index 20c4257f58..54542ff5ad 100644 --- a/plugins/love-assets/lang/ru.json +++ b/plugins/love-assets/lang/ru.json @@ -83,6 +83,8 @@ "Language": "Язык", "Kick": "Выгнать", "WithAudio": "С системным звуком", - "ShareWithAudioTooltip": "Делитесь экраном с системным звуком. Перезапустите трансляцию, чтобы применить изменения." + "ShareWithAudioTooltip": "Делитесь экраном с системным звуком. Перезапустите трансляцию, чтобы применить изменения.", + "MicPermission": "Микрофон не найден, проверьте разрешения браузера", + "CamPermission": "Камера не найдена, проверьте разрешения браузера" } } diff --git a/plugins/love-assets/lang/zh.json b/plugins/love-assets/lang/zh.json index 1d4e18135d..2914adca2f 100644 --- a/plugins/love-assets/lang/zh.json +++ b/plugins/love-assets/lang/zh.json @@ -83,6 +83,8 @@ "Language": "语言", "Kick": "踢出", "WithAudio": "包含系统音频", - "ShareWithAudioTooltip": "与系统音频一起共享屏幕。重新启动屏幕共享以应用更改。" + "ShareWithAudioTooltip": "与系统音频一起共享屏幕。重新启动屏幕共享以应用更改。", + "MicPermission": "未找到麦克风,请检查浏览器权限", + "CamPermission": "未找到摄像头,请检查浏览器权限" } } diff --git a/plugins/love-resources/src/components/ControlBar.svelte b/plugins/love-resources/src/components/ControlBar.svelte index b5141a8e01..da0f116bfe 100644 --- a/plugins/love-resources/src/components/ControlBar.svelte +++ b/plugins/love-resources/src/components/ControlBar.svelte @@ -38,9 +38,11 @@ import love from '../plugin' import { currentRoom, myInfo, myOffice } from '../stores' import { + isCamAllowed, isCameraEnabled, isConnected, isFullScreen, + isMicAllowed, isMicEnabled, isRecording, isRecordingAvailable, @@ -64,6 +66,7 @@ import RoomLanguageSelector from './RoomLanguageSelector.svelte' import RoomModal from './RoomModal.svelte' import ShareSettingPopup from './ShareSettingPopup.svelte' + import { Room as LKRoom } from 'livekit-client' export let room: Room export let canMaximize: boolean = true @@ -201,8 +204,12 @@ {#if $isConnected} (false) export const isSharingEnabled = writable(false) export const isFullScreen = writable(false) export const isShareWithSound = writable(false) +export const isMicAllowed = writable(false) +export const isCamAllowed = writable(false) function handleTrackSubscribed ( track: RemoteTrack, @@ -539,8 +541,9 @@ export async function setCam (value: boolean): Promise { try { const opt: VideoCaptureOptions = {} const selectedDevice = localStorage.getItem(selectedCamId) + const devices = await LKRoom.getLocalDevices('videoinput') + isCamAllowed.set(devices.length > 0) if (selectedDevice !== null) { - const devices = await LKRoom.getLocalDevices('videoinput') const available = devices.find((p) => p.deviceId === selectedDevice) if (available !== undefined) { opt.deviceId = available.deviceId @@ -549,6 +552,7 @@ export async function setCam (value: boolean): Promise { await lk.localParticipant.setCameraEnabled(value, opt) } catch (err) { console.error(err) + isCamAllowed.set(false) } } else { sendMessage({ type: 'set_cam', value }) @@ -572,8 +576,9 @@ export async function setMic (value: boolean): Promise { try { const opt: AudioCaptureOptions = {} const selectedDevice = localStorage.getItem(selectedMicId) + const devices = await LKRoom.getLocalDevices('audioinput') + isMicAllowed.set(devices.length > 0) if (selectedDevice !== null) { - const devices = await LKRoom.getLocalDevices('audioinput') const available = devices.find((p) => p.deviceId === selectedDevice) if (available !== undefined) { opt.deviceId = available.deviceId @@ -582,6 +587,7 @@ export async function setMic (value: boolean): Promise { await lk.localParticipant.setMicrophoneEnabled(value, opt) } catch (err) { console.error(err) + isMicAllowed.set(false) } } else { sendMessage({ type: 'set_mic', value }) From 50eac538a6e8ea16c983d178ae2c540a780320c2 Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Fri, 7 Feb 2025 18:06:02 +0300 Subject: [PATCH 3/6] EQMS-1430: Fixed infinite loop in Channellnput (#7961) Signed-off-by: Victor Ilyushchenko --- plugins/chunter-resources/src/components/ChannelInput.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chunter-resources/src/components/ChannelInput.svelte b/plugins/chunter-resources/src/components/ChannelInput.svelte index 4590d6634e..ccc9c02594 100644 --- a/plugins/chunter-resources/src/components/ChannelInput.svelte +++ b/plugins/chunter-resources/src/components/ChannelInput.svelte @@ -45,7 +45,7 @@ if (res.length > 0) { return res } - clazz = hierarchy.getClass(_class).extends + clazz = hierarchy.getClass(clazz).extends } } catch (e) { console.error(e) From 0be8b860fceee6e3a5bd37d6e72981f5ade0eee8 Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Fri, 7 Feb 2025 18:10:25 +0300 Subject: [PATCH 4/6] Fixes for document hierarchy presentation in QMS (#7938) * Fixes for document hierarchy presentation in QMS Signed-off-by: Victor Ilyushchenko * fixed test Signed-off-by: Victor Ilyushchenko * styling fix Signed-off-by: Victor Ilyushchenko * cooler names Signed-off-by: Victor Ilyushchenko --------- Signed-off-by: Victor Ilyushchenko --- .../info/DocumentFlatHierarchy.svelte | 183 +++---------- .../info/DocumentFlatTreeElement.svelte | 19 +- .../hierarchy/DocHierarchyLevel.svelte | 137 ++-------- .../hierarchy/DocumentParentSelector.svelte | 45 ++-- .../hierarchy/DocumentSpacePresenter.svelte | 105 +++----- .../src/utils.ts | 57 +++- plugins/controlled-documents/src/utils.ts | 244 ++++++++++++++---- .../sanity/tests/documents/documents.spec.ts | 8 +- 8 files changed, 375 insertions(+), 423 deletions(-) diff --git a/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatHierarchy.svelte b/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatHierarchy.svelte index 5f1dceaf0c..b08c388105 100644 --- a/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatHierarchy.svelte +++ b/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatHierarchy.svelte @@ -14,184 +14,70 @@ --> {#if levels.length > 0} {@const [firstDocs, firstHltd] = levels[0]}
- {#each firstDocs as doc} - + {#each firstDocs as bundle} + {/each} {#if levels.length > 1} {@const [secondDocs, secondHltd] = levels[1]}
- {#each secondDocs as doc} - + {#each secondDocs as bundle} + {/each} {#if levels.length > 2} {@const [thirdDocs, thirdHltd] = levels[2]}
- {#each thirdDocs as doc} - + {#each thirdDocs as bundle} + {/each}
{/if} @@ -215,5 +101,8 @@ padding: 0 1rem; border-left: 2px solid var(--theme-navpanel-border); gap: 0.25rem; + + padding-left: 0.25rem; + margin-left: 0.75rem; } diff --git a/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatTreeElement.svelte b/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatTreeElement.svelte index d7e274b9c0..85bf78e8ad 100644 --- a/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatTreeElement.svelte +++ b/plugins/controlled-documents-resources/src/components/document/right-panel/info/DocumentFlatTreeElement.svelte @@ -14,17 +14,25 @@ --> @@ -32,7 +40,8 @@
{ - const loc = getProjectDocumentLink(doc, project) + if (!document) return + const loc = getProjectDocumentLink(document, project) navigate(loc) }} > @@ -48,7 +57,7 @@
{/if} - {getDocumentName(doc)} + {document ? getDocumentName(document) : meta.title}
diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte index 3be626ba9c..58e2665ea8 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte @@ -13,23 +13,20 @@ // limitations under the License. --> -{#each docs as prjdoc} - {@const pjmeta = projectMetaById.get(prjdoc.attachedTo)} - {@const doc = prjdoc.$lookup?.document} - {@const metaid = pjmeta?.meta} - {@const meta = metaid ? docsMetaById.get(metaid) : undefined} +{#each documentIds as metaid} + {@const bundle = tree.bundleOf(metaid)} + {@const prjdoc = bundle?.ProjectDocument[0]} + {@const doc = bundle?.ControlledDocument[0]} + {@const meta = bundle?.DocumentMeta[0]} {@const title = doc ? getDocumentName(doc) : meta?.title ?? ''} - {@const docid = doc?._id ?? prjdoc._id} - {@const isFolder = prjdoc.document === documents.ids.Folder} - {@const isObsolete = doc ? doc.state === DocumentState.Obsolete : false} - {@const children = metaid ? childrenByParent[metaid] ?? [] : []} + {@const docid = doc?._id ?? prjdoc?._id} + {@const isFolder = prjdoc?.document === documents.ids.Folder} + {@const children = tree.childrenOf(metaid)} + {@const isRemoved = doc && removeStates.includes(doc.state)} - {#if metaid && (!isObsolete || children.length > 0)} + {#if prjdoc && metaid} {@const isDraggedOver = draggedOver === metaid}
{#if isDraggedOver} @@ -161,7 +72,7 @@ _id={docid} icon={isFolder ? documents.icon.Folder : documents.icon.Document} iconProps={{ - fill: isObsolete ? 'var(--dangerous-bg-color)' : 'currentColor' + fill: isRemoved ? 'var(--dangerous-bg-color)' : 'currentColor' }} {title} selected={selected === docid || selected === prjdoc._id} @@ -191,8 +102,8 @@ {#if children.length} { dispatch('selected', space) }} > - + diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte index 14db33b85b..585623a41d 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte @@ -13,20 +13,6 @@ // limitations under the License. --> {#if expanded}
- {#each approvals as approver} + {#each approvals as approval} + {@const messages = approval.messages ?? []}
- - {#key approver.timestamp} - + + {#key approval.timestamp} - {#if approver.approved === 'approved'} + + {#if approval.state === 'approved'} - {:else if approver.approved === 'rejected'} + {:else if approval.state === 'rejected'} - {:else if approver.approved === 'cancelled'} + {:else if approval.state === 'cancelled'} - {:else if approver.approved === 'waiting'} + {:else if approval.state === 'waiting'} {/if} {/key}
- {#if rejectingMessage !== undefined && approver.approved === 'rejected'} -
{rejectingMessage}
- {/if} + {#each messages as m} +
{m.message}
+ {/each} {/each}
{/if} @@ -199,7 +137,7 @@ flex-shrink: 0; border-bottom: 1px solid var(--theme-divider-color); - .reject-message { + .approval-status-message { font-weight: 400; padding: 0.625rem 1rem 0 2rem; } diff --git a/plugins/controlled-documents-resources/src/components/document/right-panel/DocumentApprovalsTab.svelte b/plugins/controlled-documents-resources/src/components/document/right-panel/DocumentApprovalsTab.svelte index 8bdd6645c5..61149a69f5 100644 --- a/plugins/controlled-documents-resources/src/components/document/right-panel/DocumentApprovalsTab.svelte +++ b/plugins/controlled-documents-resources/src/components/document/right-panel/DocumentApprovalsTab.svelte @@ -2,56 +2,71 @@ import documents, { ControlledDocumentState, DocumentRequest, - DocumentState + DocumentState, + emptyBundle, + extractValidationWorkflow } from '@hcengineering/controlled-documents' import { SortingOrder } from '@hcengineering/core' import { createQuery, getClient } from '@hcengineering/presentation' import { Label, Scroller } from '@hcengineering/ui' - import { $controlledDocument as controlledDocument } from '../../../stores/editors/document' - import document from '../../../plugin' - import RightPanelTabHeader from './RightPanelTabHeader.svelte' - import DocumentApprovalItem from './DocumentApprovalItem.svelte' + import { personIdByAccountId } from '@hcengineering/contact-resources' + import documentsRes from '../../../plugin' + import { + $controlledDocument as controlledDocument, + $documentSnapshots as documentSnapshots + } from '../../../stores/editors/document' import DocumentApprovalGuideItem from './DocumentApprovalGuideItem.svelte' + import DocumentApprovalItem from './DocumentApprovalItem.svelte' + import RightPanelTabHeader from './RightPanelTabHeader.svelte' + import chunter, { ChatMessage } from '@hcengineering/chunter' const client = getClient() const hierarchy = client.getHierarchy() let requests: DocumentRequest[] = [] - let approvals: DocumentRequest[] = [] + let messages: ChatMessage[] = [] - $: approvals = requests.filter((p) => hierarchy.isDerived(p._class, documents.class.DocumentApprovalRequest)) + $: doc = $controlledDocument + const requestQuery = createQuery() + $: if (doc) { + requestQuery.query(documents.class.DocumentRequest, { attachedTo: doc._id }, (r) => { + requests = r + }) + } - const query = createQuery() - $: query.query( - documents.class.DocumentRequest, + const messageQuery = createQuery() + $: if (doc) { + messageQuery.query(chunter.class.ChatMessage, { attachedTo: { $in: requests.map((r) => r._id) } }, (r) => { + messages = r + }) + } + + $: workflow = extractValidationWorkflow( + hierarchy, { - _class: { - $in: [documents.class.DocumentApprovalRequest, documents.class.DocumentReviewRequest] - }, - attachedTo: $controlledDocument?._id + ...emptyBundle(), + ControlledDocument: doc ? [doc] : [], + DocumentRequest: requests, + DocumentSnapshot: $documentSnapshots, + ChatMessage: messages }, - (result) => { - requests = result - }, - { - sort: { createdOn: SortingOrder.Descending } - } + (ref) => $personIdByAccountId.get(ref) ) - $: hasGuide = - $controlledDocument?.state === DocumentState.Draft && - ($controlledDocument?.controlledState == null || - ![ - ControlledDocumentState.Approved, - ControlledDocumentState.Rejected, - ControlledDocumentState.InApproval - ].includes($controlledDocument?.controlledState)) + $: validationStates = ((doc ? workflow.get(doc._id) : []) ?? []).slice() + + const noGuideStates: (ControlledDocumentState | undefined)[] = [ + ControlledDocumentState.Approved, + ControlledDocumentState.Rejected, + ControlledDocumentState.InApproval + ] + $: hasGuide = doc && doc.state === DocumentState.Draft && !noGuideStates.includes(doc.controlledState) - @@ -60,13 +75,13 @@
{/if} - {#if requests.length > 0} - {#each requests as object, idx} - + {#if validationStates.length > 0} + {#each validationStates as state, idx} + {/each} {/if} - {#if !hasGuide && approvals.length === 0} -
+ {#if !hasGuide && requests.length === 0} +
{/if} diff --git a/plugins/controlled-documents-resources/src/plugin.ts b/plugins/controlled-documents-resources/src/plugin.ts index b91b51218d..2c139b36c7 100644 --- a/plugins/controlled-documents-resources/src/plugin.ts +++ b/plugins/controlled-documents-resources/src/plugin.ts @@ -46,7 +46,7 @@ export default mergeIds(documentsId, documents, { }, string: { ID: '' as IntlString, - DocumentApprovals: '' as IntlString, + ValidationWorkflow: '' as IntlString, Cancel: '' as IntlString, NewDocumentDialogClose: '' as IntlString, NewDocumentCloseNote: '' as IntlString, diff --git a/plugins/controlled-documents-resources/src/stores/editors/document/editor.ts b/plugins/controlled-documents-resources/src/stores/editors/document/editor.ts index 5c43e56f34..374ace90be 100644 --- a/plugins/controlled-documents-resources/src/stores/editors/document/editor.ts +++ b/plugins/controlled-documents-resources/src/stores/editors/document/editor.ts @@ -366,7 +366,7 @@ export const $availableRightPanelTabs = combine($canViewDocumentComments, (canVi tabs.push({ id: RightPanelTab.APPROVALS, icon: plugin.icon.Approvals, - showTooltip: { label: plugin.string.DocumentApprovals } + showTooltip: { label: plugin.string.ValidationWorkflow } }) return tabs diff --git a/plugins/controlled-documents/src/utils.ts b/plugins/controlled-documents/src/utils.ts index 6d2796fb6f..0e093632e1 100644 --- a/plugins/controlled-documents/src/utils.ts +++ b/plugins/controlled-documents/src/utils.ts @@ -20,11 +20,13 @@ import { Doc, DocumentQuery, DocumentUpdate, + Hierarchy, getCurrentAccount, Rank, Ref, SortingOrder, Space, + Timestamp, toIdMap, TxOperations } from '@hcengineering/core' @@ -35,7 +37,7 @@ import documents from './plugin' import attachment, { Attachment } from '@hcengineering/attachment' import chunter, { ChatMessage } from '@hcengineering/chunter' -import { Employee } from '@hcengineering/contact' +import { Person, PersonAccount, Employee } from '@hcengineering/contact' import { makeRank } from '@hcengineering/rank' import tags, { TagReference } from '@hcengineering/tags' import { @@ -628,6 +630,139 @@ async function _transferDocuments ( return commit.result } +export interface DocumentApprovalState { + person?: Ref + role: 'author' | 'reviewer' | 'approver' + state: 'approved' | 'rejected' | 'cancelled' | 'waiting' + timestamp?: Timestamp + messages?: ChatMessage[] +} + +export interface DocumentValidationState { + requests: DocumentRequest[] + snapshot?: DocumentSnapshot + document: ControlledDocument + approvals: DocumentApprovalState[] + modifiedOn?: Timestamp +} + +export function extractValidationWorkflow ( + hierarchy: Hierarchy, + bundle: DocumentBundle, + accountIdToPerson: (ref: Ref) => Ref | undefined +): Map, DocumentValidationState[]> { + const result: ReturnType = new Map() + + const getApprovalStates = (request: DocumentRequest | undefined): DocumentApprovalState[] => { + if (request === undefined) return [] + + const role = hierarchy.isDerived(request._class, documents.class.DocumentReviewRequest) ? 'reviewer' : 'approver' + + const rejected: DocumentApprovalState[] = + request.rejected !== undefined + ? [ + { + person: request.rejected, + role, + state: 'rejected', + timestamp: request.modifiedOn + } + ] + : [] + + const approved: DocumentApprovalState[] = request.approved.map((person, idx) => { + return { + person, + role, + state: 'approved', + timestamp: request.approvedDates?.[idx] ?? request.modifiedOn + } + }) + + const ignored: DocumentApprovalState[] = request.requested + .filter((person) => person !== request.rejected) + .filter((person) => !request.approved.includes(person)) + .map((person) => { + return { + person, + role, + state: request.rejected !== undefined ? 'cancelled' : 'waiting' + } + }) + + const states = [...rejected, ...approved, ...ignored] + + const messages = bundle.ChatMessage.filter((m) => m.attachedTo === request._id) + for (const state of states) { + state.messages = messages.filter((m) => accountIdToPerson(m.createdBy as Ref) === state.person) + } + + return states + } + + for (const document of bundle.ControlledDocument) { + const snapshots = bundle.DocumentSnapshot.filter((s) => s.attachedTo === document._id).sort( + (a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0) + ) + const requests = bundle.DocumentRequest.filter((s) => s.attachedTo === document._id).sort( + (a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0) + ) + + const states: DocumentValidationState[] = [...snapshots, undefined].map((snapshot) => { + return { + requests: [], + snapshot, + document, + approvals: [], + messages: [] + } + }) + + for (const request of requests) { + const state = + states.find((s) => (s.snapshot?.createdOn ?? 0) > (request.createdOn ?? 0)) ?? states[states.length - 1] + state.requests.push(request) + } + + for (const state of states) { + const review = state.requests.findLast((r) => + hierarchy.isDerived(r._class, documents.class.DocumentReviewRequest) + ) + let approval = state.requests.findLast((r) => + hierarchy.isDerived(r._class, documents.class.DocumentApprovalRequest) + ) + + if ((approval?.createdOn ?? 0) < (review?.createdOn ?? 0)) approval = undefined + + const anchor = review ?? approval + const author = + anchor?.createdBy !== undefined + ? accountIdToPerson?.(anchor.createdBy as Ref) ?? document.author + : document.author + + state.approvals = [ + { + person: author, + role: 'author', + state: anchor !== undefined ? 'approved' : 'waiting', + timestamp: anchor !== undefined ? anchor.createdOn ?? document.createdOn : undefined + }, + ...getApprovalStates(review), + ...getApprovalStates(approval) + ] + + if (state.requests.length > 0) { + state.modifiedOn = Math.max(...state.requests.map((r) => r.modifiedOn ?? 0)) + } + } + + states.reverse() + result.set(document._id, states) + } + + return result +} + /** * @public */ diff --git a/qms-tests/sanity/tests/documents/documents.spec.ts b/qms-tests/sanity/tests/documents/documents.spec.ts index 18228d0633..de0eb170d5 100644 --- a/qms-tests/sanity/tests/documents/documents.spec.ts +++ b/qms-tests/sanity/tests/documents/documents.spec.ts @@ -950,7 +950,7 @@ test.describe('QMS. Documents tests', () => { await test.step('9. Send for Approval', async () => { await documentContentPage.buttonSendForApproval.click() - await documentContentPage.fillSelectApproversForm([reviewer]) + await documentContentPage.fillSelectApproversForm([reviewer], true) await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) await documentContentPage.checkDocument({ ...documentDetails, diff --git a/qms-tests/sanity/tests/model/documents/document-approvals-page.ts b/qms-tests/sanity/tests/model/documents/document-approvals-page.ts index e9c31cf7d7..92b95f2b4c 100644 --- a/qms-tests/sanity/tests/model/documents/document-approvals-page.ts +++ b/qms-tests/sanity/tests/model/documents/document-approvals-page.ts @@ -12,15 +12,16 @@ export class DocumentApprovalsPage extends DocumentCommonPage { async checkRejectApproval (approvalName: string, message: string): Promise { await expect( this.page - .locator('div.reject-message', { hasText: message }) + .locator('div.approval-status-message', { hasText: message }) .locator('xpath=..') .locator('div.approver span.ap-label') + .last() ).toHaveText(approvalName) } async checkSuccessApproval (approvalName: string): Promise { - await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=../..').locator('span.ap-label')).toHaveText( - approvalName - ) + await expect( + this.page.locator('svg[fill*="accepted"]').locator('xpath=../..').locator('span.ap-label').last() + ).toHaveText(approvalName) } } diff --git a/qms-tests/sanity/tests/model/documents/document-content-page.ts b/qms-tests/sanity/tests/model/documents/document-content-page.ts index 62f6c18ebb..2cf6d82713 100644 --- a/qms-tests/sanity/tests/model/documents/document-content-page.ts +++ b/qms-tests/sanity/tests/model/documents/document-content-page.ts @@ -733,14 +733,14 @@ export class DocumentContentPage extends DocumentCommonPage { await this.confirmSubmission() } - async fillSelectApproversForm (approvers: Array): Promise { + async fillSelectApproversForm (approvers: Array, skipConfirm: boolean = false): Promise { await this.buttonAddMembers.click() for (const approver of approvers) { await this.selectListItemWithSearch(this.page, approver) } await this.textSelectApproversPopup.click({ force: true }) await this.buttonSelectMemberSubmit.click() - await this.confirmSubmission() + if (!skipConfirm) await this.confirmSubmission() } async checkCurrentRights (right: DocumentRights): Promise { From fbc41a0a2cb425f495da32db7ffe3041ea8d334f Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Sat, 8 Feb 2025 23:36:16 +0700 Subject: [PATCH 6/6] Bump model version Signed-off-by: Andrey Sobolev --- common/scripts/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/scripts/version.txt b/common/scripts/version.txt index 672c5d09b7..0d9ffca7ea 100644 --- a/common/scripts/version.txt +++ b/common/scripts/version.txt @@ -1 +1 @@ -"0.6.421" +"0.6.435"