@conduction/nextcloud-vue 2.71.0 → 2.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +15 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +15 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +64 -0
- package/dist/esm/utils/selectOptions.js +57 -0
- package/dist/esm/utils/selectOptions.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +5945 -5645
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +64 -0
- package/l10n/en.json +15 -1
- package/l10n/nl.json +15 -1
- package/package.json +1 -1
- package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
- package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
- package/src/components/CnFlowStepOutcomes/index.js +8 -0
- package/src/components/index.js +1 -0
- package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
- package/src/dialogs/CnRunNodeDialog.vue +8 -6
- package/src/index.js +1 -0
- package/src/utils/selectOptions.js +54 -0
|
@@ -6,6 +6,7 @@ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutli
|
|
|
6
6
|
import DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue';
|
|
7
7
|
import FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue';
|
|
8
8
|
import Replay from 'vue-material-design-icons/Replay.vue';
|
|
9
|
+
import __sfc_main from '../CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js';
|
|
9
10
|
import { useFlowStore, FLOW_RUN_ACTIVE_STATUSES } from '../../composables/useFlowStore.js';
|
|
10
11
|
|
|
11
12
|
var script = {
|
|
@@ -14,6 +15,7 @@ var script = {
|
|
|
14
15
|
components: {
|
|
15
16
|
ArrowLeft,
|
|
16
17
|
CheckboxMarkedOutline,
|
|
18
|
+
CnFlowStepOutcomes: __sfc_main,
|
|
17
19
|
DatabaseOutline,
|
|
18
20
|
FormatListBulleted,
|
|
19
21
|
NcAppSidebarTab,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRunDetailSidebar.vue2.js","sources":["../../../../src/components/CnFlowDetail/CnRunDetailSidebar.vue"],"sourcesContent":["<!--\n CnRunDetailSidebar — one run: what it changed, what it asked a person to do,\n and what it did.\n\n WHY THIS IS A SIDEBAR AND NOT A PANEL INSIDE THE FLOW'S\n -------------------------------------------------------\n It used to be a block nested inside the Runs tab, with a tab strip of its own.\n That put TABS INSIDE A TAB: the flow's strip (Steps, Runs) stayed on screen\n above the run's (Objects, Tasks, Logs), and the two competed for the same\n glance. Worse, the flow's tabs were still live — pressing Steps while reading\n a run swapped the panel and left the canvas painted with that run's badges,\n so the graph and the sidebar were describing different things.\n\n So opening a run REPLACES the flow's sidebar rather than nesting inside it.\n One strip at a time, and the way back is a deliberate act with its own\n control. The canvas keeps the run's replay while this is open, which is now\n consistent rather than accidental: the sidebar and the graph are showing the\n same run because there is no way to make them show anything else.\n\n WHAT IT DOES NOT OWN\n --------------------\n The run's badges on the canvas. Those are the flow detail's animation, driven\n from `store.inspectedRunUuid` and the stored log. This component reads the\n same run; it does not paint.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<section class=\"cn-run-sidebar\" data-testid=\"run-detail-sidebar\">\n\t\t<!--\n\t\t\tTHE WAY BACK, FIRST AND ALWAYS. A view that replaces another must\n\t\t\tsay what it replaced and how to undo that, or it reads as the\n\t\t\tapplication having navigated somewhere on its own.\n\t\t-->\n\t\t<div class=\"cn-run-sidebar__head\">\n\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\tdata-testid=\"run-back\"\n\t\t\t\t@click=\"store.closeRun()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ArrowLeft :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Back to the flow') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<h4 class=\"cn-run-sidebar__title\">\n\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${run?.status}`\"\n\t\t\t\tdata-testid=\"run-status\">{{ run?.status || t('nextcloud-vue', 'Run') }}</span>\n\t\t\t<time v-if=\"run?.created\" class=\"cn-run-sidebar__when\" :datetime=\"run.created\">{{ run.created }}</time>\n\t\t</h4>\n\n\t\t<p v-if=\"run?.error\" class=\"cn-run-sidebar__error\" data-testid=\"run-error\">\n\t\t\t{{ run.error }}\n\t\t</p>\n\n\t\t<!--\n\t\t\tTHE STRIP IS NEXTCLOUD'S, NOT OURS.\n\n\t\t\tThese three used to be hand-rolled buttons with a border under the\n\t\t\tactive one. They sat directly inside NcAppSidebar, one element away\n\t\t\tfrom the real thing, so they read as a panel that had been dropped\n\t\t\tinto the sidebar rather than as the sidebar's own tabs: no icons,\n\t\t\tthe wrong size, the wrong active mark, and none of the keyboard\n\t\t\tbehaviour NcAppSidebarTabs brings.\n\n\t\t\tNcAppSidebarTab registers itself with the sidebar through an\n\t\t\tinjection, so the sidebar builds the strip. That also settles which\n\t\t\ttab is open: entering run view leaves `flow-runs` registered by\n\t\t\tnothing, and NcAppSidebarTabs falls back to the first tab it has,\n\t\t\twhich is Objects.\n\n\t\t\t`embedded` (CnFlowEditModal) has no NcAppSidebar to inject that, so\n\t\t\tit keeps a strip of its own. One list of tabs feeds both, so the two\n\t\t\thosts cannot come to offer different ones.\n\t\t-->\n\t\t<div v-if=\"embedded\" class=\"cn-run-sidebar__tabs\" role=\"tablist\">\n\t\t\t<button v-for=\"entry in tabs\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-run-sidebar__tab\"\n\t\t\t\t:class=\"{ 'cn-run-sidebar__tab--active': tab === entry.id }\"\n\t\t\t\trole=\"tab\"\n\t\t\t\t:data-testid=\"`flow-run-tab-${entry.id}`\"\n\t\t\t\t:aria-selected=\"tab === entry.id ? 'true' : 'false'\"\n\t\t\t\t@click=\"tab = entry.id\">\n\t\t\t\t{{ entry.label }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- Objects: what the run is about, and what it changed. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'objects'\"\n\t\t\t:id=\"embedded ? undefined : 'run-objects'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Objects')\"\n\t\t\t:order=\"embedded ? undefined : 1\"\n\t\t\tdata-testid=\"flow-run-panel-objects\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<DatabaseOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t\tTHE OBJECTS THE RUN IS ABOUT, FIRST.\n\n\t\t\t\t`GET /flow-runs/{uuid}/objects` answers the AUDIT: objects this\n\t\t\t\trun changed. A run that waited on a locked object and then\n\t\t\t\tfailed changed nothing, so that list is empty while the run's\n\t\t\t\town error names the object by uuid — and this panel said \"this\n\t\t\t\trun changed no objects\", which reads as the run having had\n\t\t\t\tnothing to do with it.\n\n\t\t\t\tThe run record knows better. `subjects` is what it was started\n\t\t\t\ton and `placeItems` is what it is holding at a step, so both are\n\t\t\t\tlisted here, and each row says which it is. The changed list\n\t\t\t\tkeeps its own heading below: \"worked on\" and \"changed\" are\n\t\t\t\tdifferent claims and this view exists to keep them apart.\n\t\t\t-->\n\t\t\t<template v-if=\"subjectRows.length\">\n\t\t\t\t<h5 class=\"cn-run-sidebar__group\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Objects this run is about') }}\n\t\t\t\t</h5>\n\t\t\t\t<ul class=\"cn-run-sidebar__touched\" data-testid=\"flow-run-subjects\">\n\t\t\t\t\t<!--\n\t\t\t\t\t\tSEPARATED IN THE TEXT, not only by layout. Three adjacent\n\t\t\t\t\t\tspans render as one run-on string: \"triggerDakkapel\n\t\t\t\t\t\tKerkstraat 122e98a265-...\" was what the live instance\n\t\t\t\t\t\tshowed. A gap would fix the look and leave the text\n\t\t\t\t\t\titself unreadable, which is what anything reading the\n\t\t\t\t\t\tDOM gets, so the separator is a character.\n\t\t\t\t\t-->\n\t\t\t\t\t<li v-for=\"obj in subjectRows\" :key=\"`${obj.role}-${obj.uuid}`\">\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.role }}</span>\n\t\t\t\t\t\t<span v-if=\"obj.title\"> · {{ obj.title }}</span>\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.uuid }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</template>\n\n\t\t\t<h5 v-if=\"subjectRows.length\" class=\"cn-run-sidebar__group\">\n\t\t\t\t{{ t('nextcloud-vue', 'Changed by this run') }}\n\t\t\t</h5>\n\t\t\t<p v-if=\"!objectRows.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run changed no objects.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__touched\">\n\t\t\t\t<!-- Same separator as the subject rows above, and for the same\n\t\t\t\t reason: \"create\" and the uuid ran together as \"createobj-1\"\n\t\t\t\t with nothing between them. -->\n\t\t\t\t<li v-for=\"obj in objectRows\" :key=\"obj.auditUuid\">\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.action }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.objectUuid }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ obj.node }}</span>\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA change the run's own step history cannot account for.\n\t\t\t\t\t\tIt should normally never appear; when it does, hiding it\n\t\t\t\t\t\twould be the opposite of what this view is for.\n\t\t\t\t\t-->\n\t\t\t\t\t<span v-if=\"obj.unaccounted\"\n\t\t\t\t\t\tclass=\"cn-run-sidebar__error\"\n\t\t\t\t\t\tdata-testid=\"flow-object-unaccounted\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'no matching step') }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Tasks: what the run asked a person to do. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'tasks'\"\n\t\t\t:id=\"embedded ? undefined : 'run-tasks'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Tasks')\"\n\t\t\t:order=\"embedded ? undefined : 2\"\n\t\t\tdata-testid=\"flow-run-panel-tasks\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<CheckboxMarkedOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<p v-if=\"!store.runTasks.length\"\n\t\t\t\tclass=\"cn-run-sidebar__hint\"\n\t\t\t\tdata-testid=\"flow-run-tasks-empty\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run raised no tasks.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__list\">\n\t\t\t\t<li v-for=\"task in store.runTasks\" :key=\"task.uuid\">\n\t\t\t\t\t<!-- The task's one stable address, the same one the\n\t\t\t\t\t notification buttons and the VTODO already resolve to. -->\n\t\t\t\t\t<a class=\"cn-run-sidebar__row\"\n\t\t\t\t\t\t:href=\"taskUrl(task.uuid)\"\n\t\t\t\t\t\tdata-testid=\"flow-task-link\">\n\t\t\t\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${task.state}`\">{{ task.state }}</span>\n\t\t\t\t\t\t<span>{{ task.title || task.uuid }}</span>\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Logs: what the engine did, in its own order. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'logs'\"\n\t\t\t:id=\"embedded ? undefined : 'run-logs'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Logs')\"\n\t\t\t:order=\"embedded ? undefined : 3\"\n\t\t\tdata-testid=\"flow-run-panel-logs\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<FormatListBulleted :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!-- Replay BESIDE the step list, not instead of it: the list stays\n\t\t\t what it is for reading, the replay plays the same stored log\n\t\t\t through the canvas animator. Only on a FINISHED run — a run\n\t\t\t still going is watched live. -->\n\t\t\t<NcButton v-if=\"canReplay\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\tdata-testid=\"flow-replay\"\n\t\t\t\t@click=\"store.requestReplay()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Replay :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Replay on the canvas') }}\n\t\t\t</NcButton>\n\n\t\t\t<p v-if=\"!store.steps.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run recorded no steps.') }}\n\t\t\t</p>\n\t\t\t<ol v-else class=\"cn-run-sidebar__steps\">\n\t\t\t\t<li v-for=\"(step, i) in store.steps\" :key=\"i\">\n\t\t\t\t\t<strong>{{ step.transition }}</strong>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ step.status }}</span>\n\t\t\t\t\t<span v-if=\"step.error\" class=\"cn-run-sidebar__error\"> · {{ step.error }}</span>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</component>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppSidebarTab, NcButton } from '@nextcloud/vue'\nimport ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport Replay from 'vue-material-design-icons/Replay.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\n\nexport default {\n\tname: 'CnRunDetailSidebar',\n\n\tcomponents: {\n\t\tArrowLeft,\n\t\tCheckboxMarkedOutline,\n\t\tDatabaseOutline,\n\t\tFormatListBulleted,\n\t\tNcAppSidebarTab,\n\t\tNcButton,\n\t\tReplay,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Render the panels without NcAppSidebarTab, for a host that is not\n\t\t * NcAppSidebar. CnFlowEditModal renders this sidebar inside a dialog,\n\t\t * where there is no sidebar to register a tab with, so that host keeps\n\t\t * a strip of its own.\n\t\t */\n\t\tembedded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { tab: 'objects' }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The run being read.\n\t\t *\n\t\t * The run's OWN record first, the history list second. The list is\n\t\t * capped at 25, so a run opened from a `?run=` deep link need not be in\n\t\t * it, and reading only the list left such a run with no status, no time\n\t\t * and no error on screen.\n\t\t *\n\t\t * @return {object|null} The run being read.\n\t\t */\n\t\trun() {\n\t\t\tconst detail = this.store.runDetail\n\t\t\tif (detail && String(detail.uuid || '') === String(this.store.inspectedRunUuid || '')) {\n\t\t\t\treturn detail\n\t\t\t}\n\n\t\t\treturn this.store.runs.find((r) => r.uuid === this.store.inspectedRunUuid) || null\n\t\t},\n\n\t\t/**\n\t\t * The objects the run is ABOUT, as opposed to the ones it changed.\n\t\t *\n\t\t * Two sources, both on the run's own record. `subjects` is what the run\n\t\t * was started on, keyed by the role that supplied it (`trigger` for a\n\t\t * run a trigger started). `placeItems` is what the run is holding at a\n\t\t * step right now, keyed by that step.\n\t\t *\n\t\t * Deduplicated by uuid, because a run's subject is normally also the\n\t\t * item sitting in a place and listing it twice would suggest two\n\t\t * objects. The subject wins the row: it is the more durable fact.\n\t\t *\n\t\t * @return {Array<object>} The rows, subjects first.\n\t\t */\n\t\tsubjectRows() {\n\t\t\tconst run = this.run\n\t\t\tif (!run) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\t// The held items first, because they are the half that carries a\n\t\t\t// TITLE. A subject record is a uuid, a register and a schema; the\n\t\t\t// item sitting in a place is the object itself. Merging the two\n\t\t\t// gives a subject row a name a reader recognises instead of a uuid.\n\t\t\tconst held = new Map()\n\t\t\tfor (const [node, items] of Object.entries(run.placeItems || {})) {\n\t\t\t\tfor (const item of items || []) {\n\t\t\t\t\tconst uuid = String(item?.json?.id || item?.id || '')\n\t\t\t\t\tif (uuid === '' || held.has(uuid)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\theld.set(uuid, {\n\t\t\t\t\t\t// Named by the step that is holding it, because that is\n\t\t\t\t\t\t// the useful half: \"which step is this stuck at\".\n\t\t\t\t\t\trole: this.t('nextcloud-vue', 'at {node}', { node }),\n\t\t\t\t\t\tuuid,\n\t\t\t\t\t\ttitle: item?.json?.title || '',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rows = []\n\t\t\tconst seen = new Set()\n\n\t\t\tfor (const [role, subject] of Object.entries(run.subjects || {})) {\n\t\t\t\tconst uuid = String(subject?.uuid || '')\n\t\t\t\tif (uuid === '' || seen.has(uuid)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tseen.add(uuid)\n\t\t\t\trows.push({\n\t\t\t\t\trole,\n\t\t\t\t\tuuid,\n\t\t\t\t\ttitle: subject?.title || held.get(uuid)?.title || '',\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor (const [uuid, row] of held.entries()) {\n\t\t\t\tif (seen.has(uuid) === false) {\n\t\t\t\t\tseen.add(uuid)\n\t\t\t\t\trows.push(row)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rows\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The strip.\n\t\t */\n\t\ttabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'objects', label: this.t('nextcloud-vue', 'Objects') },\n\t\t\t\t{ id: 'tasks', label: this.t('nextcloud-vue', 'Tasks') },\n\t\t\t\t{ id: 'logs', label: this.t('nextcloud-vue', 'Logs') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Every object the run touched, flattened, each marked with the step\n\t\t * that accounts for it.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\tobjectRows() {\n\t\t\tconst stepNodes = new Set((this.store.steps || []).map((step) => String(step.transition || '')))\n\n\t\t\treturn (this.store.runObjects || []).flatMap((group) => (group.objects || []).map((obj) => ({\n\t\t\t\t...obj,\n\t\t\t\tnode: group.node,\n\t\t\t\tunaccounted: !stepNodes.has(String(group.node || '')),\n\t\t\t})))\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when Replay is offered.\n\t\t */\n\t\tcanReplay() {\n\t\t\tif (this.store.steps.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst status = this.run?.status\n\t\t\treturn status !== undefined && FLOW_RUN_ACTIVE_STATUSES.includes(status) === false\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} uuid The task uuid.\n\t\t * @return {string} The task's own URL.\n\t\t */\n\t\ttaskUrl(uuid) {\n\t\t\treturn generateUrl(`/apps/openregister/flow-tasks/${uuid}`)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-sidebar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 12px;\n}\n\n.cn-run-sidebar__head {\n\tdisplay: flex;\n}\n\n.cn-run-sidebar__title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n}\n\n.cn-run-sidebar__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: normal;\n\tfont-size: 0.9em;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-run-sidebar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-run-sidebar__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-run-sidebar__group {\n\tmargin: 8px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-block-end: 1px solid var(--color-border);\n}\n\n.cn-run-sidebar__tab {\n\tborder: none;\n\tbackground: none;\n\tpadding: 8px 12px;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n\tborder-block-end: 2px solid transparent;\n}\n\n.cn-run-sidebar__tab--active {\n\tborder-block-end-color: var(--color-primary-element);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__list,\n.cn-run-sidebar__touched,\n.cn-run-sidebar__steps {\n\tmargin: 0;\n\tpadding-inline-start: 0;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-run-sidebar__steps {\n\tlist-style: decimal;\n\tpadding-inline-start: 20px;\n}\n\n.cn-run-sidebar__row {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tcolor: inherit;\n\tpadding: 6px 8px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-run-sidebar__touched-uuid {\n\tfont-family: monospace;\n\tfont-size: 0.9em;\n}\n\n/* A uuid is long and the sidebar is narrow, so a row wraps rather than\n pushing the panel wider. The gap keeps the parts apart on the line the\n separators already keep apart in the text. */\n.cn-run-sidebar__touched li {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 0 4px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnRunDetailSidebar.vue2.js","sources":["../../../../src/components/CnFlowDetail/CnRunDetailSidebar.vue"],"sourcesContent":["<!--\n CnRunDetailSidebar — one run: what it changed, what it asked a person to do,\n and what it did.\n\n WHY THIS IS A SIDEBAR AND NOT A PANEL INSIDE THE FLOW'S\n -------------------------------------------------------\n It used to be a block nested inside the Runs tab, with a tab strip of its own.\n That put TABS INSIDE A TAB: the flow's strip (Steps, Runs) stayed on screen\n above the run's (Objects, Tasks, Logs), and the two competed for the same\n glance. Worse, the flow's tabs were still live — pressing Steps while reading\n a run swapped the panel and left the canvas painted with that run's badges,\n so the graph and the sidebar were describing different things.\n\n So opening a run REPLACES the flow's sidebar rather than nesting inside it.\n One strip at a time, and the way back is a deliberate act with its own\n control. The canvas keeps the run's replay while this is open, which is now\n consistent rather than accidental: the sidebar and the graph are showing the\n same run because there is no way to make them show anything else.\n\n WHAT IT DOES NOT OWN\n --------------------\n The run's badges on the canvas. Those are the flow detail's animation, driven\n from `store.inspectedRunUuid` and the stored log. This component reads the\n same run; it does not paint.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<section class=\"cn-run-sidebar\" data-testid=\"run-detail-sidebar\">\n\t\t<!--\n\t\t\tTHE WAY BACK, FIRST AND ALWAYS. A view that replaces another must\n\t\t\tsay what it replaced and how to undo that, or it reads as the\n\t\t\tapplication having navigated somewhere on its own.\n\t\t-->\n\t\t<div class=\"cn-run-sidebar__head\">\n\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\tdata-testid=\"run-back\"\n\t\t\t\t@click=\"store.closeRun()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ArrowLeft :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Back to the flow') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<h4 class=\"cn-run-sidebar__title\">\n\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${run?.status}`\"\n\t\t\t\tdata-testid=\"run-status\">{{ run?.status || t('nextcloud-vue', 'Run') }}</span>\n\t\t\t<time v-if=\"run?.created\" class=\"cn-run-sidebar__when\" :datetime=\"run.created\">{{ run.created }}</time>\n\t\t</h4>\n\n\t\t<p v-if=\"run?.error\" class=\"cn-run-sidebar__error\" data-testid=\"run-error\">\n\t\t\t{{ run.error }}\n\t\t</p>\n\n\t\t<!--\n\t\t\tTHE STRIP IS NEXTCLOUD'S, NOT OURS.\n\n\t\t\tThese three used to be hand-rolled buttons with a border under the\n\t\t\tactive one. They sat directly inside NcAppSidebar, one element away\n\t\t\tfrom the real thing, so they read as a panel that had been dropped\n\t\t\tinto the sidebar rather than as the sidebar's own tabs: no icons,\n\t\t\tthe wrong size, the wrong active mark, and none of the keyboard\n\t\t\tbehaviour NcAppSidebarTabs brings.\n\n\t\t\tNcAppSidebarTab registers itself with the sidebar through an\n\t\t\tinjection, so the sidebar builds the strip. That also settles which\n\t\t\ttab is open: entering run view leaves `flow-runs` registered by\n\t\t\tnothing, and NcAppSidebarTabs falls back to the first tab it has,\n\t\t\twhich is Objects.\n\n\t\t\t`embedded` (CnFlowEditModal) has no NcAppSidebar to inject that, so\n\t\t\tit keeps a strip of its own. One list of tabs feeds both, so the two\n\t\t\thosts cannot come to offer different ones.\n\t\t-->\n\t\t<div v-if=\"embedded\" class=\"cn-run-sidebar__tabs\" role=\"tablist\">\n\t\t\t<button v-for=\"entry in tabs\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-run-sidebar__tab\"\n\t\t\t\t:class=\"{ 'cn-run-sidebar__tab--active': tab === entry.id }\"\n\t\t\t\trole=\"tab\"\n\t\t\t\t:data-testid=\"`flow-run-tab-${entry.id}`\"\n\t\t\t\t:aria-selected=\"tab === entry.id ? 'true' : 'false'\"\n\t\t\t\t@click=\"tab = entry.id\">\n\t\t\t\t{{ entry.label }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- Objects: what the run is about, and what it changed. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'objects'\"\n\t\t\t:id=\"embedded ? undefined : 'run-objects'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Objects')\"\n\t\t\t:order=\"embedded ? undefined : 1\"\n\t\t\tdata-testid=\"flow-run-panel-objects\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<DatabaseOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t\tTHE OBJECTS THE RUN IS ABOUT, FIRST.\n\n\t\t\t\t`GET /flow-runs/{uuid}/objects` answers the AUDIT: objects this\n\t\t\t\trun changed. A run that waited on a locked object and then\n\t\t\t\tfailed changed nothing, so that list is empty while the run's\n\t\t\t\town error names the object by uuid — and this panel said \"this\n\t\t\t\trun changed no objects\", which reads as the run having had\n\t\t\t\tnothing to do with it.\n\n\t\t\t\tThe run record knows better. `subjects` is what it was started\n\t\t\t\ton and `placeItems` is what it is holding at a step, so both are\n\t\t\t\tlisted here, and each row says which it is. The changed list\n\t\t\t\tkeeps its own heading below: \"worked on\" and \"changed\" are\n\t\t\t\tdifferent claims and this view exists to keep them apart.\n\t\t\t-->\n\t\t\t<template v-if=\"subjectRows.length\">\n\t\t\t\t<h5 class=\"cn-run-sidebar__group\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Objects this run is about') }}\n\t\t\t\t</h5>\n\t\t\t\t<ul class=\"cn-run-sidebar__touched\" data-testid=\"flow-run-subjects\">\n\t\t\t\t\t<!--\n\t\t\t\t\t\tSEPARATED IN THE TEXT, not only by layout. Three adjacent\n\t\t\t\t\t\tspans render as one run-on string: \"triggerDakkapel\n\t\t\t\t\t\tKerkstraat 122e98a265-...\" was what the live instance\n\t\t\t\t\t\tshowed. A gap would fix the look and leave the text\n\t\t\t\t\t\titself unreadable, which is what anything reading the\n\t\t\t\t\t\tDOM gets, so the separator is a character.\n\t\t\t\t\t-->\n\t\t\t\t\t<li v-for=\"obj in subjectRows\" :key=\"`${obj.role}-${obj.uuid}`\">\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.role }}</span>\n\t\t\t\t\t\t<span v-if=\"obj.title\"> · {{ obj.title }}</span>\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.uuid }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</template>\n\n\t\t\t<h5 v-if=\"subjectRows.length\" class=\"cn-run-sidebar__group\">\n\t\t\t\t{{ t('nextcloud-vue', 'Changed by this run') }}\n\t\t\t</h5>\n\t\t\t<p v-if=\"!objectRows.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run changed no objects.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__touched\">\n\t\t\t\t<!-- Same separator as the subject rows above, and for the same\n\t\t\t\t reason: \"create\" and the uuid ran together as \"createobj-1\"\n\t\t\t\t with nothing between them. -->\n\t\t\t\t<li v-for=\"obj in objectRows\" :key=\"obj.auditUuid\">\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.action }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.objectUuid }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ obj.node }}</span>\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA change the run's own step history cannot account for.\n\t\t\t\t\t\tIt should normally never appear; when it does, hiding it\n\t\t\t\t\t\twould be the opposite of what this view is for.\n\t\t\t\t\t-->\n\t\t\t\t\t<span v-if=\"obj.unaccounted\"\n\t\t\t\t\t\tclass=\"cn-run-sidebar__error\"\n\t\t\t\t\t\tdata-testid=\"flow-object-unaccounted\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'no matching step') }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Tasks: what the run asked a person to do. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'tasks'\"\n\t\t\t:id=\"embedded ? undefined : 'run-tasks'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Tasks')\"\n\t\t\t:order=\"embedded ? undefined : 2\"\n\t\t\tdata-testid=\"flow-run-panel-tasks\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<CheckboxMarkedOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<p v-if=\"!store.runTasks.length\"\n\t\t\t\tclass=\"cn-run-sidebar__hint\"\n\t\t\t\tdata-testid=\"flow-run-tasks-empty\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run raised no tasks.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__list\">\n\t\t\t\t<li v-for=\"task in store.runTasks\" :key=\"task.uuid\">\n\t\t\t\t\t<!-- The task's one stable address, the same one the\n\t\t\t\t\t notification buttons and the VTODO already resolve to. -->\n\t\t\t\t\t<a class=\"cn-run-sidebar__row\"\n\t\t\t\t\t\t:href=\"taskUrl(task.uuid)\"\n\t\t\t\t\t\tdata-testid=\"flow-task-link\">\n\t\t\t\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${task.state}`\">{{ task.state }}</span>\n\t\t\t\t\t\t<span>{{ task.title || task.uuid }}</span>\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Logs: what the engine did, in its own order. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'logs'\"\n\t\t\t:id=\"embedded ? undefined : 'run-logs'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Logs')\"\n\t\t\t:order=\"embedded ? undefined : 3\"\n\t\t\tdata-testid=\"flow-run-panel-logs\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<FormatListBulleted :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!-- Replay BESIDE the step list, not instead of it: the list stays\n\t\t\t what it is for reading, the replay plays the same stored log\n\t\t\t through the canvas animator. Only on a FINISHED run — a run\n\t\t\t still going is watched live. -->\n\t\t\t<NcButton v-if=\"canReplay\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\tdata-testid=\"flow-replay\"\n\t\t\t\t@click=\"store.requestReplay()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Replay :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Replay on the canvas') }}\n\t\t\t</NcButton>\n\n\t\t\t<p v-if=\"!store.steps.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run recorded no steps.') }}\n\t\t\t</p>\n\t\t\t<ol v-else class=\"cn-run-sidebar__steps\">\n\t\t\t\t<li v-for=\"(step, i) in store.steps\" :key=\"i\">\n\t\t\t\t\t<strong>{{ step.transition }}</strong>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ step.status }}</span>\n\t\t\t\t\t<span v-if=\"step.error\" class=\"cn-run-sidebar__error\"> · {{ step.error }}</span>\n\t\t\t\t\t<!-- A send step's outcome lists, by name: who got it, who\n\t\t\t\t\t opted out, who the opt-out check could not answer for. -->\n\t\t\t\t\t<CnFlowStepOutcomes v-if=\"step.report && step.report.messaging\"\n\t\t\t\t\t\t:report=\"step.report.messaging\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</component>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppSidebarTab, NcButton } from '@nextcloud/vue'\nimport ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport Replay from 'vue-material-design-icons/Replay.vue'\nimport CnFlowStepOutcomes from '../CnFlowStepOutcomes/CnFlowStepOutcomes.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\n\nexport default {\n\tname: 'CnRunDetailSidebar',\n\n\tcomponents: {\n\t\tArrowLeft,\n\t\tCheckboxMarkedOutline,\n\t\tCnFlowStepOutcomes,\n\t\tDatabaseOutline,\n\t\tFormatListBulleted,\n\t\tNcAppSidebarTab,\n\t\tNcButton,\n\t\tReplay,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Render the panels without NcAppSidebarTab, for a host that is not\n\t\t * NcAppSidebar. CnFlowEditModal renders this sidebar inside a dialog,\n\t\t * where there is no sidebar to register a tab with, so that host keeps\n\t\t * a strip of its own.\n\t\t */\n\t\tembedded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { tab: 'objects' }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The run being read.\n\t\t *\n\t\t * The run's OWN record first, the history list second. The list is\n\t\t * capped at 25, so a run opened from a `?run=` deep link need not be in\n\t\t * it, and reading only the list left such a run with no status, no time\n\t\t * and no error on screen.\n\t\t *\n\t\t * @return {object|null} The run being read.\n\t\t */\n\t\trun() {\n\t\t\tconst detail = this.store.runDetail\n\t\t\tif (detail && String(detail.uuid || '') === String(this.store.inspectedRunUuid || '')) {\n\t\t\t\treturn detail\n\t\t\t}\n\n\t\t\treturn this.store.runs.find((r) => r.uuid === this.store.inspectedRunUuid) || null\n\t\t},\n\n\t\t/**\n\t\t * The objects the run is ABOUT, as opposed to the ones it changed.\n\t\t *\n\t\t * Two sources, both on the run's own record. `subjects` is what the run\n\t\t * was started on, keyed by the role that supplied it (`trigger` for a\n\t\t * run a trigger started). `placeItems` is what the run is holding at a\n\t\t * step right now, keyed by that step.\n\t\t *\n\t\t * Deduplicated by uuid, because a run's subject is normally also the\n\t\t * item sitting in a place and listing it twice would suggest two\n\t\t * objects. The subject wins the row: it is the more durable fact.\n\t\t *\n\t\t * @return {Array<object>} The rows, subjects first.\n\t\t */\n\t\tsubjectRows() {\n\t\t\tconst run = this.run\n\t\t\tif (!run) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\t// The held items first, because they are the half that carries a\n\t\t\t// TITLE. A subject record is a uuid, a register and a schema; the\n\t\t\t// item sitting in a place is the object itself. Merging the two\n\t\t\t// gives a subject row a name a reader recognises instead of a uuid.\n\t\t\tconst held = new Map()\n\t\t\tfor (const [node, items] of Object.entries(run.placeItems || {})) {\n\t\t\t\tfor (const item of items || []) {\n\t\t\t\t\tconst uuid = String(item?.json?.id || item?.id || '')\n\t\t\t\t\tif (uuid === '' || held.has(uuid)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\theld.set(uuid, {\n\t\t\t\t\t\t// Named by the step that is holding it, because that is\n\t\t\t\t\t\t// the useful half: \"which step is this stuck at\".\n\t\t\t\t\t\trole: this.t('nextcloud-vue', 'at {node}', { node }),\n\t\t\t\t\t\tuuid,\n\t\t\t\t\t\ttitle: item?.json?.title || '',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rows = []\n\t\t\tconst seen = new Set()\n\n\t\t\tfor (const [role, subject] of Object.entries(run.subjects || {})) {\n\t\t\t\tconst uuid = String(subject?.uuid || '')\n\t\t\t\tif (uuid === '' || seen.has(uuid)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tseen.add(uuid)\n\t\t\t\trows.push({\n\t\t\t\t\trole,\n\t\t\t\t\tuuid,\n\t\t\t\t\ttitle: subject?.title || held.get(uuid)?.title || '',\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor (const [uuid, row] of held.entries()) {\n\t\t\t\tif (seen.has(uuid) === false) {\n\t\t\t\t\tseen.add(uuid)\n\t\t\t\t\trows.push(row)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rows\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The strip.\n\t\t */\n\t\ttabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'objects', label: this.t('nextcloud-vue', 'Objects') },\n\t\t\t\t{ id: 'tasks', label: this.t('nextcloud-vue', 'Tasks') },\n\t\t\t\t{ id: 'logs', label: this.t('nextcloud-vue', 'Logs') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Every object the run touched, flattened, each marked with the step\n\t\t * that accounts for it.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\tobjectRows() {\n\t\t\tconst stepNodes = new Set((this.store.steps || []).map((step) => String(step.transition || '')))\n\n\t\t\treturn (this.store.runObjects || []).flatMap((group) => (group.objects || []).map((obj) => ({\n\t\t\t\t...obj,\n\t\t\t\tnode: group.node,\n\t\t\t\tunaccounted: !stepNodes.has(String(group.node || '')),\n\t\t\t})))\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when Replay is offered.\n\t\t */\n\t\tcanReplay() {\n\t\t\tif (this.store.steps.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst status = this.run?.status\n\t\t\treturn status !== undefined && FLOW_RUN_ACTIVE_STATUSES.includes(status) === false\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} uuid The task uuid.\n\t\t * @return {string} The task's own URL.\n\t\t */\n\t\ttaskUrl(uuid) {\n\t\t\treturn generateUrl(`/apps/openregister/flow-tasks/${uuid}`)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-sidebar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 12px;\n}\n\n.cn-run-sidebar__head {\n\tdisplay: flex;\n}\n\n.cn-run-sidebar__title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n}\n\n.cn-run-sidebar__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: normal;\n\tfont-size: 0.9em;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-run-sidebar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-run-sidebar__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-run-sidebar__group {\n\tmargin: 8px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-block-end: 1px solid var(--color-border);\n}\n\n.cn-run-sidebar__tab {\n\tborder: none;\n\tbackground: none;\n\tpadding: 8px 12px;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n\tborder-block-end: 2px solid transparent;\n}\n\n.cn-run-sidebar__tab--active {\n\tborder-block-end-color: var(--color-primary-element);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__list,\n.cn-run-sidebar__touched,\n.cn-run-sidebar__steps {\n\tmargin: 0;\n\tpadding-inline-start: 0;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-run-sidebar__steps {\n\tlist-style: decimal;\n\tpadding-inline-start: 20px;\n}\n\n.cn-run-sidebar__row {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tcolor: inherit;\n\tpadding: 6px 8px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-run-sidebar__touched-uuid {\n\tfont-family: monospace;\n\tfont-size: 0.9em;\n}\n\n/* A uuid is long and the sidebar is narrow, so a row wraps rather than\n pushing the panel wider. The gap keeps the parts apart on the line the\n separators already keep apart in the text. */\n.cn-run-sidebar__touched li {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 0 4px;\n}\n</style>\n"],"names":["CnFlowStepOutcomes","t"],"mappings":"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"}
|
|
@@ -77,6 +77,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
77
77
|
const _component_CheckboxMarkedOutline = resolveComponent("CheckboxMarkedOutline");
|
|
78
78
|
const _component_FormatListBulleted = resolveComponent("FormatListBulleted");
|
|
79
79
|
const _component_Replay = resolveComponent("Replay");
|
|
80
|
+
const _component_CnFlowStepOutcomes = resolveComponent("CnFlowStepOutcomes");
|
|
80
81
|
|
|
81
82
|
return (openBlock(), createElementBlock("section", _hoisted_1, [
|
|
82
83
|
createElementVNode("div", _hoisted_2, [
|
|
@@ -261,6 +262,12 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
261
262
|
createElementVNode("span", _hoisted_25, " · " + toDisplayString(step.status), 1 /* TEXT */),
|
|
262
263
|
(step.error)
|
|
263
264
|
? (openBlock(), createElementBlock("span", _hoisted_26, " · " + toDisplayString(step.error), 1 /* TEXT */))
|
|
265
|
+
: createCommentVNode("v-if", true),
|
|
266
|
+
(step.report && step.report.messaging)
|
|
267
|
+
? (openBlock(), createBlock(_component_CnFlowStepOutcomes, {
|
|
268
|
+
key: 1,
|
|
269
|
+
report: step.report.messaging
|
|
270
|
+
}, null, 8 /* PROPS */, ["report"]))
|
|
264
271
|
: createCommentVNode("v-if", true)
|
|
265
272
|
]))
|
|
266
273
|
}), 128 /* KEYED_FRAGMENT */))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRunDetailSidebar.vue3.js","sources":["../../../../src/components/CnFlowDetail/CnRunDetailSidebar.vue?vue&type=template&id=48e781f0&lang.js"],"sourcesContent":["<!--\n CnRunDetailSidebar — one run: what it changed, what it asked a person to do,\n and what it did.\n\n WHY THIS IS A SIDEBAR AND NOT A PANEL INSIDE THE FLOW'S\n -------------------------------------------------------\n It used to be a block nested inside the Runs tab, with a tab strip of its own.\n That put TABS INSIDE A TAB: the flow's strip (Steps, Runs) stayed on screen\n above the run's (Objects, Tasks, Logs), and the two competed for the same\n glance. Worse, the flow's tabs were still live — pressing Steps while reading\n a run swapped the panel and left the canvas painted with that run's badges,\n so the graph and the sidebar were describing different things.\n\n So opening a run REPLACES the flow's sidebar rather than nesting inside it.\n One strip at a time, and the way back is a deliberate act with its own\n control. The canvas keeps the run's replay while this is open, which is now\n consistent rather than accidental: the sidebar and the graph are showing the\n same run because there is no way to make them show anything else.\n\n WHAT IT DOES NOT OWN\n --------------------\n The run's badges on the canvas. Those are the flow detail's animation, driven\n from `store.inspectedRunUuid` and the stored log. This component reads the\n same run; it does not paint.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<section class=\"cn-run-sidebar\" data-testid=\"run-detail-sidebar\">\n\t\t<!--\n\t\t\tTHE WAY BACK, FIRST AND ALWAYS. A view that replaces another must\n\t\t\tsay what it replaced and how to undo that, or it reads as the\n\t\t\tapplication having navigated somewhere on its own.\n\t\t-->\n\t\t<div class=\"cn-run-sidebar__head\">\n\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\tdata-testid=\"run-back\"\n\t\t\t\t@click=\"store.closeRun()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ArrowLeft :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Back to the flow') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<h4 class=\"cn-run-sidebar__title\">\n\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${run?.status}`\"\n\t\t\t\tdata-testid=\"run-status\">{{ run?.status || t('nextcloud-vue', 'Run') }}</span>\n\t\t\t<time v-if=\"run?.created\" class=\"cn-run-sidebar__when\" :datetime=\"run.created\">{{ run.created }}</time>\n\t\t</h4>\n\n\t\t<p v-if=\"run?.error\" class=\"cn-run-sidebar__error\" data-testid=\"run-error\">\n\t\t\t{{ run.error }}\n\t\t</p>\n\n\t\t<!--\n\t\t\tTHE STRIP IS NEXTCLOUD'S, NOT OURS.\n\n\t\t\tThese three used to be hand-rolled buttons with a border under the\n\t\t\tactive one. They sat directly inside NcAppSidebar, one element away\n\t\t\tfrom the real thing, so they read as a panel that had been dropped\n\t\t\tinto the sidebar rather than as the sidebar's own tabs: no icons,\n\t\t\tthe wrong size, the wrong active mark, and none of the keyboard\n\t\t\tbehaviour NcAppSidebarTabs brings.\n\n\t\t\tNcAppSidebarTab registers itself with the sidebar through an\n\t\t\tinjection, so the sidebar builds the strip. That also settles which\n\t\t\ttab is open: entering run view leaves `flow-runs` registered by\n\t\t\tnothing, and NcAppSidebarTabs falls back to the first tab it has,\n\t\t\twhich is Objects.\n\n\t\t\t`embedded` (CnFlowEditModal) has no NcAppSidebar to inject that, so\n\t\t\tit keeps a strip of its own. One list of tabs feeds both, so the two\n\t\t\thosts cannot come to offer different ones.\n\t\t-->\n\t\t<div v-if=\"embedded\" class=\"cn-run-sidebar__tabs\" role=\"tablist\">\n\t\t\t<button v-for=\"entry in tabs\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-run-sidebar__tab\"\n\t\t\t\t:class=\"{ 'cn-run-sidebar__tab--active': tab === entry.id }\"\n\t\t\t\trole=\"tab\"\n\t\t\t\t:data-testid=\"`flow-run-tab-${entry.id}`\"\n\t\t\t\t:aria-selected=\"tab === entry.id ? 'true' : 'false'\"\n\t\t\t\t@click=\"tab = entry.id\">\n\t\t\t\t{{ entry.label }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- Objects: what the run is about, and what it changed. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'objects'\"\n\t\t\t:id=\"embedded ? undefined : 'run-objects'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Objects')\"\n\t\t\t:order=\"embedded ? undefined : 1\"\n\t\t\tdata-testid=\"flow-run-panel-objects\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<DatabaseOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t\tTHE OBJECTS THE RUN IS ABOUT, FIRST.\n\n\t\t\t\t`GET /flow-runs/{uuid}/objects` answers the AUDIT: objects this\n\t\t\t\trun changed. A run that waited on a locked object and then\n\t\t\t\tfailed changed nothing, so that list is empty while the run's\n\t\t\t\town error names the object by uuid — and this panel said \"this\n\t\t\t\trun changed no objects\", which reads as the run having had\n\t\t\t\tnothing to do with it.\n\n\t\t\t\tThe run record knows better. `subjects` is what it was started\n\t\t\t\ton and `placeItems` is what it is holding at a step, so both are\n\t\t\t\tlisted here, and each row says which it is. The changed list\n\t\t\t\tkeeps its own heading below: \"worked on\" and \"changed\" are\n\t\t\t\tdifferent claims and this view exists to keep them apart.\n\t\t\t-->\n\t\t\t<template v-if=\"subjectRows.length\">\n\t\t\t\t<h5 class=\"cn-run-sidebar__group\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Objects this run is about') }}\n\t\t\t\t</h5>\n\t\t\t\t<ul class=\"cn-run-sidebar__touched\" data-testid=\"flow-run-subjects\">\n\t\t\t\t\t<!--\n\t\t\t\t\t\tSEPARATED IN THE TEXT, not only by layout. Three adjacent\n\t\t\t\t\t\tspans render as one run-on string: \"triggerDakkapel\n\t\t\t\t\t\tKerkstraat 122e98a265-...\" was what the live instance\n\t\t\t\t\t\tshowed. A gap would fix the look and leave the text\n\t\t\t\t\t\titself unreadable, which is what anything reading the\n\t\t\t\t\t\tDOM gets, so the separator is a character.\n\t\t\t\t\t-->\n\t\t\t\t\t<li v-for=\"obj in subjectRows\" :key=\"`${obj.role}-${obj.uuid}`\">\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.role }}</span>\n\t\t\t\t\t\t<span v-if=\"obj.title\"> · {{ obj.title }}</span>\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.uuid }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</template>\n\n\t\t\t<h5 v-if=\"subjectRows.length\" class=\"cn-run-sidebar__group\">\n\t\t\t\t{{ t('nextcloud-vue', 'Changed by this run') }}\n\t\t\t</h5>\n\t\t\t<p v-if=\"!objectRows.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run changed no objects.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__touched\">\n\t\t\t\t<!-- Same separator as the subject rows above, and for the same\n\t\t\t\t reason: \"create\" and the uuid ran together as \"createobj-1\"\n\t\t\t\t with nothing between them. -->\n\t\t\t\t<li v-for=\"obj in objectRows\" :key=\"obj.auditUuid\">\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.action }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.objectUuid }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ obj.node }}</span>\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA change the run's own step history cannot account for.\n\t\t\t\t\t\tIt should normally never appear; when it does, hiding it\n\t\t\t\t\t\twould be the opposite of what this view is for.\n\t\t\t\t\t-->\n\t\t\t\t\t<span v-if=\"obj.unaccounted\"\n\t\t\t\t\t\tclass=\"cn-run-sidebar__error\"\n\t\t\t\t\t\tdata-testid=\"flow-object-unaccounted\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'no matching step') }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Tasks: what the run asked a person to do. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'tasks'\"\n\t\t\t:id=\"embedded ? undefined : 'run-tasks'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Tasks')\"\n\t\t\t:order=\"embedded ? undefined : 2\"\n\t\t\tdata-testid=\"flow-run-panel-tasks\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<CheckboxMarkedOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<p v-if=\"!store.runTasks.length\"\n\t\t\t\tclass=\"cn-run-sidebar__hint\"\n\t\t\t\tdata-testid=\"flow-run-tasks-empty\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run raised no tasks.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__list\">\n\t\t\t\t<li v-for=\"task in store.runTasks\" :key=\"task.uuid\">\n\t\t\t\t\t<!-- The task's one stable address, the same one the\n\t\t\t\t\t notification buttons and the VTODO already resolve to. -->\n\t\t\t\t\t<a class=\"cn-run-sidebar__row\"\n\t\t\t\t\t\t:href=\"taskUrl(task.uuid)\"\n\t\t\t\t\t\tdata-testid=\"flow-task-link\">\n\t\t\t\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${task.state}`\">{{ task.state }}</span>\n\t\t\t\t\t\t<span>{{ task.title || task.uuid }}</span>\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Logs: what the engine did, in its own order. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'logs'\"\n\t\t\t:id=\"embedded ? undefined : 'run-logs'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Logs')\"\n\t\t\t:order=\"embedded ? undefined : 3\"\n\t\t\tdata-testid=\"flow-run-panel-logs\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<FormatListBulleted :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!-- Replay BESIDE the step list, not instead of it: the list stays\n\t\t\t what it is for reading, the replay plays the same stored log\n\t\t\t through the canvas animator. Only on a FINISHED run — a run\n\t\t\t still going is watched live. -->\n\t\t\t<NcButton v-if=\"canReplay\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\tdata-testid=\"flow-replay\"\n\t\t\t\t@click=\"store.requestReplay()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Replay :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Replay on the canvas') }}\n\t\t\t</NcButton>\n\n\t\t\t<p v-if=\"!store.steps.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run recorded no steps.') }}\n\t\t\t</p>\n\t\t\t<ol v-else class=\"cn-run-sidebar__steps\">\n\t\t\t\t<li v-for=\"(step, i) in store.steps\" :key=\"i\">\n\t\t\t\t\t<strong>{{ step.transition }}</strong>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ step.status }}</span>\n\t\t\t\t\t<span v-if=\"step.error\" class=\"cn-run-sidebar__error\"> · {{ step.error }}</span>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</component>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppSidebarTab, NcButton } from '@nextcloud/vue'\nimport ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport Replay from 'vue-material-design-icons/Replay.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\n\nexport default {\n\tname: 'CnRunDetailSidebar',\n\n\tcomponents: {\n\t\tArrowLeft,\n\t\tCheckboxMarkedOutline,\n\t\tDatabaseOutline,\n\t\tFormatListBulleted,\n\t\tNcAppSidebarTab,\n\t\tNcButton,\n\t\tReplay,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Render the panels without NcAppSidebarTab, for a host that is not\n\t\t * NcAppSidebar. CnFlowEditModal renders this sidebar inside a dialog,\n\t\t * where there is no sidebar to register a tab with, so that host keeps\n\t\t * a strip of its own.\n\t\t */\n\t\tembedded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { tab: 'objects' }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The run being read.\n\t\t *\n\t\t * The run's OWN record first, the history list second. The list is\n\t\t * capped at 25, so a run opened from a `?run=` deep link need not be in\n\t\t * it, and reading only the list left such a run with no status, no time\n\t\t * and no error on screen.\n\t\t *\n\t\t * @return {object|null} The run being read.\n\t\t */\n\t\trun() {\n\t\t\tconst detail = this.store.runDetail\n\t\t\tif (detail && String(detail.uuid || '') === String(this.store.inspectedRunUuid || '')) {\n\t\t\t\treturn detail\n\t\t\t}\n\n\t\t\treturn this.store.runs.find((r) => r.uuid === this.store.inspectedRunUuid) || null\n\t\t},\n\n\t\t/**\n\t\t * The objects the run is ABOUT, as opposed to the ones it changed.\n\t\t *\n\t\t * Two sources, both on the run's own record. `subjects` is what the run\n\t\t * was started on, keyed by the role that supplied it (`trigger` for a\n\t\t * run a trigger started). `placeItems` is what the run is holding at a\n\t\t * step right now, keyed by that step.\n\t\t *\n\t\t * Deduplicated by uuid, because a run's subject is normally also the\n\t\t * item sitting in a place and listing it twice would suggest two\n\t\t * objects. The subject wins the row: it is the more durable fact.\n\t\t *\n\t\t * @return {Array<object>} The rows, subjects first.\n\t\t */\n\t\tsubjectRows() {\n\t\t\tconst run = this.run\n\t\t\tif (!run) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\t// The held items first, because they are the half that carries a\n\t\t\t// TITLE. A subject record is a uuid, a register and a schema; the\n\t\t\t// item sitting in a place is the object itself. Merging the two\n\t\t\t// gives a subject row a name a reader recognises instead of a uuid.\n\t\t\tconst held = new Map()\n\t\t\tfor (const [node, items] of Object.entries(run.placeItems || {})) {\n\t\t\t\tfor (const item of items || []) {\n\t\t\t\t\tconst uuid = String(item?.json?.id || item?.id || '')\n\t\t\t\t\tif (uuid === '' || held.has(uuid)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\theld.set(uuid, {\n\t\t\t\t\t\t// Named by the step that is holding it, because that is\n\t\t\t\t\t\t// the useful half: \"which step is this stuck at\".\n\t\t\t\t\t\trole: this.t('nextcloud-vue', 'at {node}', { node }),\n\t\t\t\t\t\tuuid,\n\t\t\t\t\t\ttitle: item?.json?.title || '',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rows = []\n\t\t\tconst seen = new Set()\n\n\t\t\tfor (const [role, subject] of Object.entries(run.subjects || {})) {\n\t\t\t\tconst uuid = String(subject?.uuid || '')\n\t\t\t\tif (uuid === '' || seen.has(uuid)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tseen.add(uuid)\n\t\t\t\trows.push({\n\t\t\t\t\trole,\n\t\t\t\t\tuuid,\n\t\t\t\t\ttitle: subject?.title || held.get(uuid)?.title || '',\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor (const [uuid, row] of held.entries()) {\n\t\t\t\tif (seen.has(uuid) === false) {\n\t\t\t\t\tseen.add(uuid)\n\t\t\t\t\trows.push(row)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rows\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The strip.\n\t\t */\n\t\ttabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'objects', label: this.t('nextcloud-vue', 'Objects') },\n\t\t\t\t{ id: 'tasks', label: this.t('nextcloud-vue', 'Tasks') },\n\t\t\t\t{ id: 'logs', label: this.t('nextcloud-vue', 'Logs') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Every object the run touched, flattened, each marked with the step\n\t\t * that accounts for it.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\tobjectRows() {\n\t\t\tconst stepNodes = new Set((this.store.steps || []).map((step) => String(step.transition || '')))\n\n\t\t\treturn (this.store.runObjects || []).flatMap((group) => (group.objects || []).map((obj) => ({\n\t\t\t\t...obj,\n\t\t\t\tnode: group.node,\n\t\t\t\tunaccounted: !stepNodes.has(String(group.node || '')),\n\t\t\t})))\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when Replay is offered.\n\t\t */\n\t\tcanReplay() {\n\t\t\tif (this.store.steps.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst status = this.run?.status\n\t\t\treturn status !== undefined && FLOW_RUN_ACTIVE_STATUSES.includes(status) === false\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} uuid The task uuid.\n\t\t * @return {string} The task's own URL.\n\t\t */\n\t\ttaskUrl(uuid) {\n\t\t\treturn generateUrl(`/apps/openregister/flow-tasks/${uuid}`)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-sidebar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 12px;\n}\n\n.cn-run-sidebar__head {\n\tdisplay: flex;\n}\n\n.cn-run-sidebar__title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n}\n\n.cn-run-sidebar__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: normal;\n\tfont-size: 0.9em;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-run-sidebar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-run-sidebar__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-run-sidebar__group {\n\tmargin: 8px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-block-end: 1px solid var(--color-border);\n}\n\n.cn-run-sidebar__tab {\n\tborder: none;\n\tbackground: none;\n\tpadding: 8px 12px;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n\tborder-block-end: 2px solid transparent;\n}\n\n.cn-run-sidebar__tab--active {\n\tborder-block-end-color: var(--color-primary-element);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__list,\n.cn-run-sidebar__touched,\n.cn-run-sidebar__steps {\n\tmargin: 0;\n\tpadding-inline-start: 0;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-run-sidebar__steps {\n\tlist-style: decimal;\n\tpadding-inline-start: 20px;\n}\n\n.cn-run-sidebar__row {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tcolor: inherit;\n\tpadding: 6px 8px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-run-sidebar__touched-uuid {\n\tfont-family: monospace;\n\tfont-size: 0.9em;\n}\n\n/* A uuid is long and the sidebar is narrow, so a row wraps rather than\n pushing the panel wider. The gap keeps the parts apart on the line the\n separators already keep apart in the text. */\n.cn-run-sidebar__touched li {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 0 4px;\n}\n</style>\n"],"names":["_createVNode","_createTextVNode","_createElementVNode","_normalizeClass","_createElementBlock","_toDisplayString","_Fragment","_renderList","_openBlock","_createBlock","_resolveDynamicComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnRunDetailSidebar.vue3.js","sources":["../../../../src/components/CnFlowDetail/CnRunDetailSidebar.vue?vue&type=template&id=48e781f0&lang.js"],"sourcesContent":["<!--\n CnRunDetailSidebar — one run: what it changed, what it asked a person to do,\n and what it did.\n\n WHY THIS IS A SIDEBAR AND NOT A PANEL INSIDE THE FLOW'S\n -------------------------------------------------------\n It used to be a block nested inside the Runs tab, with a tab strip of its own.\n That put TABS INSIDE A TAB: the flow's strip (Steps, Runs) stayed on screen\n above the run's (Objects, Tasks, Logs), and the two competed for the same\n glance. Worse, the flow's tabs were still live — pressing Steps while reading\n a run swapped the panel and left the canvas painted with that run's badges,\n so the graph and the sidebar were describing different things.\n\n So opening a run REPLACES the flow's sidebar rather than nesting inside it.\n One strip at a time, and the way back is a deliberate act with its own\n control. The canvas keeps the run's replay while this is open, which is now\n consistent rather than accidental: the sidebar and the graph are showing the\n same run because there is no way to make them show anything else.\n\n WHAT IT DOES NOT OWN\n --------------------\n The run's badges on the canvas. Those are the flow detail's animation, driven\n from `store.inspectedRunUuid` and the stored log. This component reads the\n same run; it does not paint.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<section class=\"cn-run-sidebar\" data-testid=\"run-detail-sidebar\">\n\t\t<!--\n\t\t\tTHE WAY BACK, FIRST AND ALWAYS. A view that replaces another must\n\t\t\tsay what it replaced and how to undo that, or it reads as the\n\t\t\tapplication having navigated somewhere on its own.\n\t\t-->\n\t\t<div class=\"cn-run-sidebar__head\">\n\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\tdata-testid=\"run-back\"\n\t\t\t\t@click=\"store.closeRun()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ArrowLeft :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Back to the flow') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<h4 class=\"cn-run-sidebar__title\">\n\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${run?.status}`\"\n\t\t\t\tdata-testid=\"run-status\">{{ run?.status || t('nextcloud-vue', 'Run') }}</span>\n\t\t\t<time v-if=\"run?.created\" class=\"cn-run-sidebar__when\" :datetime=\"run.created\">{{ run.created }}</time>\n\t\t</h4>\n\n\t\t<p v-if=\"run?.error\" class=\"cn-run-sidebar__error\" data-testid=\"run-error\">\n\t\t\t{{ run.error }}\n\t\t</p>\n\n\t\t<!--\n\t\t\tTHE STRIP IS NEXTCLOUD'S, NOT OURS.\n\n\t\t\tThese three used to be hand-rolled buttons with a border under the\n\t\t\tactive one. They sat directly inside NcAppSidebar, one element away\n\t\t\tfrom the real thing, so they read as a panel that had been dropped\n\t\t\tinto the sidebar rather than as the sidebar's own tabs: no icons,\n\t\t\tthe wrong size, the wrong active mark, and none of the keyboard\n\t\t\tbehaviour NcAppSidebarTabs brings.\n\n\t\t\tNcAppSidebarTab registers itself with the sidebar through an\n\t\t\tinjection, so the sidebar builds the strip. That also settles which\n\t\t\ttab is open: entering run view leaves `flow-runs` registered by\n\t\t\tnothing, and NcAppSidebarTabs falls back to the first tab it has,\n\t\t\twhich is Objects.\n\n\t\t\t`embedded` (CnFlowEditModal) has no NcAppSidebar to inject that, so\n\t\t\tit keeps a strip of its own. One list of tabs feeds both, so the two\n\t\t\thosts cannot come to offer different ones.\n\t\t-->\n\t\t<div v-if=\"embedded\" class=\"cn-run-sidebar__tabs\" role=\"tablist\">\n\t\t\t<button v-for=\"entry in tabs\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-run-sidebar__tab\"\n\t\t\t\t:class=\"{ 'cn-run-sidebar__tab--active': tab === entry.id }\"\n\t\t\t\trole=\"tab\"\n\t\t\t\t:data-testid=\"`flow-run-tab-${entry.id}`\"\n\t\t\t\t:aria-selected=\"tab === entry.id ? 'true' : 'false'\"\n\t\t\t\t@click=\"tab = entry.id\">\n\t\t\t\t{{ entry.label }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- Objects: what the run is about, and what it changed. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'objects'\"\n\t\t\t:id=\"embedded ? undefined : 'run-objects'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Objects')\"\n\t\t\t:order=\"embedded ? undefined : 1\"\n\t\t\tdata-testid=\"flow-run-panel-objects\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<DatabaseOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t\tTHE OBJECTS THE RUN IS ABOUT, FIRST.\n\n\t\t\t\t`GET /flow-runs/{uuid}/objects` answers the AUDIT: objects this\n\t\t\t\trun changed. A run that waited on a locked object and then\n\t\t\t\tfailed changed nothing, so that list is empty while the run's\n\t\t\t\town error names the object by uuid — and this panel said \"this\n\t\t\t\trun changed no objects\", which reads as the run having had\n\t\t\t\tnothing to do with it.\n\n\t\t\t\tThe run record knows better. `subjects` is what it was started\n\t\t\t\ton and `placeItems` is what it is holding at a step, so both are\n\t\t\t\tlisted here, and each row says which it is. The changed list\n\t\t\t\tkeeps its own heading below: \"worked on\" and \"changed\" are\n\t\t\t\tdifferent claims and this view exists to keep them apart.\n\t\t\t-->\n\t\t\t<template v-if=\"subjectRows.length\">\n\t\t\t\t<h5 class=\"cn-run-sidebar__group\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Objects this run is about') }}\n\t\t\t\t</h5>\n\t\t\t\t<ul class=\"cn-run-sidebar__touched\" data-testid=\"flow-run-subjects\">\n\t\t\t\t\t<!--\n\t\t\t\t\t\tSEPARATED IN THE TEXT, not only by layout. Three adjacent\n\t\t\t\t\t\tspans render as one run-on string: \"triggerDakkapel\n\t\t\t\t\t\tKerkstraat 122e98a265-...\" was what the live instance\n\t\t\t\t\t\tshowed. A gap would fix the look and leave the text\n\t\t\t\t\t\titself unreadable, which is what anything reading the\n\t\t\t\t\t\tDOM gets, so the separator is a character.\n\t\t\t\t\t-->\n\t\t\t\t\t<li v-for=\"obj in subjectRows\" :key=\"`${obj.role}-${obj.uuid}`\">\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.role }}</span>\n\t\t\t\t\t\t<span v-if=\"obj.title\"> · {{ obj.title }}</span>\n\t\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.uuid }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</template>\n\n\t\t\t<h5 v-if=\"subjectRows.length\" class=\"cn-run-sidebar__group\">\n\t\t\t\t{{ t('nextcloud-vue', 'Changed by this run') }}\n\t\t\t</h5>\n\t\t\t<p v-if=\"!objectRows.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run changed no objects.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__touched\">\n\t\t\t\t<!-- Same separator as the subject rows above, and for the same\n\t\t\t\t reason: \"create\" and the uuid ran together as \"createobj-1\"\n\t\t\t\t with nothing between them. -->\n\t\t\t\t<li v-for=\"obj in objectRows\" :key=\"obj.auditUuid\">\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-action\">{{ obj.action }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__touched-uuid\"> · {{ obj.objectUuid }}</span>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ obj.node }}</span>\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA change the run's own step history cannot account for.\n\t\t\t\t\t\tIt should normally never appear; when it does, hiding it\n\t\t\t\t\t\twould be the opposite of what this view is for.\n\t\t\t\t\t-->\n\t\t\t\t\t<span v-if=\"obj.unaccounted\"\n\t\t\t\t\t\tclass=\"cn-run-sidebar__error\"\n\t\t\t\t\t\tdata-testid=\"flow-object-unaccounted\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'no matching step') }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Tasks: what the run asked a person to do. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'tasks'\"\n\t\t\t:id=\"embedded ? undefined : 'run-tasks'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Tasks')\"\n\t\t\t:order=\"embedded ? undefined : 2\"\n\t\t\tdata-testid=\"flow-run-panel-tasks\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<CheckboxMarkedOutline :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<p v-if=\"!store.runTasks.length\"\n\t\t\t\tclass=\"cn-run-sidebar__hint\"\n\t\t\t\tdata-testid=\"flow-run-tasks-empty\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run raised no tasks.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-run-sidebar__list\">\n\t\t\t\t<li v-for=\"task in store.runTasks\" :key=\"task.uuid\">\n\t\t\t\t\t<!-- The task's one stable address, the same one the\n\t\t\t\t\t notification buttons and the VTODO already resolve to. -->\n\t\t\t\t\t<a class=\"cn-run-sidebar__row\"\n\t\t\t\t\t\t:href=\"taskUrl(task.uuid)\"\n\t\t\t\t\t\tdata-testid=\"flow-task-link\">\n\t\t\t\t\t\t<span :class=\"`cn-run-sidebar__status cn-run-sidebar__status--${task.state}`\">{{ task.state }}</span>\n\t\t\t\t\t\t<span>{{ task.title || task.uuid }}</span>\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</component>\n\n\t\t<!-- Logs: what the engine did, in its own order. -->\n\t\t<component :is=\"embedded ? 'div' : 'NcAppSidebarTab'\"\n\t\t\tv-if=\"!embedded || tab === 'logs'\"\n\t\t\t:id=\"embedded ? undefined : 'run-logs'\"\n\t\t\t:name=\"embedded ? undefined : t('nextcloud-vue', 'Logs')\"\n\t\t\t:order=\"embedded ? undefined : 3\"\n\t\t\tdata-testid=\"flow-run-panel-logs\">\n\t\t\t<template v-if=\"!embedded\" #icon>\n\t\t\t\t<FormatListBulleted :size=\"20\" />\n\t\t\t</template>\n\n\t\t\t<!-- Replay BESIDE the step list, not instead of it: the list stays\n\t\t\t what it is for reading, the replay plays the same stored log\n\t\t\t through the canvas animator. Only on a FINISHED run — a run\n\t\t\t still going is watched live. -->\n\t\t\t<NcButton v-if=\"canReplay\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\tdata-testid=\"flow-replay\"\n\t\t\t\t@click=\"store.requestReplay()\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Replay :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Replay on the canvas') }}\n\t\t\t</NcButton>\n\n\t\t\t<p v-if=\"!store.steps.length\" class=\"cn-run-sidebar__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This run recorded no steps.') }}\n\t\t\t</p>\n\t\t\t<ol v-else class=\"cn-run-sidebar__steps\">\n\t\t\t\t<li v-for=\"(step, i) in store.steps\" :key=\"i\">\n\t\t\t\t\t<strong>{{ step.transition }}</strong>\n\t\t\t\t\t<span class=\"cn-run-sidebar__hint\"> · {{ step.status }}</span>\n\t\t\t\t\t<span v-if=\"step.error\" class=\"cn-run-sidebar__error\"> · {{ step.error }}</span>\n\t\t\t\t\t<!-- A send step's outcome lists, by name: who got it, who\n\t\t\t\t\t opted out, who the opt-out check could not answer for. -->\n\t\t\t\t\t<CnFlowStepOutcomes v-if=\"step.report && step.report.messaging\"\n\t\t\t\t\t\t:report=\"step.report.messaging\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</component>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppSidebarTab, NcButton } from '@nextcloud/vue'\nimport ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport Replay from 'vue-material-design-icons/Replay.vue'\nimport CnFlowStepOutcomes from '../CnFlowStepOutcomes/CnFlowStepOutcomes.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\n\nexport default {\n\tname: 'CnRunDetailSidebar',\n\n\tcomponents: {\n\t\tArrowLeft,\n\t\tCheckboxMarkedOutline,\n\t\tCnFlowStepOutcomes,\n\t\tDatabaseOutline,\n\t\tFormatListBulleted,\n\t\tNcAppSidebarTab,\n\t\tNcButton,\n\t\tReplay,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Render the panels without NcAppSidebarTab, for a host that is not\n\t\t * NcAppSidebar. CnFlowEditModal renders this sidebar inside a dialog,\n\t\t * where there is no sidebar to register a tab with, so that host keeps\n\t\t * a strip of its own.\n\t\t */\n\t\tembedded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { tab: 'objects' }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The run being read.\n\t\t *\n\t\t * The run's OWN record first, the history list second. The list is\n\t\t * capped at 25, so a run opened from a `?run=` deep link need not be in\n\t\t * it, and reading only the list left such a run with no status, no time\n\t\t * and no error on screen.\n\t\t *\n\t\t * @return {object|null} The run being read.\n\t\t */\n\t\trun() {\n\t\t\tconst detail = this.store.runDetail\n\t\t\tif (detail && String(detail.uuid || '') === String(this.store.inspectedRunUuid || '')) {\n\t\t\t\treturn detail\n\t\t\t}\n\n\t\t\treturn this.store.runs.find((r) => r.uuid === this.store.inspectedRunUuid) || null\n\t\t},\n\n\t\t/**\n\t\t * The objects the run is ABOUT, as opposed to the ones it changed.\n\t\t *\n\t\t * Two sources, both on the run's own record. `subjects` is what the run\n\t\t * was started on, keyed by the role that supplied it (`trigger` for a\n\t\t * run a trigger started). `placeItems` is what the run is holding at a\n\t\t * step right now, keyed by that step.\n\t\t *\n\t\t * Deduplicated by uuid, because a run's subject is normally also the\n\t\t * item sitting in a place and listing it twice would suggest two\n\t\t * objects. The subject wins the row: it is the more durable fact.\n\t\t *\n\t\t * @return {Array<object>} The rows, subjects first.\n\t\t */\n\t\tsubjectRows() {\n\t\t\tconst run = this.run\n\t\t\tif (!run) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\t// The held items first, because they are the half that carries a\n\t\t\t// TITLE. A subject record is a uuid, a register and a schema; the\n\t\t\t// item sitting in a place is the object itself. Merging the two\n\t\t\t// gives a subject row a name a reader recognises instead of a uuid.\n\t\t\tconst held = new Map()\n\t\t\tfor (const [node, items] of Object.entries(run.placeItems || {})) {\n\t\t\t\tfor (const item of items || []) {\n\t\t\t\t\tconst uuid = String(item?.json?.id || item?.id || '')\n\t\t\t\t\tif (uuid === '' || held.has(uuid)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\theld.set(uuid, {\n\t\t\t\t\t\t// Named by the step that is holding it, because that is\n\t\t\t\t\t\t// the useful half: \"which step is this stuck at\".\n\t\t\t\t\t\trole: this.t('nextcloud-vue', 'at {node}', { node }),\n\t\t\t\t\t\tuuid,\n\t\t\t\t\t\ttitle: item?.json?.title || '',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rows = []\n\t\t\tconst seen = new Set()\n\n\t\t\tfor (const [role, subject] of Object.entries(run.subjects || {})) {\n\t\t\t\tconst uuid = String(subject?.uuid || '')\n\t\t\t\tif (uuid === '' || seen.has(uuid)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tseen.add(uuid)\n\t\t\t\trows.push({\n\t\t\t\t\trole,\n\t\t\t\t\tuuid,\n\t\t\t\t\ttitle: subject?.title || held.get(uuid)?.title || '',\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor (const [uuid, row] of held.entries()) {\n\t\t\t\tif (seen.has(uuid) === false) {\n\t\t\t\t\tseen.add(uuid)\n\t\t\t\t\trows.push(row)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rows\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The strip.\n\t\t */\n\t\ttabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'objects', label: this.t('nextcloud-vue', 'Objects') },\n\t\t\t\t{ id: 'tasks', label: this.t('nextcloud-vue', 'Tasks') },\n\t\t\t\t{ id: 'logs', label: this.t('nextcloud-vue', 'Logs') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Every object the run touched, flattened, each marked with the step\n\t\t * that accounts for it.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\tobjectRows() {\n\t\t\tconst stepNodes = new Set((this.store.steps || []).map((step) => String(step.transition || '')))\n\n\t\t\treturn (this.store.runObjects || []).flatMap((group) => (group.objects || []).map((obj) => ({\n\t\t\t\t...obj,\n\t\t\t\tnode: group.node,\n\t\t\t\tunaccounted: !stepNodes.has(String(group.node || '')),\n\t\t\t})))\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when Replay is offered.\n\t\t */\n\t\tcanReplay() {\n\t\t\tif (this.store.steps.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst status = this.run?.status\n\t\t\treturn status !== undefined && FLOW_RUN_ACTIVE_STATUSES.includes(status) === false\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} uuid The task uuid.\n\t\t * @return {string} The task's own URL.\n\t\t */\n\t\ttaskUrl(uuid) {\n\t\t\treturn generateUrl(`/apps/openregister/flow-tasks/${uuid}`)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-sidebar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 12px;\n}\n\n.cn-run-sidebar__head {\n\tdisplay: flex;\n}\n\n.cn-run-sidebar__title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n}\n\n.cn-run-sidebar__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: normal;\n\tfont-size: 0.9em;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-run-sidebar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-run-sidebar__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-run-sidebar__group {\n\tmargin: 8px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-block-end: 1px solid var(--color-border);\n}\n\n.cn-run-sidebar__tab {\n\tborder: none;\n\tbackground: none;\n\tpadding: 8px 12px;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n\tborder-block-end: 2px solid transparent;\n}\n\n.cn-run-sidebar__tab--active {\n\tborder-block-end-color: var(--color-primary-element);\n\tfont-weight: bold;\n}\n\n.cn-run-sidebar__list,\n.cn-run-sidebar__touched,\n.cn-run-sidebar__steps {\n\tmargin: 0;\n\tpadding-inline-start: 0;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-run-sidebar__steps {\n\tlist-style: decimal;\n\tpadding-inline-start: 20px;\n}\n\n.cn-run-sidebar__row {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tcolor: inherit;\n\tpadding: 6px 8px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-run-sidebar__touched-uuid {\n\tfont-family: monospace;\n\tfont-size: 0.9em;\n}\n\n/* A uuid is long and the sidebar is narrow, so a row wraps rather than\n pushing the panel wider. The gap keeps the parts apart on the line the\n separators already keep apart in the text. */\n.cn-run-sidebar__touched li {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 0 4px;\n}\n</style>\n"],"names":["_createVNode","_createTextVNode","_createElementVNode","_normalizeClass","_createElementBlock","_toDisplayString","_Fragment","_renderList","_openBlock","_createBlock","_resolveDynamicComponent"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import script from './CnFlowStepOutcomes.vue2.js';
|
|
2
|
+
import { render } from './CnFlowStepOutcomes.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__scopeId = "data-v-5ac5ad4f";
|
|
6
|
+
script.__file = "src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue";
|
|
7
|
+
const __sfc_main = script;
|
|
8
|
+
|
|
9
|
+
export { __sfc_main as default };
|
|
10
|
+
//# sourceMappingURL=CnFlowStepOutcomes.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnFlowStepOutcomes.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The buckets a messaging report carries, in reading order: who got it first,
|
|
5
|
+
* then each reason someone did not. `tone` marks the row for styling only.
|
|
6
|
+
*/
|
|
7
|
+
const KNOWN_BUCKETS = [
|
|
8
|
+
{ key: 'delivered', tone: 'success' },
|
|
9
|
+
{ key: 'optedOut', tone: 'neutral' },
|
|
10
|
+
{ key: 'authorityUnavailable', tone: 'warning' },
|
|
11
|
+
{ key: 'skippedByPreference', tone: 'neutral' },
|
|
12
|
+
{ key: 'skippedByKillSwitch', tone: 'neutral' },
|
|
13
|
+
{ key: 'rateLimited', tone: 'warning' },
|
|
14
|
+
{ key: 'refusedRecipients', tone: 'warning' },
|
|
15
|
+
{ key: 'unknownRecipients', tone: 'warning' },
|
|
16
|
+
{ key: 'failed', tone: 'error' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Report keys that are facts about the send, not outcome buckets. */
|
|
20
|
+
const NOT_BUCKETS = ['channel', 'actor', 'recipients', 'truncated'];
|
|
21
|
+
|
|
22
|
+
var script = {
|
|
23
|
+
name: 'CnFlowStepOutcomes',
|
|
24
|
+
|
|
25
|
+
props: {
|
|
26
|
+
/**
|
|
27
|
+
* A step's messaging report: the run-log entry's `report.messaging`.
|
|
28
|
+
* Each outcome bucket is `{count, sample}`; a `refusedRecipients`
|
|
29
|
+
* sample entry is `{recipient, reason}`. `null` renders nothing.
|
|
30
|
+
*/
|
|
31
|
+
report: {
|
|
32
|
+
type: Object,
|
|
33
|
+
default: null,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Names to use instead of the built-in ones, keyed by bucket
|
|
38
|
+
* (`{ optedOut: 'Unsubscribed' }`). Pass already-translated text.
|
|
39
|
+
*/
|
|
40
|
+
labels: {
|
|
41
|
+
type: Object,
|
|
42
|
+
default: () => ({}),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
computed: {
|
|
47
|
+
/**
|
|
48
|
+
* The non-empty buckets, known ones first in reading order, then any
|
|
49
|
+
* bucket the server added that this component does not know.
|
|
50
|
+
*
|
|
51
|
+
* @return {Array<object>} One row per bucket: key, label, count, sample, tone.
|
|
52
|
+
*/
|
|
53
|
+
rows() {
|
|
54
|
+
const report = this.report;
|
|
55
|
+
if (!report || typeof report !== 'object') {
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const known = KNOWN_BUCKETS.map((bucket) => bucket.key);
|
|
60
|
+
const extra = Object.keys(report).filter((key) => !known.includes(key) && !NOT_BUCKETS.includes(key));
|
|
61
|
+
const ordered = [
|
|
62
|
+
...KNOWN_BUCKETS,
|
|
63
|
+
...extra.map((key) => ({ key, tone: 'neutral' })),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return ordered
|
|
67
|
+
.map(({ key, tone }) => ({ key, tone, bucket: report[key] }))
|
|
68
|
+
.filter(({ bucket }) => bucket && typeof bucket === 'object' && Number(bucket.count) > 0)
|
|
69
|
+
.map(({ key, tone, bucket }) => ({
|
|
70
|
+
key,
|
|
71
|
+
tone,
|
|
72
|
+
label: this.labelFor(key),
|
|
73
|
+
count: Number(bucket.count),
|
|
74
|
+
sample: (Array.isArray(bucket.sample) ? bucket.sample : []).map((entry) => this.describeEntry(entry)),
|
|
75
|
+
}))
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
methods: {
|
|
80
|
+
t: translate,
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The readable name of a bucket: the host's, then the built-in one,
|
|
84
|
+
* then the key itself (`heldForReview` reads "Held for review").
|
|
85
|
+
*
|
|
86
|
+
* @param {string} key The bucket key.
|
|
87
|
+
* @return {string} The name.
|
|
88
|
+
*/
|
|
89
|
+
labelFor(key) {
|
|
90
|
+
if (this.labels && this.labels[key]) {
|
|
91
|
+
return String(this.labels[key])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const builtIn = {
|
|
95
|
+
delivered: translate('nextcloud-vue', 'Delivered'),
|
|
96
|
+
optedOut: translate('nextcloud-vue', 'Opted out'),
|
|
97
|
+
authorityUnavailable: translate('nextcloud-vue', 'Not sent, the opt-out check did not answer'),
|
|
98
|
+
skippedByPreference: translate('nextcloud-vue', 'Skipped by their notification settings'),
|
|
99
|
+
skippedByKillSwitch: translate('nextcloud-vue', 'Skipped, sending is switched off'),
|
|
100
|
+
rateLimited: translate('nextcloud-vue', 'Held back by the send limit'),
|
|
101
|
+
refusedRecipients: translate('nextcloud-vue', 'Refused by the step’s address rule'),
|
|
102
|
+
unknownRecipients: translate('nextcloud-vue', 'Not a known user or group'),
|
|
103
|
+
failed: translate('nextcloud-vue', 'Failed'),
|
|
104
|
+
};
|
|
105
|
+
if (builtIn[key]) {
|
|
106
|
+
return builtIn[key]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const words = String(key).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[-_]+/g, ' ').trim().toLowerCase();
|
|
110
|
+
|
|
111
|
+
return words.charAt(0).toUpperCase() + words.slice(1)
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* One sample entry as text. A refused address carries its reason.
|
|
116
|
+
*
|
|
117
|
+
* @param {string|object} entry The sample entry.
|
|
118
|
+
* @return {string} The text.
|
|
119
|
+
*/
|
|
120
|
+
describeEntry(entry) {
|
|
121
|
+
if (entry && typeof entry === 'object') {
|
|
122
|
+
const who = String(entry.recipient ?? entry.id ?? '');
|
|
123
|
+
const reason = this.reasonFor(entry.reason);
|
|
124
|
+
|
|
125
|
+
return reason ? `${who} (${reason})` : who
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return String(entry)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Why the step's address rule refused an address, in words.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} [code] The refusal code.
|
|
135
|
+
* @return {string} The reason, or the code when it is not known.
|
|
136
|
+
*/
|
|
137
|
+
reasonFor(code) {
|
|
138
|
+
const reasons = {
|
|
139
|
+
'external-recipients-off': translate('nextcloud-vue', 'external recipients are off'),
|
|
140
|
+
'not-on-item': translate('nextcloud-vue', 'not on the item'),
|
|
141
|
+
'invalid-address': translate('nextcloud-vue', 'not a valid address'),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return code ? (reasons[code] || String(code)) : ''
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export { script as default };
|
|
150
|
+
//# sourceMappingURL=CnFlowStepOutcomes.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnFlowStepOutcomes.vue2.js","sources":["../../../../src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue"],"sourcesContent":["<!--\n CnFlowStepOutcomes: who a send step reached, and who it did not, by name.\n\n A messaging step (send email, send notification, post to Talk) writes its\n per-recipient outcome onto its run-log entry as `report.messaging`: one\n bucket per outcome, each `{count, sample}`. Nothing rendered it, so the run\n log could not show that a person was skipped because they opted out, or\n because the opt-out check did not answer.\n\n Only buckets with someone in them are listed. A bucket this component does\n not know is still shown, under its own key made readable, so a new outcome\n on the server is never hidden by an older library.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"rows.length\" class=\"cn-flow-step-outcomes\" data-testid=\"flow-step-outcomes\">\n\t\t<dl class=\"cn-flow-step-outcomes__list\" :aria-label=\"t('nextcloud-vue', 'Who this step reached')\">\n\t\t\t<div v-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-flow-step-outcomes__row\"\n\t\t\t\t:class=\"`cn-flow-step-outcomes__row--${row.tone}`\"\n\t\t\t\t:data-testid=\"`flow-step-outcome-${row.key}`\">\n\t\t\t\t<dt class=\"cn-flow-step-outcomes__name\">\n\t\t\t\t\t{{ row.label }}\n\t\t\t\t</dt>\n\t\t\t\t<dd class=\"cn-flow-step-outcomes__value\">\n\t\t\t\t\t<span class=\"cn-flow-step-outcomes__count\">{{ row.count }}</span>\n\t\t\t\t\t<span v-if=\"row.sample.length\" class=\"cn-flow-step-outcomes__sample\"> · {{ row.sample.join(', ') }}</span>\n\t\t\t\t</dd>\n\t\t\t</div>\n\t\t</dl>\n\t\t<p v-if=\"report && report.truncated\"\n\t\t\tclass=\"cn-flow-step-outcomes__truncated\"\n\t\t\tdata-testid=\"flow-step-outcomes-truncated\">\n\t\t\t{{ t('nextcloud-vue', 'Each list shows its first entries only.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * The buckets a messaging report carries, in reading order: who got it first,\n * then each reason someone did not. `tone` marks the row for styling only.\n */\nconst KNOWN_BUCKETS = [\n\t{ key: 'delivered', tone: 'success' },\n\t{ key: 'optedOut', tone: 'neutral' },\n\t{ key: 'authorityUnavailable', tone: 'warning' },\n\t{ key: 'skippedByPreference', tone: 'neutral' },\n\t{ key: 'skippedByKillSwitch', tone: 'neutral' },\n\t{ key: 'rateLimited', tone: 'warning' },\n\t{ key: 'refusedRecipients', tone: 'warning' },\n\t{ key: 'unknownRecipients', tone: 'warning' },\n\t{ key: 'failed', tone: 'error' },\n]\n\n/** Report keys that are facts about the send, not outcome buckets. */\nconst NOT_BUCKETS = ['channel', 'actor', 'recipients', 'truncated']\n\nexport default {\n\tname: 'CnFlowStepOutcomes',\n\n\tprops: {\n\t\t/**\n\t\t * A step's messaging report: the run-log entry's `report.messaging`.\n\t\t * Each outcome bucket is `{count, sample}`; a `refusedRecipients`\n\t\t * sample entry is `{recipient, reason}`. `null` renders nothing.\n\t\t */\n\t\treport: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Names to use instead of the built-in ones, keyed by bucket\n\t\t * (`{ optedOut: 'Unsubscribed' }`). Pass already-translated text.\n\t\t */\n\t\tlabels: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The non-empty buckets, known ones first in reading order, then any\n\t\t * bucket the server added that this component does not know.\n\t\t *\n\t\t * @return {Array<object>} One row per bucket: key, label, count, sample, tone.\n\t\t */\n\t\trows() {\n\t\t\tconst report = this.report\n\t\t\tif (!report || typeof report !== 'object') {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst known = KNOWN_BUCKETS.map((bucket) => bucket.key)\n\t\t\tconst extra = Object.keys(report).filter((key) => !known.includes(key) && !NOT_BUCKETS.includes(key))\n\t\t\tconst ordered = [\n\t\t\t\t...KNOWN_BUCKETS,\n\t\t\t\t...extra.map((key) => ({ key, tone: 'neutral' })),\n\t\t\t]\n\n\t\t\treturn ordered\n\t\t\t\t.map(({ key, tone }) => ({ key, tone, bucket: report[key] }))\n\t\t\t\t.filter(({ bucket }) => bucket && typeof bucket === 'object' && Number(bucket.count) > 0)\n\t\t\t\t.map(({ key, tone, bucket }) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\ttone,\n\t\t\t\t\tlabel: this.labelFor(key),\n\t\t\t\t\tcount: Number(bucket.count),\n\t\t\t\t\tsample: (Array.isArray(bucket.sample) ? bucket.sample : []).map((entry) => this.describeEntry(entry)),\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The readable name of a bucket: the host's, then the built-in one,\n\t\t * then the key itself (`heldForReview` reads \"Held for review\").\n\t\t *\n\t\t * @param {string} key The bucket key.\n\t\t * @return {string} The name.\n\t\t */\n\t\tlabelFor(key) {\n\t\t\tif (this.labels && this.labels[key]) {\n\t\t\t\treturn String(this.labels[key])\n\t\t\t}\n\n\t\t\tconst builtIn = {\n\t\t\t\tdelivered: t('nextcloud-vue', 'Delivered'),\n\t\t\t\toptedOut: t('nextcloud-vue', 'Opted out'),\n\t\t\t\tauthorityUnavailable: t('nextcloud-vue', 'Not sent, the opt-out check did not answer'),\n\t\t\t\tskippedByPreference: t('nextcloud-vue', 'Skipped by their notification settings'),\n\t\t\t\tskippedByKillSwitch: t('nextcloud-vue', 'Skipped, sending is switched off'),\n\t\t\t\trateLimited: t('nextcloud-vue', 'Held back by the send limit'),\n\t\t\t\trefusedRecipients: t('nextcloud-vue', 'Refused by the step’s address rule'),\n\t\t\t\tunknownRecipients: t('nextcloud-vue', 'Not a known user or group'),\n\t\t\t\tfailed: t('nextcloud-vue', 'Failed'),\n\t\t\t}\n\t\t\tif (builtIn[key]) {\n\t\t\t\treturn builtIn[key]\n\t\t\t}\n\n\t\t\tconst words = String(key).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[-_]+/g, ' ').trim().toLowerCase()\n\n\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * One sample entry as text. A refused address carries its reason.\n\t\t *\n\t\t * @param {string|object} entry The sample entry.\n\t\t * @return {string} The text.\n\t\t */\n\t\tdescribeEntry(entry) {\n\t\t\tif (entry && typeof entry === 'object') {\n\t\t\t\tconst who = String(entry.recipient ?? entry.id ?? '')\n\t\t\t\tconst reason = this.reasonFor(entry.reason)\n\n\t\t\t\treturn reason ? `${who} (${reason})` : who\n\t\t\t}\n\n\t\t\treturn String(entry)\n\t\t},\n\n\t\t/**\n\t\t * Why the step's address rule refused an address, in words.\n\t\t *\n\t\t * @param {string} [code] The refusal code.\n\t\t * @return {string} The reason, or the code when it is not known.\n\t\t */\n\t\treasonFor(code) {\n\t\t\tconst reasons = {\n\t\t\t\t'external-recipients-off': t('nextcloud-vue', 'external recipients are off'),\n\t\t\t\t'not-on-item': t('nextcloud-vue', 'not on the item'),\n\t\t\t\t'invalid-address': t('nextcloud-vue', 'not a valid address'),\n\t\t\t}\n\n\t\t\treturn code ? (reasons[code] || String(code)) : ''\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-step-outcomes {\n\tmargin: 4px 0 0;\n\tfont-size: 0.9em;\n}\n\n.cn-flow-step-outcomes__list {\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Stacked, name over value: the run sidebar is narrow, and a side-by-side\n row wrapped mid-list. The host's dt/dd margins and alignment are reset. */\n.cn-flow-step-outcomes__row {\n\tdisplay: block;\n\tmargin-block: 4px;\n\tpadding-block: 2px;\n\tpadding-inline-start: 8px;\n\tborder-inline-start: 3px solid var(--color-border);\n\ttext-align: start;\n}\n\n.cn-flow-step-outcomes__row--success {\n\tborder-inline-start-color: var(--color-success);\n}\n\n.cn-flow-step-outcomes__row--warning {\n\tborder-inline-start-color: var(--color-warning);\n}\n\n.cn-flow-step-outcomes__row--error {\n\tborder-inline-start-color: var(--color-error);\n}\n\n.cn-flow-step-outcomes__name {\n\tdisplay: block;\n\twhite-space: normal;\n\toverflow-wrap: anywhere;\n\tmargin: 0;\n\tpadding: 0;\n\tfont-weight: bold;\n\ttext-align: start;\n}\n\n.cn-flow-step-outcomes__value {\n\tdisplay: block;\n\twhite-space: normal;\n\tmargin: 0;\n\tpadding: 0;\n\ttext-align: start;\n\tcolor: var(--color-main-text);\n\toverflow-wrap: anywhere;\n}\n\n.cn-flow-step-outcomes__truncated {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { openBlock, createElementBlock, createElementVNode, Fragment, renderList, normalizeClass, toDisplayString, createCommentVNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
const _hoisted_1 = {
|
|
4
|
+
key: 0,
|
|
5
|
+
class: "cn-flow-step-outcomes",
|
|
6
|
+
"data-testid": "flow-step-outcomes"
|
|
7
|
+
};
|
|
8
|
+
const _hoisted_2 = ["aria-label"];
|
|
9
|
+
const _hoisted_3 = ["data-testid"];
|
|
10
|
+
const _hoisted_4 = { class: "cn-flow-step-outcomes__name" };
|
|
11
|
+
const _hoisted_5 = { class: "cn-flow-step-outcomes__value" };
|
|
12
|
+
const _hoisted_6 = { class: "cn-flow-step-outcomes__count" };
|
|
13
|
+
const _hoisted_7 = {
|
|
14
|
+
key: 0,
|
|
15
|
+
class: "cn-flow-step-outcomes__sample"
|
|
16
|
+
};
|
|
17
|
+
const _hoisted_8 = {
|
|
18
|
+
key: 0,
|
|
19
|
+
class: "cn-flow-step-outcomes__truncated",
|
|
20
|
+
"data-testid": "flow-step-outcomes-truncated"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
24
|
+
return ($options.rows.length)
|
|
25
|
+
? (openBlock(), createElementBlock("div", _hoisted_1, [
|
|
26
|
+
createElementVNode("dl", {
|
|
27
|
+
class: "cn-flow-step-outcomes__list",
|
|
28
|
+
"aria-label": $options.t('nextcloud-vue', 'Who this step reached')
|
|
29
|
+
}, [
|
|
30
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.rows, (row) => {
|
|
31
|
+
return (openBlock(), createElementBlock("div", {
|
|
32
|
+
key: row.key,
|
|
33
|
+
class: normalizeClass(["cn-flow-step-outcomes__row", `cn-flow-step-outcomes__row--${row.tone}`]),
|
|
34
|
+
"data-testid": `flow-step-outcome-${row.key}`
|
|
35
|
+
}, [
|
|
36
|
+
createElementVNode("dt", _hoisted_4, toDisplayString(row.label), 1 /* TEXT */),
|
|
37
|
+
createElementVNode("dd", _hoisted_5, [
|
|
38
|
+
createElementVNode("span", _hoisted_6, toDisplayString(row.count), 1 /* TEXT */),
|
|
39
|
+
(row.sample.length)
|
|
40
|
+
? (openBlock(), createElementBlock("span", _hoisted_7, " · " + toDisplayString(row.sample.join(', ')), 1 /* TEXT */))
|
|
41
|
+
: createCommentVNode("v-if", true)
|
|
42
|
+
])
|
|
43
|
+
], 10 /* CLASS, PROPS */, _hoisted_3))
|
|
44
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
45
|
+
], 8 /* PROPS */, _hoisted_2),
|
|
46
|
+
($props.report && $props.report.truncated)
|
|
47
|
+
? (openBlock(), createElementBlock("p", _hoisted_8, toDisplayString($options.t('nextcloud-vue', 'Each list shows its first entries only.')), 1 /* TEXT */))
|
|
48
|
+
: createCommentVNode("v-if", true)
|
|
49
|
+
]))
|
|
50
|
+
: createCommentVNode("v-if", true)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { render };
|
|
54
|
+
//# sourceMappingURL=CnFlowStepOutcomes.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnFlowStepOutcomes.vue3.js","sources":["../../../../src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue?vue&type=template&id=5ac5ad4f&lang.js"],"sourcesContent":["<!--\n CnFlowStepOutcomes: who a send step reached, and who it did not, by name.\n\n A messaging step (send email, send notification, post to Talk) writes its\n per-recipient outcome onto its run-log entry as `report.messaging`: one\n bucket per outcome, each `{count, sample}`. Nothing rendered it, so the run\n log could not show that a person was skipped because they opted out, or\n because the opt-out check did not answer.\n\n Only buckets with someone in them are listed. A bucket this component does\n not know is still shown, under its own key made readable, so a new outcome\n on the server is never hidden by an older library.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"rows.length\" class=\"cn-flow-step-outcomes\" data-testid=\"flow-step-outcomes\">\n\t\t<dl class=\"cn-flow-step-outcomes__list\" :aria-label=\"t('nextcloud-vue', 'Who this step reached')\">\n\t\t\t<div v-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-flow-step-outcomes__row\"\n\t\t\t\t:class=\"`cn-flow-step-outcomes__row--${row.tone}`\"\n\t\t\t\t:data-testid=\"`flow-step-outcome-${row.key}`\">\n\t\t\t\t<dt class=\"cn-flow-step-outcomes__name\">\n\t\t\t\t\t{{ row.label }}\n\t\t\t\t</dt>\n\t\t\t\t<dd class=\"cn-flow-step-outcomes__value\">\n\t\t\t\t\t<span class=\"cn-flow-step-outcomes__count\">{{ row.count }}</span>\n\t\t\t\t\t<span v-if=\"row.sample.length\" class=\"cn-flow-step-outcomes__sample\"> · {{ row.sample.join(', ') }}</span>\n\t\t\t\t</dd>\n\t\t\t</div>\n\t\t</dl>\n\t\t<p v-if=\"report && report.truncated\"\n\t\t\tclass=\"cn-flow-step-outcomes__truncated\"\n\t\t\tdata-testid=\"flow-step-outcomes-truncated\">\n\t\t\t{{ t('nextcloud-vue', 'Each list shows its first entries only.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * The buckets a messaging report carries, in reading order: who got it first,\n * then each reason someone did not. `tone` marks the row for styling only.\n */\nconst KNOWN_BUCKETS = [\n\t{ key: 'delivered', tone: 'success' },\n\t{ key: 'optedOut', tone: 'neutral' },\n\t{ key: 'authorityUnavailable', tone: 'warning' },\n\t{ key: 'skippedByPreference', tone: 'neutral' },\n\t{ key: 'skippedByKillSwitch', tone: 'neutral' },\n\t{ key: 'rateLimited', tone: 'warning' },\n\t{ key: 'refusedRecipients', tone: 'warning' },\n\t{ key: 'unknownRecipients', tone: 'warning' },\n\t{ key: 'failed', tone: 'error' },\n]\n\n/** Report keys that are facts about the send, not outcome buckets. */\nconst NOT_BUCKETS = ['channel', 'actor', 'recipients', 'truncated']\n\nexport default {\n\tname: 'CnFlowStepOutcomes',\n\n\tprops: {\n\t\t/**\n\t\t * A step's messaging report: the run-log entry's `report.messaging`.\n\t\t * Each outcome bucket is `{count, sample}`; a `refusedRecipients`\n\t\t * sample entry is `{recipient, reason}`. `null` renders nothing.\n\t\t */\n\t\treport: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Names to use instead of the built-in ones, keyed by bucket\n\t\t * (`{ optedOut: 'Unsubscribed' }`). Pass already-translated text.\n\t\t */\n\t\tlabels: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The non-empty buckets, known ones first in reading order, then any\n\t\t * bucket the server added that this component does not know.\n\t\t *\n\t\t * @return {Array<object>} One row per bucket: key, label, count, sample, tone.\n\t\t */\n\t\trows() {\n\t\t\tconst report = this.report\n\t\t\tif (!report || typeof report !== 'object') {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst known = KNOWN_BUCKETS.map((bucket) => bucket.key)\n\t\t\tconst extra = Object.keys(report).filter((key) => !known.includes(key) && !NOT_BUCKETS.includes(key))\n\t\t\tconst ordered = [\n\t\t\t\t...KNOWN_BUCKETS,\n\t\t\t\t...extra.map((key) => ({ key, tone: 'neutral' })),\n\t\t\t]\n\n\t\t\treturn ordered\n\t\t\t\t.map(({ key, tone }) => ({ key, tone, bucket: report[key] }))\n\t\t\t\t.filter(({ bucket }) => bucket && typeof bucket === 'object' && Number(bucket.count) > 0)\n\t\t\t\t.map(({ key, tone, bucket }) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\ttone,\n\t\t\t\t\tlabel: this.labelFor(key),\n\t\t\t\t\tcount: Number(bucket.count),\n\t\t\t\t\tsample: (Array.isArray(bucket.sample) ? bucket.sample : []).map((entry) => this.describeEntry(entry)),\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The readable name of a bucket: the host's, then the built-in one,\n\t\t * then the key itself (`heldForReview` reads \"Held for review\").\n\t\t *\n\t\t * @param {string} key The bucket key.\n\t\t * @return {string} The name.\n\t\t */\n\t\tlabelFor(key) {\n\t\t\tif (this.labels && this.labels[key]) {\n\t\t\t\treturn String(this.labels[key])\n\t\t\t}\n\n\t\t\tconst builtIn = {\n\t\t\t\tdelivered: t('nextcloud-vue', 'Delivered'),\n\t\t\t\toptedOut: t('nextcloud-vue', 'Opted out'),\n\t\t\t\tauthorityUnavailable: t('nextcloud-vue', 'Not sent, the opt-out check did not answer'),\n\t\t\t\tskippedByPreference: t('nextcloud-vue', 'Skipped by their notification settings'),\n\t\t\t\tskippedByKillSwitch: t('nextcloud-vue', 'Skipped, sending is switched off'),\n\t\t\t\trateLimited: t('nextcloud-vue', 'Held back by the send limit'),\n\t\t\t\trefusedRecipients: t('nextcloud-vue', 'Refused by the step’s address rule'),\n\t\t\t\tunknownRecipients: t('nextcloud-vue', 'Not a known user or group'),\n\t\t\t\tfailed: t('nextcloud-vue', 'Failed'),\n\t\t\t}\n\t\t\tif (builtIn[key]) {\n\t\t\t\treturn builtIn[key]\n\t\t\t}\n\n\t\t\tconst words = String(key).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[-_]+/g, ' ').trim().toLowerCase()\n\n\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * One sample entry as text. A refused address carries its reason.\n\t\t *\n\t\t * @param {string|object} entry The sample entry.\n\t\t * @return {string} The text.\n\t\t */\n\t\tdescribeEntry(entry) {\n\t\t\tif (entry && typeof entry === 'object') {\n\t\t\t\tconst who = String(entry.recipient ?? entry.id ?? '')\n\t\t\t\tconst reason = this.reasonFor(entry.reason)\n\n\t\t\t\treturn reason ? `${who} (${reason})` : who\n\t\t\t}\n\n\t\t\treturn String(entry)\n\t\t},\n\n\t\t/**\n\t\t * Why the step's address rule refused an address, in words.\n\t\t *\n\t\t * @param {string} [code] The refusal code.\n\t\t * @return {string} The reason, or the code when it is not known.\n\t\t */\n\t\treasonFor(code) {\n\t\t\tconst reasons = {\n\t\t\t\t'external-recipients-off': t('nextcloud-vue', 'external recipients are off'),\n\t\t\t\t'not-on-item': t('nextcloud-vue', 'not on the item'),\n\t\t\t\t'invalid-address': t('nextcloud-vue', 'not a valid address'),\n\t\t\t}\n\n\t\t\treturn code ? (reasons[code] || String(code)) : ''\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-step-outcomes {\n\tmargin: 4px 0 0;\n\tfont-size: 0.9em;\n}\n\n.cn-flow-step-outcomes__list {\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Stacked, name over value: the run sidebar is narrow, and a side-by-side\n row wrapped mid-list. The host's dt/dd margins and alignment are reset. */\n.cn-flow-step-outcomes__row {\n\tdisplay: block;\n\tmargin-block: 4px;\n\tpadding-block: 2px;\n\tpadding-inline-start: 8px;\n\tborder-inline-start: 3px solid var(--color-border);\n\ttext-align: start;\n}\n\n.cn-flow-step-outcomes__row--success {\n\tborder-inline-start-color: var(--color-success);\n}\n\n.cn-flow-step-outcomes__row--warning {\n\tborder-inline-start-color: var(--color-warning);\n}\n\n.cn-flow-step-outcomes__row--error {\n\tborder-inline-start-color: var(--color-error);\n}\n\n.cn-flow-step-outcomes__name {\n\tdisplay: block;\n\twhite-space: normal;\n\toverflow-wrap: anywhere;\n\tmargin: 0;\n\tpadding: 0;\n\tfont-weight: bold;\n\ttext-align: start;\n}\n\n.cn-flow-step-outcomes__value {\n\tdisplay: block;\n\twhite-space: normal;\n\tmargin: 0;\n\tpadding: 0;\n\ttext-align: start;\n\tcolor: var(--color-main-text);\n\toverflow-wrap: anywhere;\n}\n\n.cn-flow-step-outcomes__truncated {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_normalizeClass"],"mappings":";;;;AAiCyB,EAAA,KAAK,EAAC,uBAAM;AAAA,EAAA,aAAA,EAAA,oBAAA;;;;;;;;;;;;;;;;;;AAAzB,EAAA,OAAA,CAAA,QAAA,CAAA,IAAI,OAAO;AAArB,OAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;QACCC;AAAG,UAAA,KAAK,EAAC,6BAA6B;AAAC,UAAA,YAAA,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,uBAAA,CAAA;;4BACvCD,8CAAmB,QAAI,CAAA,IAAA,EAAA,CAAX,GAAG,KAAA;iCAAfA;cACC,GAAI,EAAE,GAAG,CAAC,GAAG;AACb,cAAA,KAAC,EAAAE,cAAA,CAAA,CAAA,4BAAA,EACC,CAAA,4BAAA,EAAA,GAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -4,6 +4,7 @@ import { generateUrl } from '@nextcloud/router';
|
|
|
4
4
|
import { NcButton, NcCheckboxRadioSwitch, NcDialog, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue';
|
|
5
5
|
import __sfc_main from '../components/CnCronField/CnCronField.vue.js';
|
|
6
6
|
import { useFlowStore } from '../composables/useFlowStore.js';
|
|
7
|
+
import { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js';
|
|
7
8
|
|
|
8
9
|
/** The verbs an HTTP-shaped `method` option can take. */
|
|
9
10
|
/**
|
|
@@ -121,7 +122,7 @@ var script = {
|
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
124
|
* The node's per-field declarations from the catalogue's `configForm`
|
|
124
|
-
* ({key, label, type, help, required, optionsFrom}), keyed by config
|
|
125
|
+
* ({key, label, type, help, required, optionsFrom, options}), keyed by config
|
|
125
126
|
* key. `configKeys` is the degraded form. Either way the node's OWNER
|
|
126
127
|
* declares the vocabulary — this dialog never invents fields.
|
|
127
128
|
*
|
|
@@ -232,7 +233,9 @@ var script = {
|
|
|
232
233
|
created() {
|
|
233
234
|
// Select fields need their options; everything else is local.
|
|
234
235
|
for (const [key, spec] of Object.entries(this.fieldSpecs)) {
|
|
235
|
-
if (spec
|
|
236
|
+
if (hasStaticOptions(spec)) {
|
|
237
|
+
this.selectOptions = { ...this.selectOptions, [key]: normaliseSelectOptions(spec.options) };
|
|
238
|
+
} else if (spec?.type === 'select' && spec?.optionsFrom) {
|
|
236
239
|
this.loadSelectOptions(key, spec);
|
|
237
240
|
}
|
|
238
241
|
}
|
|
@@ -468,7 +471,7 @@ var script = {
|
|
|
468
471
|
*/
|
|
469
472
|
widgetFor(key) {
|
|
470
473
|
const spec = this.fieldSpecs[key];
|
|
471
|
-
if (spec?.type === 'select' && spec?.optionsFrom) {
|
|
474
|
+
if (spec?.type === 'select' && (spec?.optionsFrom || hasStaticOptions(spec))) {
|
|
472
475
|
return 'select'
|
|
473
476
|
}
|
|
474
477
|
if (spec?.type === 'boolean') {
|
|
@@ -564,13 +567,7 @@ var script = {
|
|
|
564
567
|
? generateUrl(url)
|
|
565
568
|
: url);
|
|
566
569
|
const rows = Array.isArray(response.data) ? response.data : (response.data?.results || []);
|
|
567
|
-
const options = rows
|
|
568
|
-
const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid;
|
|
569
|
-
return {
|
|
570
|
-
id,
|
|
571
|
-
label: row.label || row.name || row.title || String(id),
|
|
572
|
-
}
|
|
573
|
-
}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '');
|
|
570
|
+
const options = normaliseSelectOptions(rows);
|
|
574
571
|
this.selectOptions = { ...this.selectOptions, [key]: options };
|
|
575
572
|
} catch (error) {
|
|
576
573
|
// A picker that could not load degrades to showing the stored
|