// 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 stores/conformance Conformance checking store for managing * rule definitions, conformance check results, and report data. */ import { defineStore } from "pinia"; import moment from "moment"; import { Decimal } from 'decimal.js'; import abbreviateNumber from '@/module/abbreviateNumber.js'; import apiClient from "@/api/client.js"; import apiError from '@/module/apiError.js'; /** * Returns the API base path for the current conformance check, * prioritizing temp IDs over created IDs, and filter over log. * @param {object} state - The store state. * @returns {string} The API base path. */ function getCheckApiBase(state) { const { conformanceFilterTempCheckId, conformanceLogTempCheckId, conformanceFilterCreateCheckId, conformanceLogCreateCheckId } = state; if (conformanceFilterTempCheckId !== null) return `/api/temp-filter-checks/${conformanceFilterTempCheckId}`; if (conformanceLogTempCheckId !== null) return `/api/temp-log-checks/${conformanceLogTempCheckId}`; if (conformanceFilterCreateCheckId !== null) return `/api/filter-checks/${conformanceFilterCreateCheckId}`; if (conformanceLogCreateCheckId !== null) return `/api/log-checks/${conformanceLogCreateCheckId}`; return ''; } /** * Returns the API path for conformance params/temp-checks, * prioritizing filter over log. * @param {object} state - The store state. * @returns {{prefix: string, idParam: string}} The API prefix and ID query param. */ function getFileTypeApi(state) { const { conformanceFilterId, conformanceLogId } = state; if (conformanceFilterId !== null) { return { prefix: 'filter', idParam: `filter_id=${conformanceFilterId}` }; } return { prefix: 'log', idParam: `log_id=${conformanceLogId}` }; } /** Pinia store for conformance checking and rule management. */ export const useConformanceStore = defineStore('conformanceStore', { state: () => ({ conformanceLogId: null, // log 檔 conformanceFilterId: null, // filter 檔 conformanceLogTempCheckId: null, // log 檔存檔前的 check Id conformanceFilterTempCheckId: null, // Filter 檔存檔前的 check Id conformanceLogCreateCheckId: null, // log 檔存檔後的 check Id(rule) conformanceFilterCreateCheckId: null, // Filter 檔存檔後的 check Id(rule) allConformanceTask: [], allCfmSeqStart: [], allCfmSeqEnd: [], allProcessingTime: {}, allWaitingTime: {}, allCycleTime: {}, allConformanceTempReportData: null, allRouteFile: null, allIssueTraces: null, allTaskSeq: null, allCases: null, allLoopTraces: null, allLoopTaskSeq: null, allLoopCases: null, selectedRuleType: 'Have activity', // radio selectedActivitySequence: 'Start & End', // radio selectedMode: 'Directly follows', // radio selectedProcessScope: 'End to end', // radio selectedActSeqMore: 'All', // radio selectedActSeqFromTo: 'From', // radio infinite404: null, isStartSelected: null, // start & end 連動先選擇 start isEndSelected: null, // start & end 連動先選擇 end conformanceRuleData: null, // create checkId's data to save isUpdateConformance: false, // 成功儲存後要跳 Modal conformanceFileName: null, // 儲存成功的 Modal 用 }), getters: { conformanceAllTasks: state => { return state.allConformanceTask; }, conformanceTask: state => { return state.allConformanceTask.map(i => i.label); }, cfmSeqStart: state => { return state.allCfmSeqStart; }, cfmSeqEnd: state => { return state.allCfmSeqEnd; }, cfmPtEteWhole: state => { return state.allProcessingTime.end_to_end.whole; }, cfmPtEteStart: state => { return state.allProcessingTime.end_to_end.starts_with; }, cfmPtEteEnd: state => { return state.allProcessingTime.end_to_end.ends_with; }, cfmPtEteSE: state => { return state.allProcessingTime.end_to_end.start_end; }, cfmPtPStart: state => { return state.allProcessingTime.partial.starts_with; }, cfmPtPEnd: state => { return state.allProcessingTime.partial.ends_with; }, cfmPtPSE: state => { return state.allProcessingTime.partial.start_end; }, cfmWtEteWhole: state => { return state.allWaitingTime.end_to_end.whole; }, cfmWtEteStart: state => { return state.allWaitingTime.end_to_end.starts_with; }, cfmWtEteEnd: state => { return state.allWaitingTime.end_to_end.ends_with; }, cfmWtEteSE: state => { return state.allWaitingTime.end_to_end.start_end; }, cfmWtPStart: state => { return state.allWaitingTime.partial.starts_with; }, cfmWtPEnd: state => { return state.allWaitingTime.partial.ends_with; }, cfmWtPSE: state => { return state.allWaitingTime.partial.start_end; }, cfmCtEteWhole: state => { return state.allCycleTime.end_to_end.whole; }, cfmCtEteStart: state => { return state.allCycleTime.end_to_end.starts_with; }, cfmCtEteEnd: state => { return state.allCycleTime.end_to_end.ends_with; }, cfmCtEteSE: state => { 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 => { if(state.allCases !== null){ return state.allCases.map(c => { const facets = c.facets.map(fac => { const copy = { ...fac }; switch(copy.type) { case 'dummy': //sonar-qube case 'duration-list': copy.value = copy.value.map(v => v !== null ? abbreviateNumber(new Decimal(v.toFixed(2))) : null); copy.value = (copy.value).map(v => v.trim()).join(', '); break; default: break; }; return copy; }); const attributes = c.attributes.map(att => { const copy = { ...att }; switch (copy.type) { case 'date': copy.value = copy.value !== null ? moment(copy.value).format('YYYY/MM/DD HH:mm:ss') : null; break; case 'float': copy.value = copy.value !== null ? new Decimal(copy.value).toFixed(2) : null; break default: break; } return copy; }); return { ...c, started_at: moment(c.started_at).format('YYYY/MM/DD HH:mm'), completed_at: moment(c.completed_at).format('YYYY/MM/DD HH:mm'), facets, attributes, }; }); }; }, loopTraces: state => { return state.allLoopTraces; }, loopTaskSeq: state => { return state.allLoopTaskSeq; }, loopCases: state => { if(state.allLoopCases !== null){ return state.allLoopCases.map(c => { const attributes = c.attributes.map(att => { const copy = { ...att }; switch (copy.type) { case 'date': copy.value = copy.value !== null ? moment(copy.value).format('YYYY/MM/DD HH:mm:ss') : null; break; case 'float': copy.value = copy.value !== null ? new Decimal(copy.value).toFixed(2) : null; break default: break; } return copy; }); return { ...c, started_at: moment(c.started_at).format('YYYY/MM/DD HH:mm'), completed_at: moment(c.completed_at).format('YYYY/MM/DD HH:mm'), attributes, }; }); }; }, }, actions: { /** * fetch Log Conformance Parameters api for Rule Settings. */ async getConformanceParams() { const { prefix, idParam } = getFileTypeApi(this); const api = `/api/${prefix}-checks/params?${idParam}`; try { const response = await apiClient.get(api); this.allConformanceTask = response.data.tasks; this.allCfmSeqStart = response.data.sources; this.allCfmSeqEnd = response.data.sinks; this.allProcessingTime = response.data.processing_time; this.allWaitingTime = response.data.waiting_time; this.allCycleTime = response.data.cycle_time; } catch(error) { apiError(error, 'Failed to load the Conformance Parameters.'); } }, /** * Creates a new temporary check for a log. * @param {object} data - The request payload for the backend. */ async addConformanceCheckId(data) { const { prefix, idParam } = getFileTypeApi(this); const api = `/api/temp-${prefix}-checks?${idParam}`; try { const response = await apiClient.post(api, data); if (prefix === 'filter') { this.conformanceFilterTempCheckId = response.data.id; } else { this.conformanceLogTempCheckId = response.data.id; } } catch(error) { apiError(error, 'Failed to add the Temporary Check for a file.'); } }, /** * Get the Temporary Log Conformance Report * @param {boolean} getRouteFile - Whether called to get the route file (used outside the Conformance page). */ async getConformanceReport(getRouteFile = false) { const api = getCheckApiBase(this); try { const response = await apiClient.get(api); if(!getRouteFile) { this.allConformanceTempReportData = response.data } else { this.allRouteFile = response.data.file; } } catch(error) { apiError(error, 'Failed to Get the Temporary Log Conformance Report.'); } }, /** * Get the detail of a temporary log conformance issue. * @param {number} issueNo - The issue number. */ async getConformanceIssue(issueNo) { const api = `${getCheckApiBase(this)}/issues/${issueNo}`; try { const response = await apiClient.get(api); this.allIssueTraces = response.data.traces; } catch(error) { apiError(error, 'Failed to Get the detail of a temporary log conformance issue.'); }; }, /** * Get the Trace Details of a Temporary Log Conformance lssue. * @param {number} issueNo - The issue number. * @param {number} traceId - The trace ID. * @param {number} start - The starting index for loading traces. */ async getConformanceTraceDetail(issueNo, traceId, start) { const api = `${getCheckApiBase(this)}/issues/${issueNo}/traces/${traceId}?start=${start}&page_size=20`; try { const response = await apiClient.get(api); this.allTaskSeq = response.data.task_seq; this.allCases = response.data.cases; return response.data.cases; } catch(error) { if(error.response?.status === 404) { this.infinite404 = 404; return; } apiError(error, 'Failed to Get the detail of a temporary log conformance issue.'); }; }, /** * Get the Details of a Temporary Log Conformance Loop. * @param {number} loopNo - The loop number. */ async getConformanceLoop(loopNo) { const api = `${getCheckApiBase(this)}/loops/${loopNo}`; try { const response = await apiClient.get(api); this.allLoopTraces = response.data.traces; } catch(error) { apiError(error, 'Failed to Get the detail of a temporary log conformance loop.'); }; }, /** * Get the Trace Details of a Temporary Log Conformance Loops. * @param {number} loopNo - The loop number. * @param {number} traceId - The trace ID. * @param {number} start - The starting index for loading traces. */ async getConformanceLoopsTraceDetail(loopNo, traceId, start) { const api = `${getCheckApiBase(this)}/loops/${loopNo}/traces/${traceId}?start=${start}&page_size=20`; try { const response = await apiClient.get(api); this.allLoopTaskSeq = response.data.task_seq; this.allLoopCases = response.data.cases; return response.data.cases; } catch(error) { if(error.response?.status === 404) { this.infinite404 = 404; return; } apiError(error, 'Failed to Get the detail of a temporary log conformance loop.'); }; }, /** * Add a New Log Conformance Check, Save the log file. * @param {string} value file's name */ async addConformanceCreateCheckId(value) { const { prefix, idParam } = getFileTypeApi(this); const api = `/api/${prefix}-checks?${idParam}`; const data = { name: value, rule: this.conformanceRuleData }; try { const response = await apiClient.post(api, data); if (prefix === 'filter') { this.conformanceFilterCreateCheckId = response.data.id; this.conformanceFilterTempCheckId = null; } else { this.conformanceLogCreateCheckId = response.data.id; this.conformanceLogTempCheckId = null; } }catch(error) { apiError(error, 'Failed to add the Conformance Check for a file.'); }; }, /** * Update an Existing Conformance Check, log and filter */ async updateConformance() { const api = getCheckApiBase(this); const data = this.conformanceRuleData; try { const response = await apiClient.put(api, data); this.isUpdateConformance = response.status === 200; this.conformanceLogTempCheckId = null; this.conformanceFilterTempCheckId = null; }catch(error) { apiError(error, 'Failed to update an Existing Conformance.'); } }, /** * Set the state value of conformance log creation check ID * @param {string} createCheckID */ setConformanceLogCreateCheckId(createCheckID) { this.conformanceLogCreateCheckId = createCheckID; }, }, })