From c10cf99b756511140f0f4d4e4df9fa55b4123381 Mon Sep 17 00:00:00 2001 From: chiayin Date: Mon, 27 Mar 2023 13:15:50 +0800 Subject: [PATCH] Discover: sideBar trace done. --- .gitignore | 3 + package-lock.json | 63 +++- package.json | 2 + src/components/Discover/sidebarState.vue | 26 +- src/components/Discover/sidebarTraces.vue | 350 +++++++++------------- src/main.js | 2 + src/module/cytoscapeMap.js | 4 +- src/module/cytoscapeMapTrace.js | 105 +++++++ src/stores/allMapData.js | 79 ++++- src/stores/files.js | 2 +- src/views/Discover/index.vue | 58 ++-- src/views/MainContainer.vue | 3 +- vite.config.js | 1 + 13 files changed, 441 insertions(+), 257 deletions(-) create mode 100644 src/module/cytoscapeMapTrace.js diff --git a/.gitignore b/.gitignore index eb5c088..78f619b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ vscode .env.demo .env.local .env.*.local + +.scannerwork +sonar-project.properties diff --git a/package-lock.json b/package-lock.json index bf43de7..bb62828 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.2.2", "cytoscape": "^3.23.0", "cytoscape-dagre": "^2.5.0", + "cytoscape-popper": "^2.0.0", "javascript-color-gradient": "^2.4.4", "mitt": "^3.0.0", "moment": "^2.29.4", @@ -20,6 +21,7 @@ "primeicons": "^6.0.1", "primevue": "^3.23.0", "tailwindcss": "^3.2.4", + "tippy.js": "^6.3.7", "vue": "^3.2.45", "vue-axios": "^3.5.2", "vue-router": "^4.1.6", @@ -631,6 +633,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -2070,6 +2081,17 @@ "cytoscape": "^3.2.22" } }, + "node_modules/cytoscape-popper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cytoscape-popper/-/cytoscape-popper-2.0.0.tgz", + "integrity": "sha512-b7WSOn8qXHWtdIXFNmrgc8qkaOs16tMY0EwtRXlxzvn8X+al6TAFrUwZoYATkYSlotfd/36ZMoeKMEoUck6feA==", + "dependencies": { + "@popperjs/core": "^2.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, "node_modules/dagre": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", @@ -5603,6 +5625,14 @@ "node": ">=14.0.0" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -6045,9 +6075,9 @@ } }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", + "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", "dev": true, "peer": true, "dependencies": { @@ -6634,6 +6664,11 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" + }, "@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -7779,6 +7814,14 @@ "dagre": "^0.8.5" } }, + "cytoscape-popper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cytoscape-popper/-/cytoscape-popper-2.0.0.tgz", + "integrity": "sha512-b7WSOn8qXHWtdIXFNmrgc8qkaOs16tMY0EwtRXlxzvn8X+al6TAFrUwZoYATkYSlotfd/36ZMoeKMEoUck6feA==", + "requires": { + "@popperjs/core": "^2.0.0" + } + }, "dagre": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", @@ -10340,6 +10383,14 @@ "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", "dev": true }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -10622,9 +10673,9 @@ "dev": true }, "webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", + "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", "dev": true, "peer": true, "requires": { diff --git a/package.json b/package.json index 6d6c8c7..29221d5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "axios": "^1.2.2", "cytoscape": "^3.23.0", "cytoscape-dagre": "^2.5.0", + "cytoscape-popper": "^2.0.0", "javascript-color-gradient": "^2.4.4", "mitt": "^3.0.0", "moment": "^2.29.4", @@ -26,6 +27,7 @@ "primeicons": "^6.0.1", "primevue": "^3.23.0", "tailwindcss": "^3.2.4", + "tippy.js": "^6.3.7", "vue": "^3.2.45", "vue-axios": "^3.5.2", "vue-router": "^4.1.6", diff --git a/src/components/Discover/sidebarState.vue b/src/components/Discover/sidebarState.vue index 7c175aa..e12fccf 100644 --- a/src/components/Discover/sidebarState.vue +++ b/src/components/Discover/sidebarState.vue @@ -15,7 +15,7 @@
{{ numberLabel(stats.cases.count) }} / {{ numberLabel(stats.cases.total) }} - +
{{ getPercentLabel(stats.cases.ratio) }}
@@ -25,7 +25,7 @@
{{ numberLabel(stats.traces.count) }} / {{ numberLabel(stats.traces.total) }} - +
{{ getPercentLabel(stats.traces.ratio) }}
@@ -35,7 +35,7 @@
{{ numberLabel(stats.task_instances.count) }} / {{ numberLabel(stats.task_instances.total) }} - +
{{ getPercentLabel(stats.task_instances.ratio) }}
@@ -45,7 +45,7 @@
{{ numberLabel(stats.tasks.count) }} / {{ numberLabel(stats.tasks.total) }} - +
{{ getPercentLabel(stats.tasks.ratio) }}
@@ -116,9 +116,8 @@

No data

@@ -126,7 +125,7 @@

No data

