// 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 file conformanceFilterId: null, // Filter file conformanceLogTempCheckId: null, // Temporary check ID for log (before saving) conformanceFilterTempCheckId: null, // Temporary check ID for filter (before saving) conformanceLogCreateCheckId: null, // Created check ID for log (rule, after saving) conformanceFilterCreateCheckId: null, // Created check ID for filter (rule, after saving) 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, // Whether start is selected in linked start & end selection isEndSelected: null, // Whether end is selected in linked start & end selection conformanceRuleData: null, // create checkId's data to save isUpdateConformance: false, // Show modal after successful save conformanceFileName: null, // File name displayed in the save success modal }), getters: { conformanceTask: (state) => { return state.allConformanceTask.map((i) => i.label); }, 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; }, 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 ? null : abbreviateNumber(new Decimal(v.toFixed(2))), ); 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 ? null : moment(copy.value).format("YYYY/MM/DD HH:mm:ss"); break; case "float": copy.value = copy.value === null ? null : new Decimal(copy.value).toFixed(2); 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, }; }); } return []; }, 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 ? null : moment(copy.value).format("YYYY/MM/DD HH:mm:ss"); break; case "float": copy.value = copy.value === null ? null : new Decimal(copy.value).toFixed(2); 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, }; }); } return []; }, }, 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("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("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.allRouteFile = response.data.file; } else { this.allConformanceTempReportData = response.data; } } catch (error) { apiError("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( "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 null; } apiError( "Failed to Get the detail of a temporary log conformance issue.", ); return null; } }, /** * 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( "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 null; } apiError( "Failed to Get the detail of a temporary log conformance loop.", ); return null; } }, /** * 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("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("Failed to update an Existing Conformance."); } }, /** * Set the state value of conformance log creation check ID * @param {string} createCheckID */ setConformanceLogCreateCheckId(createCheckID) { this.conformanceLogCreateCheckId = createCheckID; }, }, });