// 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/allMapData Process map data store for fetching, * filtering, and managing Discover Map page state including * filter rules, data layers, and process map data. */ import { defineStore } from "pinia"; import moment from "moment"; import apiClient from "@/api/client.js"; import apiError from "@/module/apiError.js"; import { Decimal } from "decimal.js"; /** * Returns the API base path for the current map data source, * prioritizing temp filter over created filter over log. * @param {object} state - The store state. * @returns {string} The API base path. */ function getMapApiBase(state) { const { tempFilterId, createFilterId, logId } = state; if (tempFilterId !== null) return `/api/temp-filters/${tempFilterId}`; if (createFilterId !== null) return `/api/filters/${createFilterId}`; return `/api/logs/${logId}`; } /** Pinia store for Discover Map page data and filter management. */ export const useAllMapDataStore = defineStore("allMapDataStore", { state: () => ({ baseLogId: null, logId: null, traceId: null, baseTraceId: 1, tempFilterId: null, createFilterId: null, filterName: null, allProcessMap: {}, allBpmn: {}, allStats: {}, allInsights: {}, allTrace: [], allBaseTrace: [], allCase: [], allBaseCase: [], allTraceTaskSeq: [], allBaseTraceTaskSeq: [], allFilterTask: [], allFilterStartToEnd: [], allFilterEndToStart: [], allFilterTimeframe: {}, allFilterTrace: [], allFilterAttrs: [], hasResultRule: null, // Whether any data remains after clicking Apply temporaryData: [], // Data not yet applied via Apply All postRuleData: [], // Data for the has-result API and temp-filters API ruleData: [], // Funnle view's data isRuleData: [], // toggle button data allFunnelData: [], isUpdateFilter: false, // Whether the filter file was saved successfully selectTimeFrame: [], // user select time start and end infinite404: null, // Whether infinite scroll has reached the last page infiniteStart: 0, // Starting index for infinite scroll cases baseInfiniteStart: 0, // Starting index for base infinite scroll cases }), getters: { processMap: (state) => { return state.allProcessMap; }, bpmn: (state) => { return state.allBpmn; }, stats: (state) => { return state.allStats; }, insights: (state) => { return state.allInsights; }, traces: (state) => { return [...state.allTrace].sort((x, y) => x.id - y.id); }, baseTraces: (state) => { return [...state.allBaseTrace].sort((x, y) => x.id - y.id); }, cases: (state) => { return state.allCase; }, baseCases: (state) => { return state.allBaseCase; }, infiniteFirstCases: (state) => { if (state.infiniteStart === 0) return state.allCase; }, BaseInfiniteFirstCases: (state) => { 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) => { if (state.allFilterAttrs !== null) { return state.allFilterAttrs.map((att) => { const copy = { ...att }; switch (copy.type) { case "date": copy.min = copy.min !== null ? moment(copy.min).format("YYYY/MM/DD HH:mm") : null; copy.max = copy.max !== null ? moment(copy.max).format("YYYY/MM/DD HH:mm") : null; break; case "float": copy.min = copy.min !== null ? Number(new Decimal(copy.min).toFixed(2, 3)) : null; copy.max = copy.max !== null ? Number(new Decimal(copy.max).toFixed(2, 2)) : null; break; default: break; } return copy; }); } }, allFunnels: (state) => { return state.allFunnelData; }, }, actions: { /** * fetch discover api, include '/process-map, /bpmn, /stats, /insights'. */ async getAllMapData() { const api = `${getMapApiBase(this)}/discover`; try { const response = await apiClient.get(api); this.allProcessMap = response.data.process_map; this.allBpmn = response.data.bpmn; this.allStats = response.data.stats; this.allInsights = response.data.insights; } catch (error) { apiError(error, "Failed to load the Map."); } }, /** * fetch trace api. */ async getAllTrace() { const baseLogId = this.baseLogId; const baseApi = `/api/logs/${baseLogId}/traces`; const api = `${getMapApiBase(this)}/traces`; try { let baseResponse; const response = await apiClient.get(api); this.allTrace = response.data; if (baseLogId) { baseResponse = await apiClient.get(baseApi); this.allBaseTrace = baseResponse.data; } } catch (error) { apiError(error, "Failed to load the Trace."); } }, /** * fetch trace detail api. */ async getTraceDetail() { const traceId = this.traceId; const start = this.infiniteStart; const api = `${getMapApiBase(this)}/traces/${traceId}?start=${start}&page_size=20`; try { const response = await apiClient.get(api); this.allTraceTaskSeq = response.data.task_seq; this.allCase = response.data.cases; this.allCase.forEach((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.forEach((att) => { switch (att.type) { case "date": att.value = att.value !== null ? moment(att.value).format("YYYY/MM/DD HH:mm") : null; break; case "float": att.value = att.value !== null ? Number(new Decimal(att.value).toFixed(2)) : null; break; default: break; } }); }); return this.allCase; } catch (error) { if (error.response?.status === 404) { this.infinite404 = 404; return; } apiError(error, "Failed to load the Trace Detail."); } }, /** * fetch base log trace detail api. */ async getBaseTraceDetail() { const logId = this.baseLogId; const traceId = this.baseTraceId; const start = this.baseInfiniteStart; const api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`; try { const response = await apiClient.get(api); this.allBaseTraceTaskSeq = response.data.task_seq; this.allBaseCase = response.data.cases; this.allBaseCase.forEach((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.forEach((att) => { switch (att.type) { case "date": att.value = att.value !== null ? moment(att.value).format("YYYY/MM/DD HH:mm") : null; break; case "float": att.value = att.value !== null ? Number(new Decimal(att.value).toFixed(2)) : null; break; default: break; } }); }); return this.allBaseCase; } catch (error) { if (error.response?.status === 404) { this.infinite404 = 404; return; } apiError(error, "Failed to load the Base Trace Detail."); } }, /** * fetch Filter Parameters api. */ async getFilterParams() { const logId = this.logId; const api = `/api/filters/params?log_id=${logId}`; try { const response = await apiClient.get(api); this.allFilterTask = response.data.tasks; this.allFilterStartToEnd = response.data.sources; this.allFilterEndToStart = response.data.sinks; this.allFilterTimeframe = response.data.timeframe; this.allFilterTrace = response.data.trace; this.allFilterAttrs = response.data.attrs; const min = this.allFilterTimeframe.x_axis.min; const max = this.allFilterTimeframe.x_axis.max; // Preserve raw data for Chart.js; incorrect format causes wrong output this.allFilterTimeframe.x_axis.min_base = min; this.allFilterTimeframe.x_axis.max_base = max; // Convert to a time format without seconds this.allFilterTimeframe.x_axis.min = min !== null ? moment(min).format("YYYY/MM/DD HH:mm") : null; this.allFilterTimeframe.x_axis.max = max !== null ? moment(max).format("YYYY/MM/DD HH:mm") : null; } catch (error) { apiError(error, "Failed to load the Filter Parameters."); } }, /** * Test if the Filter Rules Result in Any Data */ async checkHasResult() { const logId = this.logId; const api = `/api/filters/has-result?log_id=${logId}`; try { const response = await apiClient.post(api, this.postRuleData); this.hasResultRule = response.data.result; } catch (error) { apiError(error, "Failed to load the Has Result."); } }, /** * Add a New Temporary Filter */ async addTempFilterId() { const logId = this.logId; const api = `/api/temp-filters?log_id=${logId}`; try { const response = await apiClient.post(api, this.postRuleData); this.tempFilterId = response.data.id; } catch (error) { apiError(error, "Failed to add the Temporary Filters."); } }, /** * Add a New Filter * @param {string} value file's name */ async addFilterId(value) { const logId = this.logId; const api = `/api/filters?log_id=${logId}`; const createFilterObj = { name: value, rules: this.postRuleData, }; try { const response = await apiClient.post(api, createFilterObj); this.createFilterId = response.data.id; this.tempFilterId = null; } catch (error) { apiError(error, "Failed to load the Filters."); } }, /** * Get Filter Detail * @param {number} createFilterId filter type ID */ async fetchFunnel(createFilterId) { const api = `/api/filters/${createFilterId}`; if (createFilterId) { try { const response = await apiClient.get(api); this.temporaryData = response.data.rules; this.logId = response.data.log.id; this.filterName = response.data.name; this.baseLogId = response.data.log.id; } catch (error) { apiError(error, "Failed to get Filter Detail."); } } }, /** * Update an Existing Filter */ async updateFilter() { const createFilterId = this.createFilterId; const api = `/api/filters/${createFilterId}`; const data = this.postRuleData; try { const response = await apiClient.put(api, data); this.isUpdateFilter = response.status === 200; this.tempFilterId = null; } catch (error) { apiError(error, "Failed to update an Existing Filter."); } }, }, });