@@ -194,36 +193,37 @@ export default { valueTaskInstances: 0, valueTasks: 0, active1: 0, - nnnstats:this.stats } }, methods: { /** - * switch Summary or Insight + * @param {string} switch Summary or Insight */ switchTab(tab) { this.tab = tab; }, /** - * use timeLabel.js + * @param {number} time use timeLabel.js */ timeLabel(time){ return getTimeLabel(time); }, /** - * use moment + * @param {number} time use moment */ moment(time){ return getMoment(time).format('YYYY-MM-DD HH:mm'); }, /** - * use numberLabel.js + * @param {number} num use numberLabel.js */ numberLabel(num){ return getNumberLabel(num); }, /** * Number to percentage + * @param {number} val + * @returns {string} 轉換完成的百分比字串 */ getPercentLabel(val){ if(val * 100 === 100) return `${val * 100}%`; diff --git a/src/components/Discover/sidebarTraces.vue b/src/components/Discover/sidebarTraces.vue index 657f1b5..e14801a 100644 --- a/src/components/Discover/sidebarTraces.vue +++ b/src/components/Discover/sidebarTraces.vue @@ -1,12 +1,12 @@ diff --git a/src/main.js b/src/main.js index ec11fdf..ecb24a2 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,7 @@ import mitt from 'mitt'; import ToastPlugin from 'vue-toast-notification'; import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; +import popper from 'cytoscape-popper'; // import CSS import "./assets/main.css"; @@ -50,6 +51,7 @@ app.config.globalProperties.$cytoscape = cytoscape; // Cytoscape.js's style cytoscape.use( dagre ); +cytoscape.use( popper ); app.use(pinia); app.use(router); diff --git a/src/module/cytoscapeMap.js b/src/module/cytoscapeMap.js index ebf246a..f457a15 100644 --- a/src/module/cytoscapeMap.js +++ b/src/module/cytoscapeMap.js @@ -1,5 +1,7 @@ import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; +// import tippy from 'tippy.js'; +// import 'tippy.js/dist/tippy.css'; import Gradient from 'javascript-color-gradient'; // 多個色階產生器 import TimeLabel from '@/module/timeLabel.js'; // 時間格式轉換器 @@ -116,7 +118,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu text = Math.trunc(optionValue) === optionValue ? textInt : textFloat; break; - case 'duration': // Duration 除了 Relative 為百分比 % ,其他要轉變時間單位。 + case 'duration': // Duration 除了 Relative 為百分比 % ,其他要轉變時間單位。 // Relative % let textDurRel = text + (optionValue * 100).toFixed(2) + "%"; // Timelabel diff --git a/src/module/cytoscapeMapTrace.js b/src/module/cytoscapeMapTrace.js new file mode 100644 index 0000000..4b1ada2 --- /dev/null +++ b/src/module/cytoscapeMapTrace.js @@ -0,0 +1,105 @@ +import cytoscape from 'cytoscape'; +import dagre from 'cytoscape-dagre'; +import tippy from 'tippy.js'; +import 'tippy.js/dist/tippy.css'; + +cytoscape.use( dagre ); + +/** + * draw processmap for trace + * @param {array} nodes array of an object, it contains + * data:{ + * backgroundColor: string + * bordercolor: string + * height: number + * id: number + * label: string + * shape: string + * width: number + * } + * @param {array} edges it's similar to nodes + * @param {string} graphId + */ +export default function cytoscapeMapTrace(nodes, edges, graphId) { + // create Cytoscape + let cy = cytoscape({ + container: graphId, + elements: { + nodes: nodes, // 節點的資料 + edges: edges, // 關係線的資料 + }, + layout: { + name: 'dagre', + rankDir: 'LR' // 直向 TB | 橫向 LR, 'cytoscape-dagre' 套件裡的變數 + }, + style: [ + // node 節點的樣式 + { + selector: 'node', + style: { + 'label': + function(node) { // 節點要顯示的文字 + let text = ''; + + // node.data('label') 為原先陣列 node.data.label + text = node.data('label').length > 18 ? `${node.data('label').substr(0,15)}...` : `${node.data('label')}`; + + return text + }, + 'text-opacity': 0.7, + 'background-color': 'data(backgroundColor)', + 'border-color': 'data(bordercolor)', + 'border-width': '1', + 'shape': 'data(shape)', + 'text-wrap': 'wrap', + 'text-max-width': 75, + 'text-halign': 'center', + 'text-valign': 'center', + 'height': 'data(height)', + 'width': 'data(width)', + 'color': '#001933', + 'font-size': 14, + } + }, + // edge 關係線的樣式 + { + selector: 'edge', + style: { + 'curve-style': 'taxi', // unbundled-bezier | taxi + 'target-arrow-shape': 'triangle', // 指向目標的箭頭形狀: 三角形 + 'color': 'gray', //#0066cc + 'width': 'data(lineWidth)', + 'line-style': 'data(style)', + } + }, + // 點擊 node 後改變的樣式 + { + selector: 'node:selected', + style:{ + 'border-color': 'red', + 'border-width': '3', + } + }, + ], + }); + + // creat tippy.js + let tip; + cy.on('mouseover', 'node', function(event) { + var node = event.target + let ref = node.popperRef() + let dummyDomEle = document.createElement('div'); + let content = document.createElement('div'); + content.innerHTML = node.data("label") + tip = new tippy(dummyDomEle, { // tippy props: + getReferenceClientRect: ref.getBoundingClientRect, + trigger: 'manual', + content:content + }); + tip.show(); + // if(node.data("label").length > 18) tip.show(); + }) + cy.on('mouseout', 'node', function(event) { + tip.hide(); + }); +} diff --git a/src/stores/allMapData.js b/src/stores/allMapData.js index f6752da..b3df85f 100644 --- a/src/stores/allMapData.js +++ b/src/stores/allMapData.js @@ -3,19 +3,24 @@ import loadingStore from './loading.js'; import pinia from '@/stores/main.js' import {useToast} from 'vue-toast-notification'; import 'vue-toast-notification/dist/theme-sugar.css'; +import moment from "moment"; const loading = loadingStore(pinia); const $toast = useToast(); // Delay loading and toast 待模組化 -let delay = s => new Promise((resolve, reject) => setTimeout(resolve, s)); +let delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s)); export default defineStore('allMapDataStore', { state: () => ({ logId: null, + traceId: 1, allProcessMap: {}, allBpmn: {}, allStats: {}, allInsights: {}, + allTrace: [], + allCase: [], + allTraceTaskSeq: [], httpStatus: 200, }), getters: { @@ -30,6 +35,15 @@ export default defineStore('allMapDataStore', { }, insights: state => { return state.allInsights; + }, + traces: state => { + return state.allTrace; + }, + cases: state => { + return state.allCase; + }, + traceTaskSeq: state => { + return state.allTraceTaskSeq; } }, actions: { @@ -37,7 +51,7 @@ export default defineStore('allMapDataStore', { * fetch discover api, include '/process-map, /bpmn, /stats, /insights'. */ async getAllMapData() { - let logId = this.logId + let logId = this.logId; const api = `/api/logs/${logId}/discover`; try { @@ -57,9 +71,66 @@ export default defineStore('allMapDataStore', { loading.isLoading = false; return delay(500); }).then(() => { - $toast.default('Failed to load the discover.'); + $toast.default('Failed to load the Map.'); }) }; }, - } + /** + * fetch trace api. + */ + async getAllTrace() { + let logId = this.logId; + const api = `/api/filters/params?log_id=${logId}`; + + try { + const response = await this.$axios.get(api); + this.allTrace = response.data.traces; + + if(this.httpStatus < 300) loading.isLoading = false; + } catch(error) { + this.httpStatus = error.request.status; + delay().then(() =>{ + loading.isLoading = true; + return delay(1000); + }).then(()=>{ + loading.isLoading = false; + return delay(500); + }).then(() => { + $toast.default('Failed to load the Trace.'); + }) + } + }, + /** + * fetch trace detail api. + */ + async getTraceDetail() { + let logId = this.logId; + let traceId = this.traceId; + const api = `/api/logs/${logId}/traces/${traceId}`; + + try { + const response = await this.$axios.get(api); + this.allTraceTaskSeq = response.data.task_seq; + this.allCase = response.data.cases; + this.allCase.map(c => { + c.started_at = moment(c.started_at).format('YYYY-MM-DD HH:MM'); + c.completed_at = moment(c.completed_at).format('YYYY-MM-DD HH:MM'); + return this.allCase; + }); + + if(this.httpStatus < 300) loading.isLoading = false; + } catch(error) { + this.httpStatus = error.request.status; + delay().then(() =>{ + loading.isLoading = true; + return delay(1000); + }).then(()=>{ + loading.isLoading = false; + return delay(500); + }).then(() => { + $toast.default('Failed to load the Trace Detail.'); + }); + }; + }, + }, }) diff --git a/src/stores/files.js b/src/stores/files.js index fc9d06f..0cce635 100644 --- a/src/stores/files.js +++ b/src/stores/files.js @@ -118,5 +118,5 @@ export default defineStore('filesStore', { }) }; }, - } + }, }) diff --git a/src/views/Discover/index.vue b/src/views/Discover/index.vue index 729de9b..b385b75 100644 --- a/src/views/Discover/index.vue +++ b/src/views/Discover/index.vue @@ -7,8 +7,8 @@ track_changes -
  • - +
  • + tornado
  • @@ -30,7 +30,7 @@
    -
    +
    @@ -47,7 +47,15 @@ - + + + + + + + diff --git a/src/views/MainContainer.vue b/src/views/MainContainer.vue index 38e1212..c2705a4 100644 --- a/src/views/MainContainer.vue +++ b/src/views/MainContainer.vue @@ -5,7 +5,8 @@
    - + +
    diff --git a/vite.config.js b/vite.config.js index 0b95f07..9614e56 100644 --- a/vite.config.js +++ b/vite.config.js @@ -39,6 +39,7 @@ export default defineConfig(({ mode }) => { build: { commonjsOptions: { transformMixedEsModules: true, // Enable @walletconnect/web3-provider which has some code in CommonJS + // esmExternals: true, // If you set esmExternals to true, this plugins assumes that all external dependencies are ES modules } }, test: {