Conformance: Have activity More done.
This commit is contained in:
3792
package-lock.json
generated
3792
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@
|
|||||||
"cytoscape-dagre": "^2.5.0",
|
"cytoscape-dagre": "^2.5.0",
|
||||||
"cytoscape-popper": "^2.0.0",
|
"cytoscape-popper": "^2.0.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
"javascript-color-gradient": "^2.4.4",
|
"javascript-color-gradient": "^2.4.4",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/test-utils": "^2.2.6",
|
"@vue/test-utils": "^2.2.6",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"cypress": "^12.13.0",
|
"cypress": "^4.2.0",
|
||||||
"cypress-xpath": "^2.0.1",
|
"cypress-xpath": "^2.0.1",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
<td class="p-2 text-right truncate">{{ trace.count }}</td>
|
<td class="p-2 text-right truncate">{{ trace.count }}</td>
|
||||||
<td class="p-2 text-center">{{ trace.ratio }}%</td>
|
<td class="p-2 text-center">{{ trace.ratio }}%</td>
|
||||||
<td class="p-2 text-center">
|
<td class="p-2 text-center">
|
||||||
<div class="btn btn-sm btn-c-primary cursor-pointer" @click="issusModal = true">More</div>
|
<div class="btn btn-sm btn-c-primary cursor-pointer" @click="openMore(trace.no)">More</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -127,14 +127,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<MoreModal :issusModal="issusModal" @update:visible="issusModal = $event"></MoreModal>
|
<MoreModal :issusModal="issusModal" @update:visible="issusModal = $event" :issueTraces="issueTraces" :issusNo="issusNo" ref="moreModal"></MoreModal>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import iconNA from '@/components/icons/IconNA.vue';
|
|
||||||
import MoreModal from './MoreModal.vue';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import ConformanceStore from '@/stores/conformance.js';
|
import ConformanceStore from '@/stores/conformance.js';
|
||||||
|
import iconNA from '@/components/icons/IconNA.vue';
|
||||||
|
import MoreModal from './MoreModal.vue';
|
||||||
import getNumberLabel from '@/module/numberLabel.js';
|
import getNumberLabel from '@/module/numberLabel.js';
|
||||||
import { setLineChartData, setBarChartData } from '@/module/setChartData.js';
|
import { setLineChartData, setBarChartData } from '@/module/setChartData.js';
|
||||||
import abbreviateNumber from '@/module/abbreviateNumber.js';
|
import abbreviateNumber from '@/module/abbreviateNumber.js';
|
||||||
@@ -143,9 +143,9 @@ import getMoment from 'moment';
|
|||||||
export default {
|
export default {
|
||||||
setup() {
|
setup() {
|
||||||
const conformanceStore = ConformanceStore();
|
const conformanceStore = ConformanceStore();
|
||||||
const { conformanceTempReportData } = storeToRefs(conformanceStore);
|
const { conformanceTempReportData, issueTraces, taskSeq, } = storeToRefs(conformanceStore);
|
||||||
|
|
||||||
return { conformanceTempReportData, conformanceStore }
|
return { conformanceTempReportData, issueTraces, taskSeq, conformanceStore }
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -179,6 +179,7 @@ export default {
|
|||||||
rateChartOptions: null,
|
rateChartOptions: null,
|
||||||
casesChartData: null,
|
casesChartData: null,
|
||||||
casesChartOptions: null,
|
casesChartOptions: null,
|
||||||
|
issusNo: null,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
results: {
|
results: {
|
||||||
value: 'This page will perform a conformance check based on the filtering results of the map.',
|
value: 'This page will perform a conformance check based on the filtering results of the map.',
|
||||||
@@ -226,6 +227,14 @@ export default {
|
|||||||
convertSecToDay(sec) {
|
convertSecToDay(sec) {
|
||||||
return (sec / 86400)
|
return (sec / 86400)
|
||||||
},
|
},
|
||||||
|
async openMore(no) {
|
||||||
|
// async await 解決非同步資料延遲傳遞導致未讀取到而出錯的問題
|
||||||
|
this.issusNo = no;
|
||||||
|
await this.conformanceStore.getLogConformanceIssue(no);
|
||||||
|
await this.conformanceStore.getLogConformanceTraceDetail(no, this.issueTraces[0]);
|
||||||
|
this.issusModal = await true;
|
||||||
|
await this.$refs.moreModal.createCy()
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* set conformance report data
|
* set conformance report data
|
||||||
* @param {object} data new watch's value
|
* @param {object} data new watch's value
|
||||||
|
|||||||
@@ -23,13 +23,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(trace, key) in traceList" :key="key" class=" cursor-pointer hover:text-primary" @click="switchCaseData(trace.id)">
|
<tr v-for="(trace, key) in traceList" :key="key" class=" cursor-pointer hover:text-primary" @click="switchCaseData(trace.id)">
|
||||||
<td class="p-2">#{{ trace.id }}</td>
|
<td class="p-2">#{{ trace.id }}</td>
|
||||||
<td class="p-2 w-24">
|
<!-- <td class="p-2 w-24">
|
||||||
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
||||||
<div class="h-full bg-primary" :style="progressWidth(trace.value)"></div>
|
<div class="h-full bg-primary" :style="progressWidth(trace.value)"></div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 text-right">{{ trace.count }}</td>
|
<td class="py-2 text-right">{{ trace.count }}</td>
|
||||||
<td class="p-2">{{ trace.ratio }}%</td>
|
<td class="p-2">{{ trace.ratio }}%</td> -->
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -40,14 +40,14 @@
|
|||||||
<p class="h2 mb-2 px-4">Trace #{{ showTraceId }}</p>
|
<p class="h2 mb-2 px-4">Trace #{{ showTraceId }}</p>
|
||||||
<div class="h-52 w-full px-2 mb-2 border border-neutral-300 rounded">
|
<div class="h-52 w-full px-2 mb-2 border border-neutral-300 rounded">
|
||||||
<div class="h-full w-full">
|
<div class="h-full w-full">
|
||||||
<div id="cyTrace" ref="cyTrace" class="h-full min-w-full relative"></div>
|
<div id="cfmTrace" ref="cfmTrace" class="h-full min-w-full relative"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto overflow-x-auto scrollbar h-[calc(100%_-_264px)]">
|
<div class="overflow-y-auto overflow-x-auto scrollbar h-[calc(100%_-_264px)]">
|
||||||
<DataTable :value="cases" showGridlines tableClass="text-sm" breakpoint="0">
|
<DataTable :value="caseData" showGridlines tableClass="text-sm" breakpoint="0">
|
||||||
<Column field="id" header="Case ID" sortable></Column>
|
<div v-for="(col, index) in columnData" :key="index">
|
||||||
<Column field="started_at" header="Start time" sortable></Column>
|
<Column :field="col.field" :header="col.header" sortable></Column>
|
||||||
<Column field="completed_at" header="End time" sortable></Column>
|
</div>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -55,44 +55,62 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import ConformanceStore from '@/stores/conformance.js';
|
||||||
|
import cytoscapeMapTrace from '@/module/cytoscapeMapTrace.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['issusModal'],
|
props: ['issusModal', 'issusNo'],
|
||||||
|
setup() {
|
||||||
|
const conformanceStore = ConformanceStore();
|
||||||
|
const { issueTraces, taskSeq, cases } = storeToRefs(conformanceStore);
|
||||||
|
|
||||||
|
return { issueTraces, taskSeq, cases, conformanceStore }
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
contentClass: '!bg-neutral-100 border-t border-neutral-300 h-full',
|
contentClass: '!bg-neutral-100 border-t border-neutral-300 h-full',
|
||||||
traceList:[
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
value: 80,
|
|
||||||
count: 1234567890,
|
|
||||||
ratio: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
value: 80,
|
|
||||||
count: 4567890,
|
|
||||||
ratio: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
showTraceId: 1,
|
showTraceId: 1,
|
||||||
cases:[
|
processMap:{
|
||||||
{
|
nodes:[],
|
||||||
id: 1,
|
edges:[],
|
||||||
started_at: '2222/11/11',
|
|
||||||
completed_at: '2222/11/11'
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
started_at: '2222/11/11',
|
|
||||||
completed_at: '2222/11/11'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
traceTotal: function() {
|
traceTotal: function() {
|
||||||
return this.traceList.length;
|
return this.traceList.length;
|
||||||
},
|
},
|
||||||
|
traceList: function() {
|
||||||
|
return this.issueTraces.map(trace => {
|
||||||
|
return {
|
||||||
|
id: trace,
|
||||||
|
// value: Number((trace.ratio * 100).toFixed(1)),
|
||||||
|
// count: trace.count,
|
||||||
|
// ratio: this.getPercentLabel(trace.count / this.traceCountTotal),
|
||||||
|
};
|
||||||
|
}).sort((x, y) => x.id - y.id);
|
||||||
|
},
|
||||||
|
caseData: function() {
|
||||||
|
const data = JSON.parse(JSON.stringify(this.cases)); // 深拷貝原始 cases 的內容
|
||||||
|
data.forEach(item => {
|
||||||
|
item.attributes.forEach((attribute, index) => {
|
||||||
|
item[`att_${index}`] = attribute.value; // 建立新的 key-value pair
|
||||||
|
});
|
||||||
|
delete item.attributes; // 刪除原本的 attributes 屬性
|
||||||
|
})
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
columnData: function() {
|
||||||
|
const data = JSON.parse(JSON.stringify(this.cases)); // 深拷貝原始 cases 的內容
|
||||||
|
const result = [
|
||||||
|
{ field: 'id', header: 'ID' },
|
||||||
|
{ field: 'started_at', header: 'Start Date' },
|
||||||
|
{ field: 'completed_at', header: 'End Date' },
|
||||||
|
...data[0].attributes.map((att, index) => ({ field: `att_${index}`, header: att.key })),
|
||||||
|
];
|
||||||
|
return result
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
@@ -107,10 +125,66 @@ export default {
|
|||||||
* switch case data
|
* switch case data
|
||||||
* @param {number} id
|
* @param {number} id
|
||||||
*/
|
*/
|
||||||
async switchCaseData(id) {
|
switchCaseData(id) {
|
||||||
this.showTraceId = id;
|
this.showTraceId = id;
|
||||||
this.$emit('switch-Trace-Id', this.showTraceId);
|
this.conformanceStore.getLogConformanceTraceDetail(this.issusNo, id)
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 將 trace element nodes 資料彙整
|
||||||
|
*/
|
||||||
|
setNodesData(){
|
||||||
|
// 避免每次渲染都重複累加
|
||||||
|
this.processMap.nodes = [];
|
||||||
|
// 將 api call 回來的資料帶進 node
|
||||||
|
this.taskSeq.forEach((node, index) => {
|
||||||
|
this.processMap.nodes.push({
|
||||||
|
data: {
|
||||||
|
id: index,
|
||||||
|
label: node,
|
||||||
|
backgroundColor: '#CCE5FF',
|
||||||
|
bordercolor: '#003366',
|
||||||
|
shape: 'round-rectangle',
|
||||||
|
height: 80,
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 將 trace edge line 資料彙整
|
||||||
|
*/
|
||||||
|
setEdgesData(){
|
||||||
|
this.processMap.edges = [];
|
||||||
|
this.taskSeq.forEach((edge, index) => {
|
||||||
|
this.processMap.edges.push({
|
||||||
|
data: {
|
||||||
|
source: `${index}`,
|
||||||
|
target: `${index + 1}`,
|
||||||
|
lineWidth: 1,
|
||||||
|
style: 'solid'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 關係線數量筆節點少一個
|
||||||
|
this.processMap.edges.pop();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* create trace cytoscape's map
|
||||||
|
*/
|
||||||
|
createCy(){
|
||||||
|
this.$nextTick(() => {
|
||||||
|
let graphId = this.$refs.cfmTrace;
|
||||||
|
|
||||||
|
this.setNodesData();
|
||||||
|
this.setEdgesData();
|
||||||
|
cytoscapeMapTrace(this.processMap.nodes, this.processMap.edges, graphId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
taskSeq: function(newValue){
|
||||||
|
if(newValue !== null) this.createCy();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import loadingStore from "./loading";
|
|||||||
import pinia from '@/stores/main.js';
|
import pinia from '@/stores/main.js';
|
||||||
import {useToast} from 'vue-toast-notification';
|
import {useToast} from 'vue-toast-notification';
|
||||||
import 'vue-toast-notification/dist/theme-sugar.css';
|
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||||
|
import moment from "moment";
|
||||||
|
import { Decimal } from 'decimal.js';
|
||||||
|
|
||||||
const loading = loadingStore(pinia);
|
const loading = loadingStore(pinia);
|
||||||
const $toast = useToast();
|
const $toast = useToast();
|
||||||
@@ -13,10 +15,13 @@ let delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s))
|
|||||||
export default defineStore('conformanceStore', {
|
export default defineStore('conformanceStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
conformanceLogId: null,
|
conformanceLogId: null,
|
||||||
conformanceTempCheckerId: null,
|
|
||||||
conformanceFilterId: null,
|
conformanceFilterId: null,
|
||||||
|
conformanceTempCheckerId: null,
|
||||||
allConformanceTask: [],
|
allConformanceTask: [],
|
||||||
allConformanceTempReportData: null,
|
allConformanceTempReportData: null,
|
||||||
|
allIssueTraces: null,
|
||||||
|
allTaskSeq: null,
|
||||||
|
allCases: null,
|
||||||
selectedRuleType: 'Have activity', // radio
|
selectedRuleType: 'Have activity', // radio
|
||||||
selectedActivitySequence: 'Start & End', // radio
|
selectedActivitySequence: 'Start & End', // radio
|
||||||
selectedMode: 'Directly follows', // radio
|
selectedMode: 'Directly follows', // radio
|
||||||
@@ -30,7 +35,36 @@ export default defineStore('conformanceStore', {
|
|||||||
},
|
},
|
||||||
conformanceTempReportData: state => {
|
conformanceTempReportData: state => {
|
||||||
return state.allConformanceTempReportData;
|
return state.allConformanceTempReportData;
|
||||||
|
},
|
||||||
|
issueTraces: state => {
|
||||||
|
return state.allIssueTraces;
|
||||||
|
},
|
||||||
|
taskSeq: state => {
|
||||||
|
return state.allTaskSeq;
|
||||||
|
},
|
||||||
|
cases: state => {
|
||||||
|
if(state.allCases !== null){
|
||||||
|
state.allCases.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');
|
||||||
|
c.attributes.map(att => {
|
||||||
|
switch (att.type) {
|
||||||
|
case 'date':
|
||||||
|
att.value = att.value !== null ? moment(att.value).format('YYYY/MM/DD HH:MM:ss') : null;
|
||||||
|
break;
|
||||||
|
case 'float':
|
||||||
|
att.value = att.value !== null ? new Decimal(att.value).toFixed(2) : null;
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
return att;
|
||||||
|
})
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return state.allCases;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
@@ -91,5 +125,46 @@ export default defineStore('conformanceStore', {
|
|||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get the detail of a temporary log conformance issue.
|
||||||
|
*/
|
||||||
|
async getLogConformanceIssue(issueNo) {
|
||||||
|
let checkerId = this.conformanceTempCheckerId;
|
||||||
|
const api = `/api/temp-log-checkers/${checkerId}/issues/${issueNo}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.$axios.get(api);
|
||||||
|
this.allIssueTraces = response.data.traces;
|
||||||
|
} catch(error) {
|
||||||
|
await delay();
|
||||||
|
loading.isLoading = true;
|
||||||
|
await delay(1000);
|
||||||
|
loading.isLoading = false;
|
||||||
|
await delay(500);
|
||||||
|
$toast.default('Failed to Get the detail of a temporary log conformance issue.',{position: 'bottom'});
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the Trace Details of a Temporary Log Conformance lssue.
|
||||||
|
*/
|
||||||
|
async getLogConformanceTraceDetail(issueNo, traceId) {
|
||||||
|
let checkerId = this.conformanceTempCheckerId;
|
||||||
|
const api = `/api/temp-log-checkers/${checkerId}/issues/${issueNo}/traces/${traceId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.$axios.get(api);
|
||||||
|
this.allTaskSeq = response.data.task_seq;
|
||||||
|
this.allCases = response.data.cases;
|
||||||
|
} catch(error) {
|
||||||
|
await delay();
|
||||||
|
loading.isLoading = true;
|
||||||
|
await delay(1000);
|
||||||
|
loading.isLoading = false;
|
||||||
|
await delay(500);
|
||||||
|
$toast.default('Failed to Get the detail of a temporary log conformance issue.',{position: 'bottom'});
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user