Compare commits

...
12 Commits
Author SHA1 Message Date
imacatandClaude Fable 5 00ff034e0e Deduplicate save and leave dialog logic in alertModal
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-05 08:22:15 +08:00
imacatandClaude Fable 5 8b06094978 Deduplicate duration and number formatting internals
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-05 07:44:55 +08:00
imacatandClaude Fable 5 ab1eacc1c2 Remove pure passthrough getters from allMapData store
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-05 00:42:58 +08:00
imacatandClaude Fable 5 e5a6f7eaf2 Remove pure passthrough getters from conformance store
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 22:31:54 +08:00
imacatandClaude Fable 5 c54557c11c Drop unused error parameter from apiError
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 22:25:12 +08:00
imacatandClaude Fable 5 7095db5ee9 Fix duplicate node position entries in cytoscapeStore saveNodePosition
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 22:21:30 +08:00
imacatandClaude Fable 5 217c18659b Deduplicate comparator logic in sortNumEngZhtw
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 15:04:10 +08:00
imacatandClaude Fable 5 ba1f2f4ac9 Clean up stale vite config options and Cypress leftovers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 15:02:56 +08:00
imacatandClaude Fable 5 834d69f686 Remove unused pinia-plugin-persistedstate plugin
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 15:01:41 +08:00
imacatandClaude Fable 5 c6da40953f Remove unused chartjs-plugin-dragdata dependency
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 15:00:36 +08:00
imacatandClaude Fable 5 b4a6ffe743 Remove unused vue-sweetalert2 plugin
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 14:59:31 +08:00
imacatandClaude Fable 5 da2cdb240f Remove unused jsUtils module
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
2026-07-04 14:53:19 +08:00
39 changed files with 960 additions and 803 deletions
+2 -132
View File
@@ -27,14 +27,13 @@
"mitt": "^3.0.1", "mitt": "^3.0.1",
"moment": "^2.30.1", "moment": "^2.30.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primevue": "^4.5.4", "primevue": "^4.5.4",
"sweetalert2": "^11.4.4",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"vue": "^3.5.29", "vue": "^3.5.29",
"vue-chartjs": "^5.3.3", "vue-chartjs": "^5.3.3",
"vue-router": "^5.0.3", "vue-router": "^5.0.3",
"vue-sweetalert2": "^5.0.11",
"vue-toast-notification": "^3.1.3", "vue-toast-notification": "^3.1.3",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
@@ -50,7 +49,6 @@
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"ajv": "^8.18.0", "ajv": "^8.18.0",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"chartjs-plugin-dragdata": "^2.3.1",
"eslint": "^10.0.2", "eslint": "^10.0.2",
"eslint-plugin-vue": "^10.8.0", "eslint-plugin-vue": "^10.8.0",
"jsdom": "^28.1.0", "jsdom": "^28.1.0",
@@ -3201,44 +3199,6 @@
"chart.js": ">=3.0.0" "chart.js": ">=3.0.0"
} }
}, },
"node_modules/chartjs-plugin-dragdata": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chartjs-plugin-dragdata/-/chartjs-plugin-dragdata-2.3.1.tgz",
"integrity": "sha512-CFD1e2d+gyH9EXb92qWu4Zb2zVoY4OtrbJYLMoGWOInE7ftoOD//4B0/k9IvKzQbdVU3JsPqUQI9KHcMpqQVfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0"
},
"peerDependencies": {
"chart.js": "^3.9.1 || ^4.0.1"
}
},
"node_modules/chartjs-plugin-dragdata/node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"dev": true,
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/chartjs-plugin-dragdata/node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
@@ -3580,12 +3540,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -5691,31 +5645,6 @@
} }
} }
}, },
"node_modules/pinia-plugin-persistedstate": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz",
"integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==",
"license": "MIT",
"dependencies": {
"defu": "^6.1.4"
},
"peerDependencies": {
"@nuxt/kit": ">=3.0.0",
"@pinia/nuxt": ">=0.10.0",
"pinia": ">=3.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@pinia/nuxt": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/@vue/devtools-api": { "node_modules/pinia/node_modules/@vue/devtools-api": {
"version": "7.7.9", "version": "7.7.9",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
@@ -6242,6 +6171,7 @@
"version": "11.4.4", "version": "11.4.4",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.4.tgz", "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.4.tgz",
"integrity": "sha512-9yYWQuRT1v9JNI/paPTSYV+68MHwe9C+HQ/I2jtfaFzoJgYRftWXOs4JqmDSjT7m2m4r8ebMMn8LcxD1Wq9B/w==", "integrity": "sha512-9yYWQuRT1v9JNI/paPTSYV+68MHwe9C+HQ/I2jtfaFzoJgYRftWXOs4JqmDSjT7m2m4r8ebMMn8LcxD1Wq9B/w==",
"license": "MIT",
"funding": { "funding": {
"url": "https://sweetalert2.github.io/#donations" "url": "https://sweetalert2.github.io/#donations"
} }
@@ -6945,17 +6875,6 @@
} }
} }
}, },
"node_modules/vue-sweetalert2": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/vue-sweetalert2/-/vue-sweetalert2-5.0.11.tgz",
"integrity": "sha512-agujdlcNeLb4Fj0/V3qgR7bEVBN8IzWFoDBP/vrSgbEGl9pjMmxnv32V3lxB0SZtQh6tL6R/UZ+C3q8tzsJbNg==",
"dependencies": {
"sweetalert2": "11.4.4"
},
"peerDependencies": {
"vue": "*"
}
},
"node_modules/vue-toast-notification": { "node_modules/vue-toast-notification": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/vue-toast-notification/-/vue-toast-notification-3.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-toast-notification/-/vue-toast-notification-3.1.3.tgz",
@@ -9042,34 +8961,6 @@
"integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==",
"requires": {} "requires": {}
}, },
"chartjs-plugin-dragdata": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chartjs-plugin-dragdata/-/chartjs-plugin-dragdata-2.3.1.tgz",
"integrity": "sha512-CFD1e2d+gyH9EXb92qWu4Zb2zVoY4OtrbJYLMoGWOInE7ftoOD//4B0/k9IvKzQbdVU3JsPqUQI9KHcMpqQVfg==",
"dev": true,
"requires": {
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0"
},
"dependencies": {
"d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"dev": true,
"requires": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
}
},
"d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"dev": true
}
}
},
"chokidar": { "chokidar": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
@@ -9324,11 +9215,6 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true "dev": true
}, },
"defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
},
"delayed-stream": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -10643,14 +10529,6 @@
} }
} }
}, },
"pinia-plugin-persistedstate": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz",
"integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==",
"requires": {
"defu": "^6.1.4"
}
},
"pkg-types": { "pkg-types": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
@@ -11373,14 +11251,6 @@
"yaml": "^2.8.2" "yaml": "^2.8.2"
} }
}, },
"vue-sweetalert2": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/vue-sweetalert2/-/vue-sweetalert2-5.0.11.tgz",
"integrity": "sha512-agujdlcNeLb4Fj0/V3qgR7bEVBN8IzWFoDBP/vrSgbEGl9pjMmxnv32V3lxB0SZtQh6tL6R/UZ+C3q8tzsJbNg==",
"requires": {
"sweetalert2": "11.4.4"
}
},
"vue-toast-notification": { "vue-toast-notification": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/vue-toast-notification/-/vue-toast-notification-3.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-toast-notification/-/vue-toast-notification-3.1.3.tgz",
+1 -3
View File
@@ -36,14 +36,13 @@
"mitt": "^3.0.1", "mitt": "^3.0.1",
"moment": "^2.30.1", "moment": "^2.30.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primevue": "^4.5.4", "primevue": "^4.5.4",
"sweetalert2": "^11.4.4",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"vue": "^3.5.29", "vue": "^3.5.29",
"vue-chartjs": "^5.3.3", "vue-chartjs": "^5.3.3",
"vue-router": "^5.0.3", "vue-router": "^5.0.3",
"vue-sweetalert2": "^5.0.11",
"vue-toast-notification": "^3.1.3", "vue-toast-notification": "^3.1.3",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
@@ -59,7 +58,6 @@
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"ajv": "^8.18.0", "ajv": "^8.18.0",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"chartjs-plugin-dragdata": "^2.3.1",
"eslint": "^10.0.2", "eslint": "^10.0.2",
"eslint-plugin-vue": "^10.8.0", "eslint-plugin-vue": "^10.8.0",
"jsdom": "^28.1.0", "jsdom": "^28.1.0",
@@ -358,8 +358,8 @@
<MoreModal <MoreModal
:listModal="issuesModal" :listModal="issuesModal"
@closeModal="issuesModal = $event" @closeModal="issuesModal = $event"
:listTraces="issueTraces" :listTraces="allIssueTraces"
:taskSeq="taskSeq" :taskSeq="allTaskSeq"
:cases="cases" :cases="cases"
:listNo="issuesNo" :listNo="issuesNo"
:traceId="traceId" :traceId="traceId"
@@ -369,8 +369,8 @@
<MoreModal <MoreModal
:listModal="loopModal" :listModal="loopModal"
@closeModal="loopModal = $event" @closeModal="loopModal = $event"
:listTraces="loopTraces" :listTraces="allLoopTraces"
:taskSeq="loopTaskSeq" :taskSeq="allLoopTaskSeq"
:cases="loopCases" :cases="loopCases"
:listNo="loopNo" :listNo="loopNo"
:traceId="looptraceId" :traceId="looptraceId"
@@ -413,12 +413,12 @@ import emitter from "@/utils/emitter";
const conformanceStore = useConformanceStore(); const conformanceStore = useConformanceStore();
const { const {
conformanceTempReportData, allConformanceTempReportData,
issueTraces, allIssueTraces,
taskSeq, allTaskSeq,
cases, cases,
loopTraces, allLoopTraces,
loopTaskSeq, allLoopTaskSeq,
loopCases, loopCases,
} = storeToRefs(conformanceStore); } = storeToRefs(conformanceStore);
@@ -525,11 +525,11 @@ const openMore = async (no) => {
// Use async/await to prevent errors caused by asynchronous data not being available yet // Use async/await to prevent errors caused by asynchronous data not being available yet
issuesNo.value = no; issuesNo.value = no;
await conformanceStore.getConformanceIssue(no); await conformanceStore.getConformanceIssue(no);
if (issueTraces.value.length === 0) return; if (allIssueTraces.value.length === 0) return;
traceId.value = issueTraces.value[0].id; traceId.value = allIssueTraces.value[0].id;
firstCases.value = await conformanceStore.getConformanceTraceDetail( firstCases.value = await conformanceStore.getConformanceTraceDetail(
no, no,
issueTraces.value[0].id, allIssueTraces.value[0].id,
0, 0,
); );
issuesModal.value = true; issuesModal.value = true;
@@ -543,11 +543,11 @@ const openLoopMore = async (no) => {
// Use async/await to prevent errors caused by asynchronous data not being available yet // Use async/await to prevent errors caused by asynchronous data not being available yet
loopNo.value = no; loopNo.value = no;
await conformanceStore.getConformanceLoop(no); await conformanceStore.getConformanceLoop(no);
if (loopTraces.value.length === 0) return; if (allLoopTraces.value.length === 0) return;
looptraceId.value = loopTraces.value[0].id; looptraceId.value = allLoopTraces.value[0].id;
loopFirstCases.value = await conformanceStore.getConformanceLoopsTraceDetail( loopFirstCases.value = await conformanceStore.getConformanceLoopsTraceDetail(
no, no,
loopTraces.value[0].id, allLoopTraces.value[0].id,
0, 0,
); );
loopModal.value = true; loopModal.value = true;
@@ -959,7 +959,7 @@ const setTimeChartData = (chartData, xMax, xMin, yMax, yMin) => {
}; };
// watch // watch
watch(conformanceTempReportData, (newValue) => { watch(allConformanceTempReportData, (newValue) => {
if (newValue?.rule && newValue.rule.min !== null) { if (newValue?.rule && newValue.rule.min !== null) {
selectDurationTime.value = { selectDurationTime.value = {
min: newValue.rule.min, min: newValue.rule.min,
@@ -180,7 +180,7 @@ const {
selectedProcessScope, selectedProcessScope,
selectedActSeqMore, selectedActSeqMore,
selectedActSeqFromTo, selectedActSeqFromTo,
conformanceAllTasks, allConformanceTask,
cfmPtEteWhole, cfmPtEteWhole,
cfmPtEteStart: cfmPtEteStartStore, cfmPtEteStart: cfmPtEteStartStore,
cfmPtEteEnd: cfmPtEteEndStore, cfmPtEteEnd: cfmPtEteEndStore,
@@ -204,7 +204,7 @@ const {
conformanceRuleData, conformanceRuleData,
conformanceLogCreateCheckId, conformanceLogCreateCheckId,
conformanceFilterCreateCheckId, conformanceFilterCreateCheckId,
conformanceTempReportData, allConformanceTempReportData,
} = storeToRefs(conformanceStore); } = storeToRefs(conformanceStore);
const { activityRadioData } = storeToRefs(conformanceInputStore); const { activityRadioData } = storeToRefs(conformanceInputStore);
@@ -391,7 +391,7 @@ const isApplyBtnDisabled = computed(() => {
// When opening a rule file, display the saved options, rules, and time // When opening a rule file, display the saved options, rules, and time
let reportWatchTimerId = null; let reportWatchTimerId = null;
watch(conformanceTempReportData, (newValue) => { watch(allConformanceTempReportData, (newValue) => {
if (reportWatchTimerId) clearTimeout(reportWatchTimerId); if (reportWatchTimerId) clearTimeout(reportWatchTimerId);
reportWatchTimerId = setTimeout(() => { reportWatchTimerId = setTimeout(() => {
if (newValue !== null) { if (newValue !== null) {
@@ -376,8 +376,8 @@ const {
selectedActSeqMore, selectedActSeqMore,
selectedActSeqFromTo, selectedActSeqFromTo,
conformanceTask, conformanceTask,
cfmSeqStart, allCfmSeqStart,
cfmSeqEnd, allCfmSeqEnd,
cfmPtEteStart, cfmPtEteStart,
cfmPtEteEnd, cfmPtEteEnd,
cfmPtEteSE, cfmPtEteSE,
@@ -435,14 +435,14 @@ const taskEnd = ref(null);
const cfmSeqStartData = computed(() => { const cfmSeqStartData = computed(() => {
const selectedTask = task.value ?? props.isSubmitShowDataSeq?.task ?? null; const selectedTask = task.value ?? props.isSubmitShowDataSeq?.task ?? null;
return isEndSelected.value return isEndSelected.value
? setSeqStartAndEndData(cfmSeqEnd.value, "sources", selectedTask) ? setSeqStartAndEndData(allCfmSeqEnd.value, "sources", selectedTask)
: cfmSeqStart.value.map((i) => i.label); : allCfmSeqStart.value.map((i) => i.label);
}); });
const cfmSeqEndData = computed(() => { const cfmSeqEndData = computed(() => {
const selectedTask = task.value ?? props.isSubmitShowDataSeq?.task ?? null; const selectedTask = task.value ?? props.isSubmitShowDataSeq?.task ?? null;
return isStartSelected.value return isStartSelected.value
? setSeqStartAndEndData(cfmSeqStart.value, "sinks", selectedTask) ? setSeqStartAndEndData(allCfmSeqStart.value, "sinks", selectedTask)
: cfmSeqEnd.value.map((i) => i.label); : allCfmSeqEnd.value.map((i) => i.label);
}); });
// Processing time // Processing time
const cfmPtEteStartData = computed(() => { const cfmPtEteStartData = computed(() => {
@@ -584,7 +584,7 @@ watch(
/** /**
* Sets the start and end radio data. * Sets the start and end radio data.
* @param {object} data - Activities list data from the backend (cfmSeqStart, cfmSeqEnd, cfmPtEteSE, etc.). * @param {object} data - Activities list data from the backend (allCfmSeqStart, allCfmSeqEnd, cfmPtEteSE, etc.).
* @param {string} category - 'start' or 'end'. * @param {string} category - 'start' or 'end'.
* @returns {array} * @returns {array}
*/ */
@@ -615,7 +615,7 @@ function setStartAndEndData(data, category, taskVal) {
} }
/** /**
* Resets the activity sequence linked start and end radio data. * Resets the activity sequence linked start and end radio data.
* @param {object} data - Activities list data from the backend (cfmSeqStart or cfmSeqEnd). * @param {object} data - Activities list data from the backend (allCfmSeqStart or allCfmSeqEnd).
* @param {string} category - 'sources' or 'sinks'. * @param {string} category - 'sources' or 'sinks'.
* @param {string} task - The selected activity task. * @param {string} task - The selected activity task.
* @returns {array} * @returns {array}
@@ -250,10 +250,10 @@ const {
selectedProcessScope, selectedProcessScope,
selectedActSeqMore, selectedActSeqMore,
selectedActSeqFromTo, selectedActSeqFromTo,
conformanceAllTasks, allConformanceTask,
conformanceTask, conformanceTask,
cfmSeqStart, allCfmSeqStart,
cfmSeqEnd, allCfmSeqEnd,
cfmPtEteStart, cfmPtEteStart,
cfmPtEteEnd, cfmPtEteEnd,
cfmPtEteSE, cfmPtEteSE,
@@ -471,7 +471,7 @@ emitter.on("actRadioData", (data) => {
// Activity duration // Activity duration
case "cfmDur": case "cfmDur":
state.timeDuration = getDurationTime( state.timeDuration = getDurationTime(
conformanceAllTasks.value, allConformanceTask.value,
"act", "act",
task, task,
); );
@@ -83,7 +83,7 @@ import getMoment from "moment";
const props = defineProps(["selectValue"]); const props = defineProps(["selectValue"]);
const allMapDataStore = useAllMapDataStore(); const allMapDataStore = useAllMapDataStore();
const { filterTimeframe, selectTimeFrame } = storeToRefs(allMapDataStore); const { allFilterTimeframe, selectTimeFrame } = storeToRefs(allMapDataStore);
const selectRange = ref(1000); // Number of divisions for the selection range const selectRange = ref(1000); // Number of divisions for the selection range
const selectArea = ref(null); const selectArea = ref(null);
@@ -120,8 +120,8 @@ watch(timeFrameStartEnd, (newValue) => {
// Compute slider data; time format: millisecond timestamps // Compute slider data; time format: millisecond timestamps
const sliderData = computed(() => { const sliderData = computed(() => {
const xAxisMin = new Date(filterTimeframe.value.x_axis.min).getTime(); const xAxisMin = new Date(allFilterTimeframe.value.x_axis.min).getTime();
const xAxisMax = new Date(filterTimeframe.value.x_axis.max).getTime(); const xAxisMax = new Date(allFilterTimeframe.value.x_axis.max).getTime();
const range = xAxisMax - xAxisMin; const range = xAxisMax - xAxisMin;
const step = range / selectRange.value; const step = range / selectRange.value;
const data = []; const data = [];
@@ -135,9 +135,9 @@ const sliderData = computed(() => {
// Add the minimum and maximum values // Add the minimum and maximum values
const timeFrameData = computed(() => { const timeFrameData = computed(() => {
if (!filterTimeframe.value?.data || filterTimeframe.value.data.length < 10) if (!allFilterTimeframe.value?.data || allFilterTimeframe.value.data.length < 10)
return []; return [];
const data = filterTimeframe.value.data.map((i) => ({ x: i.x, y: i.y })); const data = allFilterTimeframe.value.data.map((i) => ({ x: i.x, y: i.y }));
// See ./public/timeFrameSlope for the y-axis slope calculation diagram // See ./public/timeFrameSlope for the y-axis slope calculation diagram
// x values are 0 ~ 11, // x values are 0 ~ 11,
// Name three coordinates (ax, ay), (bx, by), (cx, cy) as (a, b), (c, d), (e, f) // Name three coordinates (ax, ay), (bx, by), (cx, cy) as (a, b), (c, d), (e, f)
@@ -148,18 +148,18 @@ const timeFrameData = computed(() => {
const a = 0; const a = 0;
let b; let b;
const c = 1; const c = 1;
const d = filterTimeframe.value.data[0].y; const d = allFilterTimeframe.value.data[0].y;
const e = 2; const e = 2;
const f = filterTimeframe.value.data[1].y; const f = allFilterTimeframe.value.data[1].y;
b = (e * d - a * d - f * a - f * c) / (e - c - a); b = (e * d - a * d - f * a - f * c) / (e - c - a);
if (b < 0) { if (b < 0) {
b = 0; b = 0;
} }
// Y-axis maximum value // Y-axis maximum value
const ma = 9; const ma = 9;
const mb = filterTimeframe.value.data[8].y; const mb = allFilterTimeframe.value.data[8].y;
const mc = 10; const mc = 10;
const md = filterTimeframe.value.data[9].y; const md = allFilterTimeframe.value.data[9].y;
const me = 11; const me = 11;
let mf = (mb * me - mb * mc - md * me + md * ma) / (ma - mc); let mf = (mb * me - mb * mc - md * me + md * ma) / (ma - mc);
if (mf < 0) { if (mf < 0) {
@@ -168,12 +168,12 @@ const timeFrameData = computed(() => {
// Add the minimum value // Add the minimum value
data.unshift({ data.unshift({
x: filterTimeframe.value.x_axis.min_base, x: allFilterTimeframe.value.x_axis.min_base,
y: b, y: b,
}); });
// Add the maximum value // Add the maximum value
data.push({ data.push({
x: filterTimeframe.value.x_axis.max_base, x: allFilterTimeframe.value.x_axis.max_base,
y: mf, y: mf,
}); });
@@ -181,8 +181,8 @@ const timeFrameData = computed(() => {
}); });
const labelsData = computed(() => { const labelsData = computed(() => {
const min = new Date(filterTimeframe.value.x_axis.min_base).getTime(); const min = new Date(allFilterTimeframe.value.x_axis.min_base).getTime();
const max = new Date(filterTimeframe.value.x_axis.max_base).getTime(); const max = new Date(allFilterTimeframe.value.x_axis.max_base).getTime();
const numPoints = 11; const numPoints = 11;
const step = (max - min) / (numPoints - 1); const step = (max - min) / (numPoints - 1);
const data = []; const data = [];
@@ -195,8 +195,8 @@ const labelsData = computed(() => {
watch(selectTimeFrame, (newValue, oldValue) => { watch(selectTimeFrame, (newValue, oldValue) => {
if (newValue.length === 0) { if (newValue.length === 0) {
startTime.value = new Date(filterTimeframe.value.x_axis.min); startTime.value = new Date(allFilterTimeframe.value.x_axis.min);
endTime.value = new Date(filterTimeframe.value.x_axis.max); endTime.value = new Date(allFilterTimeframe.value.x_axis.max);
selectArea.value = [0, selectRange.value]; selectArea.value = [0, selectRange.value];
resizeMask(chart.value); resizeMask(chart.value);
} }
@@ -247,7 +247,7 @@ function resizeRightMask(chartInstance, to) {
* Creates and renders the Chart.js area chart for timeframe data. * Creates and renders the Chart.js area chart for timeframe data.
*/ */
function createChart() { function createChart() {
const max = filterTimeframe.value.y_axis.max * 1.1; const max = allFilterTimeframe.value.y_axis.max * 1.1;
const minX = timeFrameData.value[0]?.x; const minX = timeFrameData.value[0]?.x;
const maxX = timeFrameData.value[timeFrameData.value.length - 1]?.x; const maxX = timeFrameData.value[timeFrameData.value.length - 1]?.x;
@@ -403,10 +403,10 @@ onMounted(() => {
// Slider // Slider
selectArea.value = [0, selectRange.value]; selectArea.value = [0, selectRange.value];
// Calendar // Calendar
startMinDate.value = new Date(filterTimeframe.value.x_axis.min); startMinDate.value = new Date(allFilterTimeframe.value.x_axis.min);
startMaxDate.value = new Date(filterTimeframe.value.x_axis.max); startMaxDate.value = new Date(allFilterTimeframe.value.x_axis.max);
endMinDate.value = new Date(filterTimeframe.value.x_axis.min); endMinDate.value = new Date(allFilterTimeframe.value.x_axis.min);
endMaxDate.value = new Date(filterTimeframe.value.x_axis.max); endMaxDate.value = new Date(allFilterTimeframe.value.x_axis.max);
// Set the calendar range to match the timeline range // Set the calendar range to match the timeline range
startTime.value = startMinDate.value; startTime.value = startMinDate.value;
endTime.value = startMaxDate.value; endTime.value = startMaxDate.value;
@@ -146,8 +146,8 @@ const {
infinit404, infinit404,
baseInfiniteStart, baseInfiniteStart,
baseTraces, baseTraces,
baseTraceTaskSeq, allBaseTraceTaskSeq,
baseCases, allBaseCase,
} = storeToRefs(allMapDataStore); } = storeToRefs(allMapDataStore);
const { isLoading } = storeToRefs(loadingStore); const { isLoading } = storeToRefs(loadingStore);
@@ -240,7 +240,7 @@ const caseData = computed(() => {
}); });
const columnData = computed(() => { const columnData = computed(() => {
const data = cloneDeep(baseCases.value); const data = cloneDeep(allBaseCase.value);
let result = [ let result = [
{ field: "id", header: "Case Id" }, { field: "id", header: "Case Id" },
{ field: "started_at", header: "Start time" }, { field: "started_at", header: "Start time" },
@@ -378,7 +378,7 @@ function setNodesData() {
// Clear nodes to prevent accumulation on each render // Clear nodes to prevent accumulation on each render
processMap.value.nodes = []; processMap.value.nodes = [];
// Populate nodes with data returned from the API call // Populate nodes with data returned from the API call
baseTraceTaskSeq.value.forEach((node, index) => { allBaseTraceTaskSeq.value.forEach((node, index) => {
processMap.value.nodes.push({ processMap.value.nodes.push({
data: { data: {
id: index, id: index,
@@ -398,7 +398,7 @@ function setNodesData() {
*/ */
function setEdgesData() { function setEdgesData() {
processMap.value.edges = []; processMap.value.edges = [];
baseTraceTaskSeq.value.forEach((edge, index) => { allBaseTraceTaskSeq.value.forEach((edge, index) => {
processMap.value.edges.push({ processMap.value.edges.push({
data: { data: {
source: `${index}`, source: `${index}`,
@@ -431,7 +431,7 @@ function createCy() {
function handleScroll(event) { function handleScroll(event) {
if ( if (
infinitMaxItems.value || infinitMaxItems.value ||
baseCases.value.length < 20 || allBaseCase.value.length < 20 ||
infiniteFinish.value === false infiniteFinish.value === false
) )
return; return;
@@ -452,7 +452,7 @@ async function fetchData() {
infiniteFinish.value = false; infiniteFinish.value = false;
baseInfiniteStart.value += 20; baseInfiniteStart.value += 20;
await allMapDataStore.getBaseTraceDetail(); await allMapDataStore.getBaseTraceDetail();
infiniteData.value = [...infiniteData.value, ...baseCases.value]; infiniteData.value = [...infiniteData.value, ...allBaseCase.value];
infiniteFinish.value = true; infiniteFinish.value = true;
isLoading.value = false; isLoading.value = false;
} catch (error) { } catch (error) {
@@ -128,7 +128,7 @@ const {
infiniteStart, infiniteStart,
traceId, traceId,
traces, traces,
traceTaskSeq, allTraceTaskSeq,
infiniteFirstCases, infiniteFirstCases,
} = storeToRefs(allMapDataStore); } = storeToRefs(allMapDataStore);
@@ -261,7 +261,7 @@ function setNodesData() {
// Clear nodes to prevent accumulation on each render // Clear nodes to prevent accumulation on each render
processMap.value.nodes = []; processMap.value.nodes = [];
// Populate nodes with data returned from the API call // Populate nodes with data returned from the API call
traceTaskSeq.value.forEach((node, index) => { allTraceTaskSeq.value.forEach((node, index) => {
processMap.value.nodes.push({ processMap.value.nodes.push({
data: { data: {
id: index, id: index,
@@ -281,7 +281,7 @@ function setNodesData() {
*/ */
function setEdgesData() { function setEdgesData() {
processMap.value.edges = []; processMap.value.edges = [];
traceTaskSeq.value.forEach((edge, index) => { allTraceTaskSeq.value.forEach((edge, index) => {
processMap.value.edges.push({ processMap.value.edges.push({
data: { data: {
source: `${index}`, source: `${index}`,
+20 -20
View File
@@ -167,7 +167,7 @@ import getMoment from "moment";
const route = useRoute(); const route = useRoute();
const allMapDataStore = useAllMapDataStore(); const allMapDataStore = useAllMapDataStore();
const { logId, stats, createFilterId } = storeToRefs(allMapDataStore); const { logId, allStats, createFilterId } = storeToRefs(allMapDataStore);
const isPanel = ref(false); const isPanel = ref(false);
const statData = ref(null); const statData = ref(null);
@@ -184,37 +184,37 @@ function getPercentLabel(val) {
/** Transforms raw stats into display-ready format with localized numbers and time labels. */ /** Transforms raw stats into display-ready format with localized numbers and time labels. */
function getStatData() { function getStatData() {
if (!stats.value) return; if (!allStats.value) return;
statData.value = { statData.value = {
cases: { cases: {
count: stats.value.cases.count.toLocaleString("en-US"), count: allStats.value.cases.count.toLocaleString("en-US"),
total: stats.value.cases.total.toLocaleString("en-US"), total: allStats.value.cases.total.toLocaleString("en-US"),
ratio: getPercentLabel(stats.value.cases.ratio), ratio: getPercentLabel(allStats.value.cases.ratio),
}, },
traces: { traces: {
count: stats.value.traces.count.toLocaleString("en-US"), count: allStats.value.traces.count.toLocaleString("en-US"),
total: stats.value.traces.total.toLocaleString("en-US"), total: allStats.value.traces.total.toLocaleString("en-US"),
ratio: getPercentLabel(stats.value.traces.ratio), ratio: getPercentLabel(allStats.value.traces.ratio),
}, },
task_instances: { task_instances: {
count: stats.value.task_instances.count.toLocaleString("en-US"), count: allStats.value.task_instances.count.toLocaleString("en-US"),
total: stats.value.task_instances.total.toLocaleString("en-US"), total: allStats.value.task_instances.total.toLocaleString("en-US"),
ratio: getPercentLabel(stats.value.task_instances.ratio), ratio: getPercentLabel(allStats.value.task_instances.ratio),
}, },
tasks: { tasks: {
count: stats.value.tasks.count.toLocaleString("en-US"), count: allStats.value.tasks.count.toLocaleString("en-US"),
total: stats.value.tasks.total.toLocaleString("en-US"), total: allStats.value.tasks.total.toLocaleString("en-US"),
ratio: getPercentLabel(stats.value.tasks.ratio), ratio: getPercentLabel(allStats.value.tasks.ratio),
}, },
started_at: getMoment(stats.value.started_at).format("YYYY-MM-DD HH:mm"), started_at: getMoment(allStats.value.started_at).format("YYYY-MM-DD HH:mm"),
completed_at: getMoment(stats.value.completed_at).format( completed_at: getMoment(allStats.value.completed_at).format(
"YYYY-MM-DD HH:mm", "YYYY-MM-DD HH:mm",
), ),
case_duration: { case_duration: {
min: getTimeLabel(stats.value.case_duration.min), min: getTimeLabel(allStats.value.case_duration.min),
max: getTimeLabel(stats.value.case_duration.max), max: getTimeLabel(allStats.value.case_duration.max),
average: getTimeLabel(stats.value.case_duration.average), average: getTimeLabel(allStats.value.case_duration.average),
median: getTimeLabel(stats.value.case_duration.median), median: getTimeLabel(allStats.value.case_duration.median),
}, },
}; };
} }
+1 -3
View File
@@ -20,7 +20,6 @@ import cytoscape from "cytoscape";
import dagre from "cytoscape-dagre"; import dagre from "cytoscape-dagre";
import popper from "cytoscape-popper"; import popper from "cytoscape-popper";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import VueSweetalert2 from "vue-sweetalert2";
// import CSS // import CSS
import "./assets/main.css"; import "./assets/main.css";
@@ -67,7 +66,6 @@ cytoscape.use(popper((ref) => ref));
app.use(pinia); app.use(pinia);
app.use(router); app.use(router);
app.use(VueSweetalert2);
app.use(ToastPlugin, { app.use(ToastPlugin, {
// use `this.$toast` in Vue.js // use `this.$toast` in Vue.js
position: "bottom", position: "bottom",
@@ -107,7 +105,7 @@ async function enableMocking() {
const { http, HttpResponse } = await import("msw"); const { http, HttpResponse } = await import("msw");
const { worker } = await import("./mocks/browser.js"); const { worker } = await import("./mocks/browser.js");
await worker.start({ onUnhandledRequest: "bypass" }); await worker.start({ onUnhandledRequest: "bypass" });
// Expose on window so Cypress can add per-test overrides // Expose on window so Playwright can add per-test overrides
window.__mswWorker__ = worker; window.__mswWorker__ = worker;
window.__msw__ = { http, HttpResponse }; window.__msw__ = { http, HttpResponse };
} }
+5 -23
View File
@@ -6,6 +6,8 @@
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30 // cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module abbreviateNumber Duration abbreviation formatting. */ /** @module abbreviateNumber Duration abbreviation formatting. */
import formatDuration from "./formatDuration.js";
/** /**
* Converts a total number of seconds into a human-readable abbreviated * Converts a total number of seconds into a human-readable abbreviated
* duration string using days, hours, minutes, and seconds. * duration string using days, hours, minutes, and seconds.
@@ -15,27 +17,7 @@
* or "0" if totalSeconds is zero. * or "0" if totalSeconds is zero.
*/ */
export default function abbreviateNumber(totalSeconds) { export default function abbreviateNumber(totalSeconds) {
let seconds = 0; const parsed = Number.parseInt(totalSeconds);
let minutes = 0; if (parsed === 0) return "0";
let hours = 0; return formatDuration(parsed, { separator: " " });
let days = 0;
let result = "";
let symbols = ["d", "h", "m", "s"];
totalSeconds = Number.parseInt(totalSeconds);
if (!Number.isNaN(totalSeconds)) {
seconds = totalSeconds % 60;
minutes = (Math.floor(totalSeconds - seconds) / 60) % 60;
hours = Math.floor(totalSeconds / 3600) % 24;
days = Math.floor(totalSeconds / (3600 * 24));
}
const units = [days, hours, minutes, seconds];
for (let i = 0; i < units.length; i++) {
if (units[i] > 0) result += units[i] + symbols[i] + " ";
}
result = result.trim();
if (totalSeconds === 0) result = "0";
return result;
} }
+134 -160
View File
@@ -29,20 +29,23 @@ const customClass = {
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ", "!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ",
}; };
/** /**
* Shows a modal dialog to save a new filter with a user-provided name. * Shows a modal dialog prompting for a name and saves through the
* given backend API function.
* *
* @param {Function} addFilterId - Backend API function to create the filter. * @param {string} title - The dialog title.
* @param {Function|null} [next=null] - Vue Router next() guard callback. * @param {string} inputPlaceholder - The name input placeholder text.
* @returns {Promise<boolean>} True if the filter was saved, false otherwise. * @param {Function} save - Backend API function receiving the name.
* @param {Function|null} [onCancel=null] - Callback invoked when the
* dialog is cancelled or dismissed.
* @returns {Promise<boolean>} True if saved, false otherwise.
*/ */
export async function saveFilter(addFilterId, next = null) { async function promptSaveName(title, inputPlaceholder, save, onCancel = null) {
let fileName = ""; let fileName = "";
const pageAdminStore = usePageAdminStore();
const { value, isConfirmed } = await Swal.fire({ const { value, isConfirmed } = await Swal.fire({
title: "SAVE NEW FILTER", title: title,
input: "text", input: "text",
inputPlaceholder: "Enter Filter Name.", inputPlaceholder: inputPlaceholder,
inputValue: fileName, inputValue: fileName,
inputAttributes: { inputAttributes: {
maxlength: 200, maxlength: 200,
@@ -64,7 +67,7 @@ export async function saveFilter(addFilterId, next = null) {
// Determine whether to redirect based on the return value // Determine whether to redirect based on the return value
if (isConfirmed) { if (isConfirmed) {
// Save succeeded // Save succeeded
await addFilterId(fileName); await save(fileName);
// Show save complete notification // Show save complete notification
if (value) { if (value) {
// Example of value: yes // Example of value: yes
@@ -73,16 +76,34 @@ export async function saveFilter(addFilterId, next = null) {
// Clear the input field // Clear the input field
fileName = ""; fileName = "";
return true; return true;
} else {
// Clicked cancel or outside the dialog; save failed.
pageAdminStore.keepPreviousPage();
// Not every time we have nontrivial next value
if (next !== null) {
next(false);
}
return false;
} }
// Clicked cancel or outside the dialog; save failed.
if (onCancel !== null) onCancel();
return false;
}
/**
* Shows a modal dialog to save a new filter with a user-provided name.
*
* @param {Function} addFilterId - Backend API function to create the filter.
* @param {Function|null} [next=null] - Vue Router next() guard callback.
* @returns {Promise<boolean>} True if the filter was saved, false otherwise.
*/
export async function saveFilter(addFilterId, next = null) {
const pageAdminStore = usePageAdminStore();
return promptSaveName(
"SAVE NEW FILTER",
"Enter Filter Name.",
addFilterId,
() => {
pageAdminStore.keepPreviousPage();
// Not every time we have nontrivial next value
if (next !== null) {
next(false);
}
},
);
} }
/** /**
* Shows a timed success notification after a file has been saved. * Shows a timed success notification after a file has been saved.
@@ -117,9 +138,67 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
const allMapDataStore = useAllMapDataStore(); const allMapDataStore = useAllMapDataStore();
const pageAdminStore = usePageAdminStore(); const pageAdminStore = usePageAdminStore();
const result = await Swal.fire({ await confirmLeave({
title: "SAVE YOUR FILTER?", title: "SAVE YOUR FILTER?",
html: "If you want to continue using this filter in any other page, please select [Yes].", html: "If you want to continue using this filter in any other page, please select [Yes].",
saveChanges: async () => {
if (allMapDataStore.createFilterId) {
await allMapDataStore.updateFilter();
if (allMapDataStore.isUpdateFilter) {
await savedSuccessfully(allMapDataStore.filterName);
}
} else {
// Dangerous, here shows a modal
await saveFilter(addFilterId, next);
}
},
navigateAfterSave: true,
// Handle page admin issue
beforeDismiss: () => pageAdminStore.keepPreviousPage(),
discardTemp: () => {
allMapDataStore.tempFilterId = null;
},
next: next,
toPath: toPath,
logOut: logOut,
});
}
/**
* Shows the "save before leaving" confirmation dialog and runs the
* confirm, cancel, and backdrop flows shared by the filter and
* conformance pages.
*
* @param {Object} options - The dialog and flow options.
* @param {string} options.title - The dialog title.
* @param {string} [options.html] - Optional dialog body HTML.
* @param {Function} options.saveChanges - Async callback saving the
* unsaved changes on confirm.
* @param {boolean} options.navigateAfterSave - Whether to navigate (or
* log out) after saving on confirm.
* @param {Function|null} options.beforeDismiss - Callback invoked on
* cancel or backdrop dismiss before anything else, or null.
* @param {Function} options.discardTemp - Callback discarding the
* temporary unsaved data on cancel.
* @param {Function} options.next - Vue Router next() guard callback.
* @param {string} options.toPath - The destination route path.
* @param {Function} [options.logOut] - Optional logout function to call
* instead of navigating.
* @returns {Promise<void>}
*/
async function confirmLeave({
title,
html,
saveChanges,
navigateAfterSave,
beforeDismiss,
discardTemp,
next,
toPath,
logOut,
}) {
const result = await Swal.fire({
title: title,
...(html !== undefined && { html: html }),
icon: "warning", icon: "warning",
iconColor: "#FF3366", iconColor: "#FF3366",
reverseButtons: true, reverseButtons: true,
@@ -132,31 +211,16 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
}); });
if (result.isConfirmed) { if (result.isConfirmed) {
if (allMapDataStore.createFilterId) { await saveChanges();
await allMapDataStore.updateFilter(); if (navigateAfterSave) {
if (allMapDataStore.isUpdateFilter) { logOut ? logOut() : next(toPath);
await savedSuccessfully(allMapDataStore.filterName);
}
} else {
// Dangerous, here shows a modal
await saveFilter(addFilterId, next);
} }
logOut ? logOut() : next(toPath);
} else if (result.dismiss === "cancel") { } else if (result.dismiss === "cancel") {
// console.log('popup cancel case', ); if (beforeDismiss) beforeDismiss();
// Handle page admin issue discardTemp();
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
pageAdminStore.keepPreviousPage();
allMapDataStore.tempFilterId = null;
logOut ? logOut() : next(toPath); logOut ? logOut() : next(toPath);
} else if (result.dismiss === "backdrop") { } else if (result.dismiss === "backdrop") {
// console.log('popup backdrop case', ); if (beforeDismiss) beforeDismiss();
// Handle page admin issue
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
pageAdminStore.keepPreviousPage();
if (!logOut) { if (!logOut) {
next(false); next(false);
} }
@@ -171,47 +235,16 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
* @returns {Promise<boolean>} True if the rule was saved, false otherwise. * @returns {Promise<boolean>} True if the rule was saved, false otherwise.
*/ */
export async function saveConformance(addConformanceCreateCheckId) { export async function saveConformance(addConformanceCreateCheckId) {
let fileName = ""; return promptSaveName(
const { value, isConfirmed } = await Swal.fire({ "SAVE NEW RULE",
title: "SAVE NEW RULE", "Enter Rule Name.",
input: "text", addConformanceCreateCheckId,
inputPlaceholder: "Enter Rule Name.", );
inputValue: fileName,
inputAttributes: {
maxlength: 200,
},
inputValidator: (value) => {
if (!value) return "You need to write something!";
fileName = value;
},
icon: "info",
iconHtml:
'<span class="material-symbols-outlined !text-[58px]">cloud_upload</span>',
iconColor: "#0099FF",
reverseButtons: true,
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
customClass: customClass,
});
// Determine whether to redirect based on the return value
if (isConfirmed) {
// Save succeeded
await addConformanceCreateCheckId(fileName);
// Show save complete notification
if (value) savedSuccessfully(value);
// Clear the input field
fileName = "";
return true;
} else {
// Clicked cancel or outside the dialog; save failed.
return false;
}
} }
/** /**
* Prompts the user to save unsaved conformance rule changes before * Prompts the user to save unsaved conformance rule changes before
* leaving the Conformance page. Delegates to helper functions for * leaving the Conformance page. Handles confirm (save), cancel
* confirm, cancel, and backdrop scenarios. * (discard), and backdrop (stay) scenarios.
* *
* @param {Function} next - Vue Router next() guard callback. * @param {Function} next - Vue Router next() guard callback.
* @param {Function} addConformanceCreateCheckId - Backend API function * @param {Function} addConformanceCreateCheckId - Backend API function
@@ -227,91 +260,32 @@ export async function leaveConformance(
logOut, logOut,
) { ) {
const conformanceStore = useConformanceStore(); const conformanceStore = useConformanceStore();
const result = await showConfirmationDialog();
if (result.isConfirmed) { await confirmLeave({
await handleConfirmed(conformanceStore, addConformanceCreateCheckId);
} else {
await handleDismiss(result.dismiss, conformanceStore, next, toPath, logOut);
}
}
/**
* Displays the "SAVE YOUR RULE?" confirmation dialog.
* @returns {Promise<Object>} The SweetAlert2 result object.
*/
async function showConfirmationDialog() {
return Swal.fire({
title: "SAVE YOUR RULE?", title: "SAVE YOUR RULE?",
icon: "warning", saveChanges: async () => {
iconColor: "#FF3366", if (
reverseButtons: true, conformanceStore.conformanceFilterCreateCheckId ||
confirmButtonText: "Yes", conformanceStore.conformanceLogCreateCheckId
confirmButtonColor: "#FF3366", ) {
showCancelButton: true, await conformanceStore.updateConformance();
cancelButtonText: "No", if (conformanceStore.isUpdateConformance) {
cancelButtonColor: "#94a3b8", await savedSuccessfully(conformanceStore.conformanceFileName);
customClass: customClass, }
}); } else {
} await saveConformance(addConformanceCreateCheckId);
/**
* Handles the confirmed save action for conformance rules.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} addConformanceCreateCheckId - API function to create check.
* @returns {Promise<void>}
*/
async function handleConfirmed(conformanceStore, addConformanceCreateCheckId) {
if (
conformanceStore.conformanceFilterCreateCheckId ||
conformanceStore.conformanceLogCreateCheckId
) {
await conformanceStore.updateConformance();
if (conformanceStore.isUpdateConformance) {
await savedSuccessfully(conformanceStore.conformanceFileName);
}
} else {
await saveConformance(addConformanceCreateCheckId);
}
}
/**
* Handles dismiss actions (cancel or backdrop click) for conformance modals.
* @param {string} dismissType - The SweetAlert2 dismiss reason.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} next - Vue Router next() guard callback.
* @param {string} toPath - The destination route path.
* @param {Function} [logOut] - Optional logout function.
* @returns {Promise<void>}
*/
async function handleDismiss(
dismissType,
conformanceStore,
next,
toPath,
logOut,
) {
switch (dismissType) {
case "cancel":
resetTempCheckId(conformanceStore);
logOut ? logOut() : next(toPath);
break;
case "backdrop":
if (!logOut) {
next(false);
} }
break; },
default: navigateAfterSave: false,
break; beforeDismiss: null,
} discardTemp: () => {
} conformanceStore.conformanceFilterTempCheckId = null;
conformanceStore.conformanceLogTempCheckId = null;
/** },
* Resets temporary conformance check IDs to null. next: next,
* @param {Object} conformanceStore - The conformance Pinia store. toPath: toPath,
*/ logOut: logOut,
function resetTempCheckId(conformanceStore) { });
conformanceStore.conformanceFilterTempCheckId = null;
conformanceStore.conformanceLogTempCheckId = null;
} }
/** /**
+1 -2
View File
@@ -14,10 +14,9 @@ import "vue-toast-notification/dist/theme-sugar.css";
* 401 errors are handled by the axios response interceptor * 401 errors are handled by the axios response interceptor
* in api/client.js. * in api/client.js.
* *
* @param {Object} error - The error object from the API call.
* @param {string} toastMessage - The message to display in the toast. * @param {string} toastMessage - The message to display in the toast.
*/ */
export default function apiError(error, toastMessage) { export default function apiError(toastMessage) {
const $toast = useToast(); const $toast = useToast();
$toast.default(toastMessage, { position: "bottom" }); $toast.default(toastMessage, { position: "bottom" });
} }
+71
View File
@@ -0,0 +1,71 @@
// The Lucia project.
// Copyright 2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/7/5
// AI assistance: Claude Code (Anthropic)
/** @module formatDuration Shared duration formatting internals. */
/**
* The duration units from the largest to the smallest.
*
* @constant {Array<{symbol: string, seconds: number}>}
*/
export const DURATION_UNITS = [
{ symbol: "d", seconds: 24 * 60 * 60 },
{ symbol: "h", seconds: 60 * 60 },
{ symbol: "m", seconds: 60 },
{ symbol: "s", seconds: 1 },
];
/**
* Picks the largest duration unit suitable for a number of seconds.
*
* @param {number} seconds - The number of seconds to pick a unit for.
* @param {boolean} [exclusive=false] - Whether a unit is chosen only
* when the value is strictly greater than one unit (true), or when
* it is greater than or equal to one unit (false).
* @returns {{symbol: string, seconds: number}} The chosen unit; falls
* back to seconds when no larger unit qualifies.
*/
export function pickDurationUnit(seconds, exclusive = false) {
return DURATION_UNITS.find(
(unit) =>
unit.seconds === 1 ||
(exclusive ? seconds > unit.seconds : seconds >= unit.seconds),
);
}
/**
* Formats a duration in seconds as a day/hour/minute/second cascade.
*
* Zero-valued units are omitted, except that the seconds unit can be
* forced with the alwaysSeconds option. The result is truncated to at
* most maxUnits units, starting from the largest.
*
* @param {number} totalSeconds - The total number of seconds.
* @param {Object} [options] - The formatting options.
* @param {string} [options.separator=" "] - The string between units.
* @param {number} [options.maxUnits=Infinity] - The maximum number of
* units to keep, starting from the largest.
* @param {boolean} [options.alwaysSeconds=false] - Whether to always
* append the seconds unit, even when it is zero.
* @returns {string} The formatted duration (e.g. "2d 3h 15m 30s"), or
* an empty string when no unit qualifies.
*/
export default function formatDuration(
totalSeconds,
{ separator = " ", maxUnits = Infinity, alwaysSeconds = false } = {},
) {
const seconds = totalSeconds % 60;
const minutes = (Math.floor(totalSeconds - seconds) / 60) % 60;
const hours = Math.floor(totalSeconds / 3600) % 24;
const days = Math.floor(totalSeconds / (3600 * 24));
const parts = [];
if (days > 0) parts.push(days + "d");
if (hours > 0) parts.push(hours + "h");
if (minutes > 0) parts.push(minutes + "m");
if (seconds > 0 || alwaysSeconds) parts.push(seconds + "s");
return parts.slice(0, maxUnits).join(separator);
}
+5 -17
View File
@@ -6,30 +6,18 @@
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30 // cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module numberLabel Number formatting with comma separators. */ /** @module numberLabel Number formatting with comma separators. */
/**
* Formats an integer string by inserting commas every three digits.
* @param {string} numberStr - A string of digits to format.
* @returns {string} The formatted string with commas (e.g. "1,000,000").
*/
const formatNumberWithCommas = (numberStr) => {
let reversedStr = numberStr.split("").reverse().join("");
let groupedStr = reversedStr.match(/.{1,3}/g) || [];
let joinedStr = groupedStr.join(",");
let finalStr = joinedStr.split("").reverse().join("");
return finalStr;
};
/** /**
* Converts a number to a string with comma-separated thousands. * Converts a number to a string with comma-separated thousands.
* *
* Handles decimal numbers by formatting only the integer part. * Handles decimal numbers by formatting only the integer part. A comma
* is inserted before every group of three characters counted from the
* end of the integer part.
* *
* @param {number} num - The number to format. * @param {number} num - The number to format.
* @returns {string} The formatted number string (e.g. "1,234.56"). * @returns {string} The formatted number string (e.g. "1,234.56").
*/ */
export default function numberLabel(num) { export default function numberLabel(num) {
let parts = num.toString().split("."); const parts = num.toString().split(".");
parts[0] = formatNumberWithCommas(parts[0]); parts[0] = parts[0].replace(/(.)(?=(?:.{3})+$)/g, "$1,");
return parts.join("."); return parts.join(".");
} }
+22 -37
View File
@@ -8,6 +8,8 @@
import getMoment from "moment"; import getMoment from "moment";
import formatDuration, { DURATION_UNITS } from "./formatDuration.js";
/** /**
* Extends backend chart data with extrapolated boundary points for * Extends backend chart data with extrapolated boundary points for
* line charts. Prepends a calculated minimum and appends a calculated * line charts. Prepends a calculated minimum and appends a calculated
@@ -241,26 +243,8 @@ export function getXIndex(data, xValue) {
export function formatTime(seconds) { export function formatTime(seconds) {
if (Number.isNaN(Number(seconds))) { if (Number.isNaN(Number(seconds))) {
return null; return null;
} else {
const remainingSeconds = seconds % 60;
const minutes = (Math.floor(seconds - remainingSeconds) / 60) % 60;
const hours = Math.floor(seconds / 3600) % 24;
const days = Math.floor(seconds / (3600 * 24));
let result = "";
if (days > 0) {
result += `${days}d`;
}
if (hours > 0) {
result += `${hours}h`;
}
if (minutes > 0) {
result += `${minutes}m`;
}
result += `${remainingSeconds}s`;
return result.trim(); // Remove trailing whitespace
} }
return formatDuration(seconds, { separator: "", alwaysSeconds: true });
} }
/** /**
* Truncates each time string to show only the two largest time units. * Truncates each time string to show only the two largest time units.
@@ -269,22 +253,23 @@ export function formatTime(seconds) {
* @returns {Array<string>} Array of truncated strings (e.g. "2d 3h"). * @returns {Array<string>} Array of truncated strings (e.g. "2d 3h").
*/ */
export function formatMaxTwo(times) { export function formatMaxTwo(times) {
const formattedTimes = []; const secondsPerUnit = Object.fromEntries(
for (let time of times) { DURATION_UNITS.map((unit) => [unit.symbol, unit.seconds]),
// Match numbers and units (days, hours, minutes, seconds); assume numbers have at most 10 digits );
let units = time.match(/\d{1,10}[dhms]/g) || []; return times.map((time) => {
let formattedTime = ""; // Match numbers and units (days, hours, minutes, seconds); assume
let count = 0; // numbers have at most 10 digits.
const units = time.match(/\d{1,10}[dhms]/g);
// Keep only the two largest units if (!units) return "";
for (let unit of units) { const totalSeconds = units.reduce(
if (count >= 2) { (sum, unit) =>
break; sum + Number.parseInt(unit) * secondsPerUnit[unit.slice(-1)],
} 0,
formattedTime += unit + " "; );
count++; return formatDuration(totalSeconds, {
} separator: " ",
formattedTimes.push(formattedTime.trim()); // Remove trailing whitespace maxUnits: 2,
} alwaysSeconds: units.some((unit) => unit.endsWith("s")),
return formattedTimes; });
});
} }
+1 -11
View File
@@ -16,17 +16,7 @@
* @returns {Array<string|number>} The sorted array (same reference). * @returns {Array<string|number>} The sorted array (same reference).
*/ */
export function sortNumEngZhtw(data) { export function sortNumEngZhtw(data) {
return data.sort((a, b) => { return data.sort(sortNumEngZhtwForFilter);
const isANumber = !Number.isNaN(Number.parseFloat(a)) && Number.isFinite(Number(a));
const isBNumber = !Number.isNaN(Number.parseFloat(b)) && Number.isFinite(Number(b));
if (isANumber && isBNumber) return Number.parseFloat(a) - Number.parseFloat(b);
if (isANumber) return -1;
if (isBNumber) return 1;
return a.localeCompare(b, "zh-Hant-TW", { sensitivity: "accent" });
});
} }
/** /**
+43 -113
View File
@@ -8,6 +8,8 @@
import moment from "moment"; import moment from "moment";
import { pickDurationUnit } from "./formatDuration.js";
/** @constant {number} Number of decimal places for formatted time values. */ /** @constant {number} Number of decimal places for formatted time values. */
const TOFIXED_DECIMAL = 1; const TOFIXED_DECIMAL = 1;
@@ -36,34 +38,11 @@ export const getStepSizeOfYTicks = (maxTimeInSecond, numOfParts) => {
* character ("d", "h", "m", or "s") and the converted time value. * character ("d", "h", "m", or "s") and the converted time value.
*/ */
const getTimeUnitAndValueToUse = (secondToDecide) => { const getTimeUnitAndValueToUse = (secondToDecide) => {
const day = 24 * 60 * 60; const unit = pickDurationUnit(secondToDecide, true);
const hour = 60 * 60; return {
const minutes = 60; unitToUse: unit.symbol,
const dd = secondToDecide / day; timeValue: secondToDecide / unit.seconds,
const hh = secondToDecide / hour; };
const mm = secondToDecide / minutes;
if (dd > 0 && dd > 1) {
return {
unitToUse: "d",
timeValue: secondToDecide / day,
};
} else if (hh > 0 && hh > 1) {
return {
unitToUse: "h",
timeValue: secondToDecide / hour,
};
} else if (mm > 0 && mm > 1) {
return {
unitToUse: "m",
timeValue: secondToDecide / minutes,
};
} else {
return {
unitToUse: "s",
timeValue: secondToDecide,
};
}
}; };
/** /**
@@ -80,6 +59,27 @@ export function getYTicksByIndex(stepSize, index, unitToUse) {
return `${shortenStepsizeMultIndex}${unitToUse}`; return `${shortenStepsizeMultIndex}${unitToUse}`;
} }
/**
* Formats seconds as a single-unit time label with the given suffixes.
*
* Chooses the largest unit whose size the value reaches. Values are
* rounded to fixedNumber decimal places, except seconds, which are
* concatenated as-is.
*
* @param {number} second - The total number of seconds.
* @param {number} fixedNumber - Number of decimal places.
* @param {{d: string, h: string, m: string, s: string}} suffixes - The
* unit suffixes to append.
* @returns {string} The formatted time label.
*/
function scaledTimeLabel(second, fixedNumber, suffixes) {
const unit = pickDurationUnit(second);
if (unit.symbol === "s") {
return second + suffixes.s;
}
return (second / unit.seconds).toFixed(fixedNumber) + suffixes[unit.symbol];
}
/** /**
* Converts seconds to a human-readable time string with full unit names. * Converts seconds to a human-readable time string with full unit names.
* *
@@ -89,27 +89,12 @@ export function getYTicksByIndex(stepSize, index, unitToUse) {
* "30 mins", "5 sec"). * "30 mins", "5 sec").
*/ */
export function getTimeLabel(second, fixedNumber = 0) { export function getTimeLabel(second, fixedNumber = 0) {
const day = 24 * 60 * 60; return scaledTimeLabel(second, fixedNumber, {
const hour = 60 * 60; d: " days",
const minutes = 60; h: " hrs",
m: " mins",
// The modulo operation limits the value to within one day (0 to 86399), s: " sec",
// representing the remaining seconds after full days. });
const dd = Math.floor(second / day);
const hh = Math.floor((second % day) / hour);
const mm = Math.floor((second % hour) / minutes);
if (dd > 0) {
return (second / day).toFixed(fixedNumber) + " days";
} else if (hh > 0) {
return ((second % day) / hour).toFixed(fixedNumber) + " hrs";
} else if (mm > 0) {
return ((second % hour) / minutes).toFixed(fixedNumber) + " mins";
}
if (second === 0) {
return second + " sec";
}
return second + " sec";
} }
/** /**
@@ -123,24 +108,12 @@ export function getTimeLabel(second, fixedNumber = 0) {
* @returns {string} The formatted string (e.g. "1d", "6.8h", "30m", "5s"). * @returns {string} The formatted string (e.g. "1d", "6.8h", "30m", "5s").
*/ */
export function simpleTimeLabel(second, fixedNumber = 0) { export function simpleTimeLabel(second, fixedNumber = 0) {
const day = 24 * 60 * 60; return scaledTimeLabel(second, fixedNumber, {
const hour = 60 * 60; d: "d",
const minutes = 60; h: "h",
const dd = Math.floor(second / day); m: "m",
const hh = Math.floor((second % day) / hour); s: "s",
const mm = Math.floor((second % hour) / minutes); });
if (dd > 0) {
return (second / day).toFixed(fixedNumber) + "d";
} else if (hh > 0) {
return ((second % day) / hour).toFixed(fixedNumber) + "h";
} else if (mm > 0) {
return ((second % hour) / minutes).toFixed(fixedNumber) + "m";
}
if (second === 0) {
return second + "s";
}
return second + "s";
} }
/** /**
* Converts seconds to a time string using the same unit as the maximum * Converts seconds to a time string using the same unit as the maximum
@@ -155,52 +128,9 @@ export function simpleTimeLabel(second, fixedNumber = 0) {
* @returns {string} The formatted string (e.g. "1.5d", "6.8h"). * @returns {string} The formatted string (e.g. "1.5d", "6.8h").
*/ */
export function followTimeLabel(second, max, fixedNumber = 0) { export function followTimeLabel(second, max, fixedNumber = 0) {
const day = 24 * 60 * 60; const unit = pickDurationUnit(max, true);
const hour = 60 * 60; const value = second / unit.seconds;
const minutes = 60; return value.toFixed(value === 0 ? 0 : fixedNumber) + unit.symbol;
const dd = max / day;
const hh = max / hour;
const mm = max / minutes;
let maxUnit = "";
let result = "";
if (dd > 1) {
maxUnit = "d";
} else if (hh > 1) {
maxUnit = "h";
} else if (mm > 1) {
maxUnit = "m";
} else {
maxUnit = "s";
}
switch (maxUnit) {
case "d":
if (second / day === 0) {
fixedNumber = 0;
}
result = (second / day).toFixed(fixedNumber) + "d";
break;
case "h":
if (second / hour === 0) {
fixedNumber = 0;
}
result = (second / hour).toFixed(fixedNumber) + "h";
break;
case "m":
if (second / minutes === 0) {
fixedNumber = 0;
}
result = (second / minutes).toFixed(fixedNumber) + "m";
break;
case "s":
if (second === 0) {
fixedNumber = 0;
}
result = second.toFixed(fixedNumber) + "s";
break;
}
return result;
} }
/** /**
+8 -8
View File
@@ -103,7 +103,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
const customizedResponseData = await this.customizeAllUserList(response.data); const customizedResponseData = await this.customizeAllUserList(response.data);
this.allUserAccountList = await this.moveCurrentLoginUserToFirstRow(customizedResponseData); this.allUserAccountList = await this.moveCurrentLoginUserToFirstRow(customizedResponseData);
} catch (error) { } catch (error) {
apiError(error, 'Failed to get all users.'); apiError('Failed to get all users.');
} }
}, },
/** /**
@@ -155,7 +155,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
setTimeout(() => this.resetJustCreateFlag(), JUST_CREATE_ACCOUNT_HOT_DURATION_MINS * 1000 * 60); setTimeout(() => this.resetJustCreateFlag(), JUST_CREATE_ACCOUNT_HOT_DURATION_MINS * 1000 * 60);
} }
} catch (error) { } catch (error) {
apiError(error, 'Failed to add a new account.'); apiError('Failed to add a new account.');
} }
}, },
/** /**
@@ -170,7 +170,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
const response = await apiClient.delete(apiDelete); const response = await apiClient.delete(apiDelete);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to delete the account.'); apiError('Failed to delete the account.');
return false; return false;
} }
}, },
@@ -191,7 +191,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
}); });
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to edit the account.'); apiError('Failed to edit the account.');
return false; return false;
} }
}, },
@@ -207,7 +207,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
}); });
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to edit name of account.'); apiError('Failed to edit name of account.');
return false; return false;
} }
}, },
@@ -224,7 +224,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
}); });
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to edit password of account.'); apiError('Failed to edit password of account.');
return false; return false;
} }
}, },
@@ -239,7 +239,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
const response = await apiClient.put(apiAddRole); const response = await apiClient.put(apiAddRole);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to add role to the account.'); apiError('Failed to add role to the account.');
return false; return false;
} }
}, },
@@ -254,7 +254,7 @@ export const useAcctMgmtStore = defineStore('acctMgmtStore', {
const response = await apiClient.delete(apiDeleteRole); const response = await apiClient.delete(apiDeleteRole);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to delete a role from the account.'); apiError('Failed to delete a role from the account.');
return false; return false;
} }
}, },
+10 -52
View File
@@ -71,57 +71,18 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
processMap: (state) => { processMap: (state) => {
return state.allProcessMap; return state.allProcessMap;
}, },
bpmn: (state) => {
return state.allBpmn;
},
stats: (state) => {
return state.allStats;
},
insights: (state) => {
return state.allInsights;
},
traces: (state) => { traces: (state) => {
return [...state.allTrace].sort((x, y) => x.id - y.id); return [...state.allTrace].sort((x, y) => x.id - y.id);
}, },
baseTraces: (state) => { baseTraces: (state) => {
return [...state.allBaseTrace].sort((x, y) => x.id - y.id); return [...state.allBaseTrace].sort((x, y) => x.id - y.id);
}, },
cases: (state) => {
return state.allCase;
},
baseCases: (state) => {
return state.allBaseCase;
},
infiniteFirstCases: (state) => { infiniteFirstCases: (state) => {
if (state.infiniteStart === 0) return state.allCase; if (state.infiniteStart === 0) return state.allCase;
}, },
BaseInfiniteFirstCases: (state) => { BaseInfiniteFirstCases: (state) => {
if (state.baseInfiniteStart === 0) return state.allBaseCase; if (state.baseInfiniteStart === 0) return state.allBaseCase;
}, },
traceTaskSeq: (state) => {
return state.allTraceTaskSeq;
},
baseTraceTaskSeq: (state) => {
return state.allBaseTraceTaskSeq;
},
// All tasks
filterTasks: (state) => {
return state.allFilterTask;
},
// form start to end tasks
filterStartToEnd: (state) => {
return state.allFilterStartToEnd;
},
// form end to start tasks
filterEndToStart: (state) => {
return state.allFilterEndToStart;
},
filterTimeframe: (state) => {
return state.allFilterTimeframe;
},
filterTrace: (state) => {
return state.allFilterTrace;
},
filterAttrs: (state) => { filterAttrs: (state) => {
if (state.allFilterAttrs !== null) { if (state.allFilterAttrs !== null) {
return state.allFilterAttrs.map((att) => { return state.allFilterAttrs.map((att) => {
@@ -155,9 +116,6 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
} }
return []; return [];
}, },
allFunnels: (state) => {
return state.allFunnelData;
},
}, },
actions: { actions: {
/** /**
@@ -172,7 +130,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.allStats = response.data.stats; this.allStats = response.data.stats;
this.allInsights = response.data.insights; this.allInsights = response.data.insights;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Map."); apiError("Failed to load the Map.");
} }
}, },
/** /**
@@ -191,7 +149,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.allBaseTrace = baseResponse.data; this.allBaseTrace = baseResponse.data;
} }
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Trace."); apiError("Failed to load the Trace.");
} }
}, },
/** /**
@@ -233,7 +191,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.infinite404 = 404; this.infinite404 = 404;
return; return;
} }
apiError(error, "Failed to load the Trace Detail."); apiError("Failed to load the Trace Detail.");
return []; return [];
} }
}, },
@@ -278,7 +236,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.infinite404 = 404; this.infinite404 = 404;
return; return;
} }
apiError(error, "Failed to load the Base Trace Detail."); apiError("Failed to load the Base Trace Detail.");
return []; return [];
} }
}, },
@@ -309,7 +267,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.allFilterTimeframe.x_axis.max = this.allFilterTimeframe.x_axis.max =
max === null ? null : moment(max).format("YYYY/MM/DD HH:mm"); max === null ? null : moment(max).format("YYYY/MM/DD HH:mm");
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Filter Parameters."); apiError("Failed to load the Filter Parameters.");
} }
}, },
/** /**
@@ -323,7 +281,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
const response = await apiClient.post(api, this.postRuleData); const response = await apiClient.post(api, this.postRuleData);
this.hasResultRule = response.data.result; this.hasResultRule = response.data.result;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Has Result."); apiError("Failed to load the Has Result.");
} }
}, },
/** /**
@@ -337,7 +295,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
const response = await apiClient.post(api, this.postRuleData); const response = await apiClient.post(api, this.postRuleData);
this.tempFilterId = response.data.id; this.tempFilterId = response.data.id;
} catch (error) { } catch (error) {
apiError(error, "Failed to add the Temporary Filters."); apiError("Failed to add the Temporary Filters.");
} }
}, },
/** /**
@@ -357,7 +315,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.createFilterId = response.data.id; this.createFilterId = response.data.id;
this.tempFilterId = null; this.tempFilterId = null;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Filters."); apiError("Failed to load the Filters.");
} }
}, },
/** /**
@@ -375,7 +333,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.filterName = response.data.name; this.filterName = response.data.name;
this.baseLogId = response.data.log?.id; this.baseLogId = response.data.log?.id;
} catch (error) { } catch (error) {
apiError(error, "Failed to get Filter Detail."); apiError("Failed to get Filter Detail.");
} }
} }
}, },
@@ -392,7 +350,7 @@ export const useAllMapDataStore = defineStore("allMapDataStore", {
this.isUpdateFilter = response.status === 200; this.isUpdateFilter = response.status === 200;
this.tempFilterId = null; this.tempFilterId = null;
} catch (error) { } catch (error) {
apiError(error, "Failed to update an Existing Filter."); apiError("Failed to update an Existing Filter.");
} }
}, },
}, },
+3 -3
View File
@@ -37,7 +37,7 @@ export const useCompareStore = defineStore("compareStore", {
const response = await apiClient.get(api); const response = await apiClient.get(api);
this.allCompareDashboardData = response.data; this.allCompareDashboardData = response.data;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Compare."); apiError("Failed to load the Compare.");
} }
}, },
/** /**
@@ -63,7 +63,7 @@ export const useCompareStore = defineStore("compareStore", {
return response.data.stats; return response.data.stats;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Compare's States."); apiError("Failed to load the Compare's States.");
return null; return null;
} }
}, },
@@ -79,7 +79,7 @@ export const useCompareStore = defineStore("compareStore", {
if (file) return file.name; if (file) return file.name;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Compare's file name."); apiError("Failed to load the Compare's file name.");
} }
return ""; return "";
}, },
+5 -36
View File
@@ -91,18 +91,9 @@ export const useConformanceStore = defineStore("conformanceStore", {
conformanceFileName: null, // File name displayed in the save success modal conformanceFileName: null, // File name displayed in the save success modal
}), }),
getters: { getters: {
conformanceAllTasks: (state) => {
return state.allConformanceTask;
},
conformanceTask: (state) => { conformanceTask: (state) => {
return state.allConformanceTask.map((i) => i.label); return state.allConformanceTask.map((i) => i.label);
}, },
cfmSeqStart: (state) => {
return state.allCfmSeqStart;
},
cfmSeqEnd: (state) => {
return state.allCfmSeqEnd;
},
cfmPtEteWhole: (state) => { cfmPtEteWhole: (state) => {
return state.allProcessingTime.end_to_end.whole; return state.allProcessingTime.end_to_end.whole;
}, },
@@ -157,18 +148,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
cfmCtEteSE: (state) => { cfmCtEteSE: (state) => {
return state.allCycleTime.end_to_end.start_end; return state.allCycleTime.end_to_end.start_end;
}, },
conformanceTempReportData: (state) => {
return state.allConformanceTempReportData;
},
routeFile: (state) => {
return state.allRouteFile;
},
issueTraces: (state) => {
return state.allIssueTraces;
},
taskSeq: (state) => {
return state.allTaskSeq;
},
cases: (state) => { cases: (state) => {
if (state.allCases !== null) { if (state.allCases !== null) {
return state.allCases.map((c) => { return state.allCases.map((c) => {
@@ -220,12 +199,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
} }
return []; return [];
}, },
loopTraces: (state) => {
return state.allLoopTraces;
},
loopTaskSeq: (state) => {
return state.allLoopTaskSeq;
},
loopCases: (state) => { loopCases: (state) => {
if (state.allLoopCases !== null) { if (state.allLoopCases !== null) {
return state.allLoopCases.map((c) => { return state.allLoopCases.map((c) => {
@@ -276,7 +249,7 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.allWaitingTime = response.data.waiting_time; this.allWaitingTime = response.data.waiting_time;
this.allCycleTime = response.data.cycle_time; this.allCycleTime = response.data.cycle_time;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Conformance Parameters."); apiError("Failed to load the Conformance Parameters.");
} }
}, },
/** /**
@@ -295,7 +268,7 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.conformanceLogTempCheckId = response.data.id; this.conformanceLogTempCheckId = response.data.id;
} }
} catch (error) { } catch (error) {
apiError(error, "Failed to add the Temporary Check for a file."); apiError("Failed to add the Temporary Check for a file.");
} }
}, },
/** /**
@@ -312,7 +285,7 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.allConformanceTempReportData = response.data; this.allConformanceTempReportData = response.data;
} }
} catch (error) { } catch (error) {
apiError(error, "Failed to Get the Temporary Log Conformance Report."); apiError("Failed to Get the Temporary Log Conformance Report.");
} }
}, },
/** /**
@@ -326,7 +299,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.allIssueTraces = response.data.traces; this.allIssueTraces = response.data.traces;
} catch (error) { } catch (error) {
apiError( apiError(
error,
"Failed to Get the detail of a temporary log conformance issue.", "Failed to Get the detail of a temporary log conformance issue.",
); );
} }
@@ -350,7 +322,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
return null; return null;
} }
apiError( apiError(
error,
"Failed to Get the detail of a temporary log conformance issue.", "Failed to Get the detail of a temporary log conformance issue.",
); );
return null; return null;
@@ -367,7 +338,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.allLoopTraces = response.data.traces; this.allLoopTraces = response.data.traces;
} catch (error) { } catch (error) {
apiError( apiError(
error,
"Failed to Get the detail of a temporary log conformance loop.", "Failed to Get the detail of a temporary log conformance loop.",
); );
} }
@@ -391,7 +361,6 @@ export const useConformanceStore = defineStore("conformanceStore", {
return null; return null;
} }
apiError( apiError(
error,
"Failed to Get the detail of a temporary log conformance loop.", "Failed to Get the detail of a temporary log conformance loop.",
); );
return null; return null;
@@ -419,7 +388,7 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.conformanceLogTempCheckId = null; this.conformanceLogTempCheckId = null;
} }
} catch (error) { } catch (error) {
apiError(error, "Failed to add the Conformance Check for a file."); apiError("Failed to add the Conformance Check for a file.");
} }
}, },
/** /**
@@ -435,7 +404,7 @@ export const useConformanceStore = defineStore("conformanceStore", {
this.conformanceLogTempCheckId = null; this.conformanceLogTempCheckId = null;
this.conformanceFilterTempCheckId = null; this.conformanceFilterTempCheckId = null;
} catch (error) { } catch (error) {
apiError(error, "Failed to update an Existing Conformance."); apiError("Failed to update an Existing Conformance.");
} }
}, },
/** /**
+6 -12
View File
@@ -26,8 +26,8 @@ export interface Node {
} }
export interface NodePositions { export interface NodePositions {
[direction: string]: { [graphId: string]: {
[graphId: string]: Node[]; [direction: string]: Node[];
} }
} }
@@ -51,16 +51,10 @@ export const useCytoscapeStore = defineStore('cytoscapeStore', {
if (!this.nodePositions[this.currentGraphId][direction]) { if (!this.nodePositions[this.currentGraphId][direction]) {
this.nodePositions[this.currentGraphId][direction] = []; this.nodePositions[this.currentGraphId][direction] = [];
} }
// If this graph's data was previously stored in localStorage const nodeToSave = this.nodePositions[this.currentGraphId][direction]
if (localStorage.getItem(SAVE_KEY_NAME)) { .find(node => node.id === nodeId);
const nodeToSave = this.nodePositions[this.currentGraphId][direction] if (nodeToSave) {
.find(node => node.id === nodeId); nodeToSave.position = position;
if (nodeToSave) {
nodeToSave.position = position;
} else {
this.nodePositions[this.currentGraphId][direction]
.push({ id: nodeId, position: position });
}
} else { } else {
this.nodePositions[this.currentGraphId][direction] this.nodePositions[this.currentGraphId][direction]
.push({ id: nodeId, position: position }); .push({ id: nodeId, position: position });
+9 -9
View File
@@ -146,7 +146,7 @@ export const useFilesStore = defineStore("filesStore", {
: null; : null;
}); });
} catch (error) { } catch (error) {
apiError(error, "Failed to load the files."); apiError("Failed to load the files.");
} }
}, },
/** /**
@@ -180,7 +180,7 @@ export const useFilesStore = defineStore("filesStore", {
} }
} else { } else {
Swal.close(); // Close the loading progress bar Swal.close(); // Close the loading progress bar
apiError(error, "Failed to upload the files."); apiError("Failed to upload the files.");
} }
} }
}, },
@@ -195,7 +195,7 @@ export const useFilesStore = defineStore("filesStore", {
const response = await apiClient.get(api); const response = await apiClient.get(api);
this.allUploadDetail = response.data.preview; this.allUploadDetail = response.data.preview;
} catch (error) { } catch (error) {
apiError(error, "Failed to get upload detail."); apiError("Failed to get upload detail.");
} }
}, },
/** /**
@@ -223,7 +223,7 @@ export const useFilesStore = defineStore("filesStore", {
uploadFailedSecond(detail); uploadFailedSecond(detail);
} else { } else {
Swal.close(); // Close the loading progress bar Swal.close(); // Close the loading progress bar
apiError(error, "Failed to upload the log files."); apiError("Failed to upload the log files.");
} }
} }
}, },
@@ -247,7 +247,7 @@ export const useFilesStore = defineStore("filesStore", {
this.uploadFileName = null; this.uploadFileName = null;
await this.fetchAllFiles(); await this.fetchAllFiles();
} catch (error) { } catch (error) {
apiError(error, "Failed to rename."); apiError("Failed to rename.");
} }
}, },
/** /**
@@ -262,7 +262,7 @@ export const useFilesStore = defineStore("filesStore", {
); );
this.allDependentsData = response.data; this.allDependentsData = response.data;
} catch (error) { } catch (error) {
apiError(error, "Failed to get Dependents of the files."); apiError("Failed to get Dependents of the files.");
} }
}, },
/** /**
@@ -282,7 +282,7 @@ export const useFilesStore = defineStore("filesStore", {
await this.fetchAllFiles(); await this.fetchAllFiles();
await deleteSuccess(); await deleteSuccess();
} catch (error) { } catch (error) {
apiError(error, "Failed to delete."); apiError("Failed to delete.");
} finally { } finally {
loading.isLoading = false; loading.isLoading = false;
} }
@@ -300,7 +300,7 @@ export const useFilesStore = defineStore("filesStore", {
try { try {
await apiClient.delete(api); await apiClient.delete(api);
} catch (error) { } catch (error) {
apiError(error, "Failed to Remove a Deletion Record."); apiError("Failed to Remove a Deletion Record.");
} finally { } finally {
loading.isLoading = false; loading.isLoading = false;
} }
@@ -325,7 +325,7 @@ export const useFilesStore = defineStore("filesStore", {
link.click(); link.click();
globalThis.URL.revokeObjectURL(url); globalThis.URL.revokeObjectURL(url);
} catch (error) { } catch (error) {
apiError(error, "Failed to download."); apiError("Failed to download.");
} }
}, },
}, },
+1 -1
View File
@@ -121,7 +121,7 @@ export const useLoginStore = defineStore("loginStore", {
this.userData = response.data; this.userData = response.data;
} catch (error) { } catch (error) {
apiError(error, "Failed to load user data."); apiError("Failed to load user data.");
} }
}, },
/** /**
+1 -3
View File
@@ -5,13 +5,11 @@
// imacat.yang@dsp.im (imacat), 2023/9/23 // imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30 // cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** /**
* @module stores/main Pinia store instance with persisted state plugin. * @module stores/main Pinia store instance.
*/ */
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia(); const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia; export default pinia;
+4 -4
View File
@@ -163,8 +163,8 @@ export const useMapPathStore = defineStore('mapPathStore', {
} }
}, },
async createInsightWithPath() { async createInsightWithPath() {
const { insights } = useAllMapDataStore(); const { allInsights } = useAllMapDataStore();
this.insights = { ...insights }; this.insights = { ...allInsights };
this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes() this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes()
.filter(function (elem) { .filter(function (elem) {
return elem.data('label').toLowerCase() === 'start'; return elem.data('label').toLowerCase() === 'start';
@@ -182,8 +182,8 @@ export const useMapPathStore = defineStore('mapPathStore', {
}); });
// Depth First Search from the starting node // Depth First Search from the starting node
this.depthFirstSearchCreatePath(this.startNode, [this.startNode], []); this.depthFirstSearchCreatePath(this.startNode, [this.startNode], []);
const { insights } = useAllMapDataStore(); const { allInsights } = useAllMapDataStore();
this.insights = { ...insights }; this.insights = { ...allInsights };
this.matchGraphPathWithInsightsPath(); this.matchGraphPathWithInsightsPath();
}, },
/** /**
+1 -1
View File
@@ -52,7 +52,7 @@ export const usePerformanceStore = defineStore("performanceStore", {
const response = await apiClient.get(api); const response = await apiClient.get(api);
this.allPerformanceData = response.data; this.allPerformanceData = response.data;
} catch (error) { } catch (error) {
apiError(error, "Failed to load the Performance."); apiError("Failed to load the Performance.");
} }
}, },
/** /**
-39
View File
@@ -1,39 +0,0 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module jsUtils General JavaScript utility functions. */
/**
* Recursively prints an object's properties to the console.
* @param {Object} obj - The object to print.
* @param {number} [indent=0] - The current indentation level.
*/
export const printObject = (obj, indent = 0) => {
const padding = " ".repeat(indent);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === "object" && obj[key] !== null) {
console.log(`${padding}${key}: {`);
printObject(obj[key], indent + 2);
console.log(`${padding}}`);
} else {
console.log(`${padding}${key}: ${obj[key]}`);
}
}
}
};
/**
* Returns a cryptographically random integer between 0 and max (inclusive).
* @param {number} max - The upper bound (inclusive).
* @returns {number} A random integer in [0, max].
*/
export const getRandomInt = (max) => {
const array = new Uint32Array(1);
globalThis.crypto.getRandomValues(array);
return Math.floor((array[0] / (0xffffffff + 1)) * (max + 1));
};
+21 -21
View File
@@ -86,23 +86,23 @@
></SidebarView> ></SidebarView>
<SidebarState <SidebarState
v-model:visible="sidebarState" v-model:visible="sidebarState"
:insights="insights" :insights="allInsights"
:stats="stats" :stats="allStats"
></SidebarState> ></SidebarState>
<SidebarTraces <SidebarTraces
v-model:visible="sidebarTraces" v-model:visible="sidebarTraces"
:cases="cases" :cases="allCase"
@switch-Trace-Id="switchTraceId" @switch-Trace-Id="switchTraceId"
ref="tracesViewRef" ref="tracesViewRef"
> >
</SidebarTraces> </SidebarTraces>
<SidebarFilter <SidebarFilter
v-model:visible="sidebarFilter" v-model:visible="sidebarFilter"
:filterTasks="filterTasks" :filterTasks="allFilterTask"
:filterStartToEnd="filterStartToEnd" :filterStartToEnd="allFilterStartToEnd"
:filterEndToStart="filterEndToStart" :filterEndToStart="allFilterEndToStart"
:filterTimeframe="filterTimeframe" :filterTimeframe="allFilterTimeframe"
:filterTrace="filterTrace" :filterTrace="allFilterTrace"
@submit-all="createCy(mapType)" @submit-all="createCy(mapType)"
@switch-Trace-Id="switchTraceId" @switch-Trace-Id="switchTraceId"
ref="sidebarFilterRefComp" ref="sidebarFilterRefComp"
@@ -138,7 +138,7 @@ export default {
break; break;
} }
await conformanceStore.getConformanceReport(true); await conformanceStore.getConformanceReport(true);
to.meta.file = conformanceStore.routeFile; to.meta.file = conformanceStore.allRouteFile;
} }
next(); next();
}, },
@@ -176,25 +176,25 @@ const allMapDataStore = useAllMapDataStore();
const { isLoading } = storeToRefs(loadingStore); const { isLoading } = storeToRefs(loadingStore);
const { const {
processMap, processMap,
bpmn, allBpmn,
stats, allStats,
insights, allInsights,
traceId, traceId,
traces, traces,
baseTraces, baseTraces,
baseTraceId, baseTraceId,
filterTasks, allFilterTask,
filterStartToEnd, allFilterStartToEnd,
filterEndToStart, allFilterEndToStart,
filterTimeframe, allFilterTimeframe,
filterTrace, allFilterTrace,
temporaryData, temporaryData,
isRuleData, isRuleData,
ruleData, ruleData,
logId, logId,
baseLogId, baseLogId,
createFilterId, createFilterId,
cases, allCase,
postRuleData, postRuleData,
} = storeToRefs(allMapDataStore); } = storeToRefs(allMapDataStore);
@@ -401,7 +401,7 @@ function setNodesData(mapData) {
}; };
mapData.nodes = []; mapData.nodes = [];
const mapSource = mapTypeVal === "processMap" ? processMap.value : bpmn.value; const mapSource = mapTypeVal === "processMap" ? processMap.value : allBpmn.value;
mapSource.vertices.forEach((node) => { mapSource.vertices.forEach((node) => {
switch (node.type) { switch (node.type) {
case "gateway": case "gateway":
@@ -485,7 +485,7 @@ function setEdgesData(mapData) {
}; };
mapData.edges = []; mapData.edges = [];
const mapSource = mapTypeVal === "processMap" ? processMap.value : bpmn.value; const mapSource = mapTypeVal === "processMap" ? processMap.value : allBpmn.value;
mapSource.edges.forEach((edge) => { mapSource.edges.forEach((edge) => {
mapData.edges.push({ mapData.edges.push({
data: { data: {
@@ -510,7 +510,7 @@ function setEdgesData(mapData) {
async function createCy(type) { async function createCy(type) {
const graphId = document.getElementById("cy"); const graphId = document.getElementById("cy");
const mapData = type === "processMap" ? processMapData.value : bpmnData.value; const mapData = type === "processMap" ? processMapData.value : bpmnData.value;
const mapSource = type === "processMap" ? processMap.value : bpmn.value; const mapSource = type === "processMap" ? processMap.value : allBpmn.value;
if (mapSource.vertices.length !== 0) { if (mapSource.vertices.length !== 0) {
setNodesData(mapData); setNodesData(mapData);
@@ -42,7 +42,7 @@ defineOptions({
break; break;
} }
await conformanceStore.getConformanceReport(); await conformanceStore.getConformanceReport();
to.meta.file = await conformanceStore.conformanceTempReportData?.file; to.meta.file = await conformanceStore.allConformanceTempReportData?.file;
} }
next(); next();
}, },
@@ -68,7 +68,7 @@ const {
selectedActSeqMore, selectedActSeqMore,
selectedActSeqFromTo, selectedActSeqFromTo,
conformanceRuleData, conformanceRuleData,
conformanceTempReportData, allConformanceTempReportData,
conformanceFileName, conformanceFileName,
} = storeToRefs(conformanceStore); } = storeToRefs(conformanceStore);
+21 -21
View File
@@ -86,22 +86,22 @@
></SidebarView> ></SidebarView>
<SidebarState <SidebarState
v-model:visible="sidebarState" v-model:visible="sidebarState"
:insights="insights" :insights="allInsights"
:stats="stats" :stats="allStats"
></SidebarState> ></SidebarState>
<SidebarTraces <SidebarTraces
v-model:visible="sidebarTraces" v-model:visible="sidebarTraces"
:cases="cases" :cases="allCase"
@switch-Trace-Id="switchTraceId" @switch-Trace-Id="switchTraceId"
ref="tracesViewRef" ref="tracesViewRef"
></SidebarTraces> ></SidebarTraces>
<SidebarFilter <SidebarFilter
v-model:visible="sidebarFilter" v-model:visible="sidebarFilter"
:filterTasks="filterTasks" :filterTasks="allFilterTask"
:filterStartToEnd="filterStartToEnd" :filterStartToEnd="allFilterStartToEnd"
:filterEndToStart="filterEndToStart" :filterEndToStart="allFilterEndToStart"
:filterTimeframe="filterTimeframe" :filterTimeframe="allFilterTimeframe"
:filterTrace="filterTrace" :filterTrace="allFilterTrace"
@submit-all="createCy(mapType)" @submit-all="createCy(mapType)"
@switch-Trace-Id="switchTraceId" @switch-Trace-Id="switchTraceId"
ref="sidebarFilterRefComp" ref="sidebarFilterRefComp"
@@ -137,7 +137,7 @@ export default {
break; break;
} }
await conformanceStore.getConformanceReport(true); await conformanceStore.getConformanceReport(true);
to.meta.file = conformanceStore.routeFile; to.meta.file = conformanceStore.allRouteFile;
} }
next(); next();
}, },
@@ -175,25 +175,25 @@ const allMapDataStore = useAllMapDataStore();
const { isLoading } = storeToRefs(loadingStore); const { isLoading } = storeToRefs(loadingStore);
const { const {
processMap, processMap,
bpmn, allBpmn,
stats, allStats,
insights, allInsights,
traceId, traceId,
traces, traces,
baseTraces, baseTraces,
baseTraceId, baseTraceId,
filterTasks, allFilterTask,
filterStartToEnd, allFilterStartToEnd,
filterEndToStart, allFilterEndToStart,
filterTimeframe, allFilterTimeframe,
filterTrace, allFilterTrace,
temporaryData, temporaryData,
isRuleData, isRuleData,
ruleData, ruleData,
logId, logId,
baseLogId, baseLogId,
createFilterId, createFilterId,
cases, allCase,
postRuleData, postRuleData,
} = storeToRefs(allMapDataStore); } = storeToRefs(allMapDataStore);
@@ -398,7 +398,7 @@ function setNodesData(mapData) {
}; };
mapData.nodes = []; mapData.nodes = [];
const mapSource = mapTypeVal === "processMap" ? processMap.value : bpmn.value; const mapSource = mapTypeVal === "processMap" ? processMap.value : allBpmn.value;
mapSource.vertices.forEach((node) => { mapSource.vertices.forEach((node) => {
switch (node.type) { switch (node.type) {
case "gateway": case "gateway":
@@ -477,7 +477,7 @@ function setEdgesData(mapData) {
}; };
mapData.edges = []; mapData.edges = [];
const mapSource = mapTypeVal === "processMap" ? processMap.value : bpmn.value; const mapSource = mapTypeVal === "processMap" ? processMap.value : allBpmn.value;
mapSource.edges.forEach((edge) => { mapSource.edges.forEach((edge) => {
mapData.edges.push({ mapData.edges.push({
data: { data: {
@@ -499,7 +499,7 @@ function setEdgesData(mapData) {
async function createCy(type) { async function createCy(type) {
const graphId = document.getElementById("cy"); const graphId = document.getElementById("cy");
const mapData = type === "processMap" ? processMapData.value : bpmnData.value; const mapData = type === "processMap" ? processMapData.value : bpmnData.value;
const mapSource = type === "processMap" ? processMap.value : bpmn.value; const mapSource = type === "processMap" ? processMap.value : allBpmn.value;
if (mapSource.vertices.length !== 0) { if (mapSource.vertices.length !== 0) {
setNodesData(mapData); setNodesData(mapData);
@@ -263,7 +263,7 @@ export default {
break; break;
} }
await conformanceStore.getConformanceReport(true); await conformanceStore.getConformanceReport(true);
to.meta.file = conformanceStore.routeFile; to.meta.file = conformanceStore.allRouteFile;
} }
next(); next();
}, },
@@ -0,0 +1,63 @@
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/07/04
import { describe, it, expect, beforeEach, vi } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { useCytoscapeStore } from "@/stores/cytoscapeStore";
// Mock localStorage since jsdom's localStorage is limited
const localStorageMock = (() => {
let store = {};
return {
getItem: vi.fn((key) => store[key] || null),
setItem: vi.fn((key, value) => {
store[key] = value;
}),
removeItem: vi.fn((key) => {
delete store[key];
}),
clear: vi.fn(() => {
store = {};
}),
};
})();
Object.defineProperty(globalThis, "localStorage", {
value: localStorageMock,
});
describe("cytoscapeStore saveNodePosition without stored data", () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useCytoscapeStore();
localStorageMock.clear();
vi.clearAllMocks();
// Keep localStorage empty for SAVE_KEY_NAME throughout, as when
// storage is unavailable or cleared externally between saves.
localStorageMock.getItem.mockReturnValue(null);
});
it("does not duplicate a node saved twice without stored data", () => {
store.setCurrentGraphId("graph1");
store.saveNodePosition("node1", { x: 10, y: 20 }, "TB");
store.saveNodePosition("node1", { x: 30, y: 40 }, "TB");
expect(store.nodePositions["graph1"]["TB"]).toHaveLength(1);
expect(store.nodePositions["graph1"]["TB"][0]).toEqual({
id: "node1",
position: { x: 30, y: 40 },
});
});
it("adds separate entries for different nodes", () => {
store.setCurrentGraphId("graph1");
store.saveNodePosition("node1", { x: 10, y: 20 }, "TB");
store.saveNodePosition("node2", { x: 30, y: 40 }, "TB");
expect(store.nodePositions["graph1"]["TB"]).toEqual([
{ id: "node1", position: { x: 10, y: 20 } },
{ id: "node2", position: { x: 30, y: 40 } },
]);
});
});
+353
View File
@@ -0,0 +1,353 @@
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/7/5
// AI assistance: Claude Code (Anthropic)
import { describe, it, expect, beforeEach, vi } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { http, HttpResponse } from "msw";
import { server } from "@/mocks/node.js";
import { findRequest, captureRequest } from "@/mocks/request-log.js";
vi.mock("@/module/apiError.js", () => ({
default: vi.fn(),
}));
vi.mock("sweetalert2", () => ({
default: { close: vi.fn(), fire: vi.fn() },
}));
import Swal from "sweetalert2";
import {
saveFilter,
saveConformance,
leaveFilter,
leaveConformance,
} from "@/module/alertModal.js";
import { useAllMapDataStore } from "@/stores/allMapData";
import { useConformanceStore } from "@/stores/conformance";
import { usePageAdminStore } from "@/stores/pageAdmin";
/**
* Makes Swal.fire simulate a confirmed dialog. If the dialog config
* has an input validator, it is invoked with the value first, the
* same way SweetAlert2 validates the input before resolving.
*
* @param {string} value - The input value the user typed.
*/
function fireConfirmedWithInput(value) {
Swal.fire.mockImplementation(async (config) => {
if (config.inputValidator) config.inputValidator(value);
return { value, isConfirmed: true };
});
}
describe("alertModal", () => {
let pageAdminStore;
beforeEach(() => {
setActivePinia(createPinia());
pageAdminStore = usePageAdminStore();
vi.spyOn(pageAdminStore, "keepPreviousPage");
Swal.fire.mockReset();
document.cookie = "luciaToken=fake-test-token";
});
describe("saveFilter", () => {
it("prompts with the filter save dialog config", async () => {
fireConfirmedWithInput("My Filter");
await saveFilter(vi.fn());
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE NEW FILTER",
input: "text",
inputPlaceholder: "Enter Filter Name.",
icon: "info",
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
}),
);
});
it("rejects an empty input value", async () => {
fireConfirmedWithInput("My Filter");
await saveFilter(vi.fn());
const config = Swal.fire.mock.calls[0][0];
expect(config.inputValidator("")).toBe("You need to write something!");
expect(config.inputValidator("ok")).toBeUndefined();
});
it("saves via the API function and notifies on confirm", async () => {
fireConfirmedWithInput("My Filter");
const addFilterId = vi.fn();
const saved = await saveFilter(addFilterId);
expect(saved).toBe(true);
expect(addFilterId).toHaveBeenCalledWith("My Filter");
expect(Swal.fire).toHaveBeenCalledTimes(2);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
icon: "success",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Filter");
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("keeps the previous page and stops navigation on cancel", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const addFilterId = vi.fn();
const next = vi.fn();
const saved = await saveFilter(addFilterId, next);
expect(saved).toBe(false);
expect(addFilterId).not.toHaveBeenCalled();
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(false);
});
it("cancels without a next() callback", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const saved = await saveFilter(vi.fn());
expect(saved).toBe(false);
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
});
});
describe("saveConformance", () => {
it("prompts with the rule save dialog config", async () => {
fireConfirmedWithInput("My Rule");
await saveConformance(vi.fn());
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE NEW RULE",
input: "text",
inputPlaceholder: "Enter Rule Name.",
icon: "info",
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
}),
);
});
it("saves via the API function and notifies on confirm", async () => {
fireConfirmedWithInput("My Rule");
const addCheckId = vi.fn();
const saved = await saveConformance(addCheckId);
expect(saved).toBe(true);
expect(addCheckId).toHaveBeenCalledWith("My Rule");
expect(Swal.fire).toHaveBeenCalledTimes(2);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
icon: "success",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Rule");
});
it("returns false on cancel without touching the page admin", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const addCheckId = vi.fn();
const saved = await saveConformance(addCheckId);
expect(saved).toBe(false);
expect(addCheckId).not.toHaveBeenCalled();
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
});
describe("leaveFilter", () => {
let allMapDataStore;
beforeEach(() => {
allMapDataStore = useAllMapDataStore();
});
it("shows the leave confirmation dialog config", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "esc" });
await leaveFilter(vi.fn(), vi.fn(), "/files");
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE YOUR FILTER?",
html: "If you want to continue using this filter in any other page, please select [Yes].",
icon: "warning",
confirmButtonText: "Yes",
cancelButtonText: "No",
confirmButtonColor: "#FF3366",
showCancelButton: true,
}),
);
});
it("updates an existing filter and navigates on confirm", async () => {
server.use(
http.put("/api/filters/:id", ({ params }) => {
captureRequest("PUT", `/api/filters/${params.id}`);
return HttpResponse.json({});
}),
);
allMapDataStore.createFilterId = 5;
allMapDataStore.filterName = "My Filter";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(findRequest("PUT", "/api/filters/5")).toHaveLength(1);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Filter");
expect(next).toHaveBeenCalledWith("/files");
});
it("prompts to save a new filter and navigates on confirm", async () => {
Swal.fire.mockImplementationOnce(async () => ({ isConfirmed: true }));
Swal.fire.mockImplementationOnce(async (config) => {
config.inputValidator("Fresh Filter");
return { value: "Fresh Filter", isConfirmed: true };
});
const addFilterId = vi.fn();
const next = vi.fn();
await leaveFilter(next, addFilterId, "/files");
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE NEW FILTER",
});
expect(addFilterId).toHaveBeenCalledWith("Fresh Filter");
expect(next).toHaveBeenCalledWith("/files");
});
it("calls logOut instead of navigating on confirm", async () => {
server.use(http.put("/api/filters/:id", () => HttpResponse.json({})));
allMapDataStore.createFilterId = 5;
allMapDataStore.filterName = "My Filter";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
const logOut = vi.fn();
await leaveFilter(next, vi.fn(), "/files", logOut);
expect(logOut).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
});
it("discards the filter and navigates on cancel", async () => {
allMapDataStore.tempFilterId = 42;
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(allMapDataStore.tempFilterId).toBeNull();
expect(next).toHaveBeenCalledWith("/files");
});
it("stays on the page on backdrop dismiss", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(false);
});
it("does nothing extra on backdrop dismiss when logging out", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
const logOut = vi.fn();
await leaveFilter(next, vi.fn(), "/files", logOut);
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
expect(logOut).not.toHaveBeenCalled();
});
});
describe("leaveConformance", () => {
let conformanceStore;
beforeEach(() => {
conformanceStore = useConformanceStore();
});
it("shows the leave confirmation dialog config", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "esc" });
await leaveConformance(vi.fn(), vi.fn(), "/files");
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE YOUR RULE?",
icon: "warning",
confirmButtonText: "Yes",
cancelButtonText: "No",
confirmButtonColor: "#FF3366",
showCancelButton: true,
}),
);
expect(Swal.fire.mock.calls[0][0].html).toBeUndefined();
});
it("updates an existing rule without navigating on confirm", async () => {
server.use(
http.put("/api/filter-checks/:id", ({ params }) => {
captureRequest("PUT", `/api/filter-checks/${params.id}`);
return HttpResponse.json({});
}),
);
conformanceStore.conformanceFilterCreateCheckId = 7;
conformanceStore.conformanceFileName = "My Rule";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(findRequest("PUT", "/api/filter-checks/7")).toHaveLength(1);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Rule");
expect(next).not.toHaveBeenCalled();
});
it("prompts to save a new rule without navigating on confirm", async () => {
Swal.fire.mockImplementationOnce(async () => ({ isConfirmed: true }));
Swal.fire.mockImplementationOnce(async (config) => {
config.inputValidator("Fresh Rule");
return { value: "Fresh Rule", isConfirmed: true };
});
const addCheckId = vi.fn();
const next = vi.fn();
await leaveConformance(next, addCheckId, "/files");
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE NEW RULE",
});
expect(addCheckId).toHaveBeenCalledWith("Fresh Rule");
expect(next).not.toHaveBeenCalled();
});
it("resets temporary check IDs and navigates on cancel", async () => {
conformanceStore.conformanceFilterTempCheckId = 3;
conformanceStore.conformanceLogTempCheckId = 4;
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(conformanceStore.conformanceFilterTempCheckId).toBeNull();
expect(conformanceStore.conformanceLogTempCheckId).toBeNull();
expect(next).toHaveBeenCalledWith("/files");
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("calls logOut instead of navigating on cancel", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
const logOut = vi.fn();
await leaveConformance(next, vi.fn(), "/files", logOut);
expect(logOut).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
});
it("stays on the page on backdrop dismiss", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(next).toHaveBeenCalledWith(false);
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("does nothing on backdrop dismiss when logging out", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
const logOut = vi.fn();
await leaveConformance(next, vi.fn(), "/files", logOut);
expect(next).not.toHaveBeenCalled();
expect(logOut).not.toHaveBeenCalled();
});
});
});
+23
View File
@@ -0,0 +1,23 @@
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/07/04
// AI assistance: Claude Code (Anthropic)
import { describe, it, expect, vi } from "vitest";
import apiError from "@/module/apiError.js";
const toastDefault = vi.fn();
vi.mock("vue-toast-notification", () => ({
useToast: () => ({ default: toastDefault }),
}));
describe("apiError", () => {
it("shows the message as a toast at the bottom", () => {
apiError("some message");
expect(toastDefault).toHaveBeenCalledWith("some message", {
position: "bottom",
});
});
});
+61
View File
@@ -0,0 +1,61 @@
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/7/5
// AI assistance: Claude Code (Anthropic)
import { describe, it, expect } from "vitest";
import formatDuration, { pickDurationUnit } from "@/module/formatDuration.js";
describe("formatDuration", () => {
it("formats a full cascade with the default separator", () => {
expect(formatDuration(93784)).toBe("1d 2h 3m 4s");
});
it("omits zero-valued units", () => {
expect(formatDuration(86400)).toBe("1d");
expect(formatDuration(3661)).toBe("1h 1m 1s");
});
it("returns an empty string when no unit qualifies", () => {
expect(formatDuration(0)).toBe("");
expect(formatDuration(NaN)).toBe("");
});
it("supports a custom separator", () => {
expect(formatDuration(90061, { separator: "" })).toBe("1d1h1m1s");
});
it("always appends seconds when alwaysSeconds is set", () => {
expect(formatDuration(0, { alwaysSeconds: true })).toBe("0s");
expect(formatDuration(60, { separator: "", alwaysSeconds: true })).toBe(
"1m0s",
);
});
it("truncates to maxUnits largest units", () => {
expect(formatDuration(93784, { maxUnits: 2 })).toBe("1d 2h");
expect(formatDuration(45, { maxUnits: 2 })).toBe("45s");
});
});
describe("pickDurationUnit", () => {
it("picks the largest unit reached by the value", () => {
expect(pickDurationUnit(86400).symbol).toBe("d");
expect(pickDurationUnit(3600).symbol).toBe("h");
expect(pickDurationUnit(60).symbol).toBe("m");
expect(pickDurationUnit(59).symbol).toBe("s");
});
it("requires strictly more than one unit when exclusive", () => {
expect(pickDurationUnit(86400, true).symbol).toBe("h");
expect(pickDurationUnit(86401, true).symbol).toBe("d");
expect(pickDurationUnit(60, true).symbol).toBe("s");
expect(pickDurationUnit(61, true).symbol).toBe("m");
});
it("falls back to seconds for zero and negative values", () => {
expect(pickDurationUnit(0).symbol).toBe("s");
expect(pickDurationUnit(-100).symbol).toBe("s");
});
});
-8
View File
@@ -46,9 +46,7 @@ export default defineConfig(({ mode }) => {
base: "/", base: "/",
resolve: { resolve: {
alias: { alias: {
util: "util/",
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),
"/images": "src/assets/images",
}, },
}, },
server: { server: {
@@ -61,13 +59,7 @@ export default defineConfig(({ mode }) => {
port: 4173, port: 4173,
proxy, proxy,
}, },
optimizeDeps: {
include: ["vue", "vue-router", "pinia", "axios"],
},
build: { build: {
commonjsOptions: {
transformMixedEsModules: true, // Enable @walletconnect/web3-provider which has some code in CommonJS
},
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {