Discover: fetch discover api && set element(nodes, edges) done

This commit is contained in:
chiayin
2023-02-22 16:10:40 +08:00
parent 8228f9791c
commit 78e18663aa
11 changed files with 361 additions and 74 deletions

View File

@@ -44,8 +44,10 @@ export default {
return {
showNavbarBreadcrumb: false,
// 之後優化要模組化
// navViewName: {
// files: ['all', 'discover', 'compare', 'design'],
// navViewData:
// {
// FILES: ['ALL', 'DISCOVER', 'COMPARE', 'DESIGN'],
// DISCOVER: ['MAP', 'CONFORMANCE', 'PERFORMANCE', 'DATA']
// },
// navViewName: '',
};

View File

@@ -8,6 +8,8 @@ import VueAxios from 'vue-axios';
import moment from 'moment';
import mitt from 'mitt';
import ToastPlugin from 'vue-toast-notification';
import cytoscape from 'cytoscape';
import klay from 'cytoscape-klay';
import "./assets/main.css";
import 'vue-toast-notification/dist/theme-sugar.css';
@@ -16,19 +18,22 @@ const emitter = mitt();
const app = createApp(App);
pinia.use(({ store }) => {
store.$router = markRaw(router)
store.$toast = markRaw(ToastPlugin)
store.$router = markRaw(router);
store.$axios = markRaw(axios);
store.$toast = markRaw(ToastPlugin);
});
// can use `this.$moment` in Vue.js
app.config.globalProperties.$moment = moment;
app.config.globalProperties.emitter = emitter;
app.config.globalProperties.$cytoscape = cytoscape;
cytoscape.use( klay );
app.use(pinia);
app.use(router);
app.use(VueAxios, axios);
// use `this.$toast` in Vue.js
app.use(ToastPlugin, {
app.use(ToastPlugin, { // use `this.$toast` in Vue.js
duration: 10000,
});

View File

@@ -1,8 +1,9 @@
import { createRouter, createWebHistory } from "vue-router";
import { createRouter, createWebHistory, createWebHashHistory, useRoute, useRouter } from "vue-router";
import AuthContainer from '@/views/AuthContainer.vue';
import MainContainer from '@/views/MainContainer.vue';
import Login from '@/views/Login/index.vue';
import Files from '@/views/Files/index.vue';
import Discover from '@/views/Discover/index.vue';
import MemberArea from '@/views/MemberArea/index.vue';
import NotFound404 from '@/views/NotFound404.vue';
@@ -17,7 +18,7 @@ const routes = [
component: AuthContainer,
children: [
{
path: "login",
path: "/login",
name: "Login",
component: Login,
},
@@ -29,15 +30,25 @@ const routes = [
component: MainContainer,
children: [
{
path: "member-area",
path: "/member-area",
name: "MemberArea",
component: MemberArea,
},
{
path: "files",
path: "/files",
name: "Files",
component: Files,
},
{
path: "/discover/:logId",
name: "Discover",
component: Discover,
// 先注意,之後思考是否修改,網址打 discover 時,要跳預設的頁面
// path: "discover",
// redirect: 'discover/12345'
}
]
},
{
@@ -51,7 +62,9 @@ const routes = [
const base_url = import.meta.env.BASE_URL;
const router = createRouter({
history: createWebHistory(base_url),
history: createWebHistory(base_url), //(/)
// history: createWebHashHistory(base_url), // (/#)
routes
});

29
src/stores/allMapData.js Normal file
View File

@@ -0,0 +1,29 @@
import { defineStore } from "pinia";
export default defineStore('allMapDataStore', {
state: () => ({
logId: null,
processMap: {},
bpmn: {},
}),
getters: {
},
actions: {
/**
* fetch discover api, include '/process-map, /bpmn, /stats, /insights'.
*/
async getAllMapData() {
let logId = this.logId
const api = `/api/logs/${logId}/discover`;
try {
const response = await this.$axios.get(api);
// console.log(response);
this.processMap = response.data.process_map;
this.bpmn = response.data.bpmn;
} catch(error) {
// console.dir(error);
};
},
}
})

View File

@@ -30,7 +30,7 @@ export default defineStore('filesStore', {
switchFilesTagData: {
ALL: ['Log', 'Filter', 'Rule', 'Design'],
DISCOVER: ['Log', 'Filter', 'Rule'],
COMPARE: ['Filter'],
COMPARE: ['Log','Filter'],
DESIGN: ['Log', 'Design'],
},
filesTag: 'ALL',
@@ -60,7 +60,7 @@ export default defineStore('filesStore', {
* Fetch event logs api
*/
async fetchEventLog() {
const api = 'api/logs';
const api = '/api/logs';
try {
const response = await axios.get(api);
@@ -92,7 +92,7 @@ export default defineStore('filesStore', {
* Fetch filters api
*/
async fetchFilter() {
const api = 'api/filters';
const api = '/api/filters';
try {
const response = await axios.get(api);

View File

@@ -22,7 +22,7 @@ export default defineStore('loginStore', {
* fetch Login For Access Token api
*/
async signIn() {
const api = 'api/oauth/token';
const api = '/api/oauth/token';
const config = {
headers: {
// http post 預設的 url 編碼,非 json 格式
@@ -38,7 +38,7 @@ export default defineStore('loginStore', {
const token = response.data.access_token;
document.cookie = `luciaToken=${token}`;
this.$router.push('/files')
this.$router.push('/files');
}
} catch(error) {
this.isInvalid = true;
@@ -61,7 +61,7 @@ export default defineStore('loginStore', {
* get user detail for 'my-account' api
*/
async getUserData() {
const api = 'api/my-account';
const api = '/api/my-account';
try {
const response = await axios.get(api);
@@ -74,7 +74,7 @@ export default defineStore('loginStore', {
* check login for 'my-account' api
*/
async checkLogin() {
const api = 'api/my-account';
const api = '/api/my-account';
try {
const response = await axios.get(api);

View File

@@ -0,0 +1,187 @@
<template>
<h1>H1底家啦~~這裡是 Discovery 動物頻道耶~</h1>
<div class="flex min-w-full min-h-screen">
<div class="cyto" id="cyto"></div>
</div>
</template>
<script>
import { storeToRefs } from 'pinia';
import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js';
export default {
setup() {
const loadingStore = LoadingStore();
const allMapDataStore = AllMapDataStore();
const { isLoading } = storeToRefs(loadingStore);
const { processMap, bpmn } = storeToRefs(allMapDataStore);
return {
isLoading,
processMap, bpmn, allMapDataStore,
}
},
data() {
return {
processMapData: {
startId: 0,
endId: 1,
nodes: [],
edges: [],
},
curveStyle:'unbundled-bezier', // 貝茲曲線 bezier | 直角 unbundled-bezier
}
},
methods: {
/**
* Switch curve-style is 'bezier' or 'unbundled-bezier'.
* @param {string} style 貝茲曲線 bezier | 直角 unbundled-bezier
*/
switchCurveStyle(style){
this.curveStyle = style;
},
/**
* 將node資料彙整
* @param {string} type ProcessMap | BPMN
*/
setNodesData(type){
const logFreq = {
"total": "",
"rel_freq": "",
"average": "",
"median": "",
"max": "",
"min": "",
"cases": ""
};
const logDuration = {
"total": "",
"rel_duration": "",
"average": "",
"median": "",
"max": "",
"min": "",
};
// BPMN 才有 gateway 類別
const gateway = {
parallel: "+",
exclusive: "x",
inclusive: "o",
};
// 將 api call 回來的資料帶進 node
this.processMap.vertices.forEach(node => {
switch (node.type) {
// add type of 'event' node
case 'event':
if(node.event_type === 'start') this.processMapData.startId = node.id;
else if(node.event_type === 'end') this.processMapData.endId = node.id;
this.processMapData.nodes.push({
data:{
id:node.id,
type:node.type,
label:node.event_type,
height:60,
width:60,
backgroundColor:'#FFCCCC',
bordercolor:'#003366',
shape:"ellipse",
freq:logFreq,
duration:logDuration,
}
});
break;
// add type of 'activity' node
default:
this.processMapData.nodes.push({
data:{
id:node.id,
type:node.type,
label:node.label,
height:80,
width:100,
backgroundColor:'#FFCCCC',
bordercolor:'#003366',
shape:"ellipse",
freq:node.freq,
duration:node.duration,
}
})
break;
}
});
},
/**
* 將edge資料彙整
* @param {string} type ProcessMap/BPMN
*/
setEdgesData(type) {
//add event duration is empty
const event_duration = {
"total": "",
"rel_duration": "",
"average": "",
"median": "",
"max": "",
"min": "",
"cases": ""
};
this.processMap.edges.forEach(edge => {
this.processMapData.nodes.push({
data: {
source:edge.tail,
target:edge.head,
freq:edge.freq,
duration:edge.duration == null ? event_duration:edge.duration,
style:'dotted',
lineWidth:1,
},
});
});
},
/**
* create and setting cytoscape
*/
createCytoscape() {
let graphId = document.getElementById('cyto');
// let nodes
// let edges
let cy = this.$cytoscape({
container: graphId,
// elements: {
// nodes: nodes, // 節點的資料
// edges: edges, // 關係線的資料
// },
layout: {
name: 'klay',
rankDir: 'LR' // 直向 TB | 橫向 LR
},
style: [
{
selector: 'node',
}
],
})
// this.$cytoscape.warnings(false); // false
},
async executeApi() {
await this.allMapDataStore.getAllMapData();
this.setNodesData();
console.log(this.processMapData.nodes);
}
},
created() {
this.allMapDataStore.logId = this.$route.params.logId;
},
mounted() {
this.isLoading = false
// this.createCytoscape();
this.executeApi();
},
}
</script>

View File

@@ -6,23 +6,22 @@
<!-- card group 最多六個-->
<ul class="flex justify-start items-center gap-4 overflow-x-auto w-full h-[192px] scrollbar whitespace-nowrap pb-4">
<!-- card item v-for -->
<li class="min-w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:text-primary hover:bg-primary/50 hover:border-primary duration-300" v-for="(file, index) in recentlyUsedFiles.slice(0, 6)" :key="file.id">
<a href="" class="h-full flex flex-col justify-between">
<div>
<figure class="mb-2">
<IconDataFormat class="w-8 h-8 hover:fill-primary"></IconDataFormat>
</figure>
<h3 class="text-sm font-medium mb-2">
{{ file.name }}
</h3>
<p class="text-sm text-neutral-500 whitespace-nowrap break-keep text-ellipsis overflow-hidden">
{{ file.parentLog }}
</p>
</div>
<p class="text-sm text-neutral-500">
{{ file.accessed_at }}
<li class="min-w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:text-primary hover:bg-primary/50 hover:border-primary duration-300
flex flex-col justify-between cursor-pointer" v-for="(file, index) in recentlyUsedFiles.slice(0, 6)" :key="file.id" @dblclick="enterDiscover(file)">
<div>
<figure class="mb-2">
<IconDataFormat class="w-8 h-8 hover:fill-primary"></IconDataFormat>
</figure>
<h3 class="text-sm font-medium mb-2">
{{ file.name }}
</h3>
<p class="text-sm text-neutral-500 whitespace-nowrap break-keep text-ellipsis overflow-hidden">
{{ file.parentLog }}
</p>
</a>
</div>
<p class="text-sm text-neutral-500">
{{ file.accessed_at }}
</p>
</li>
</ul>
</section>
@@ -52,7 +51,7 @@
</tr>
</thead>
<tbody>
<tr v-for="(file, index) in allFiles" :key="file.id">
<tr v-for="(file, index) in allFiles" :key="file.id" @dblclick="enterDiscover(file)">
<td>{{ file.name }}</td>
<td class="text-neutral-500">{{ file.parentLog }}</td>
<td class="text-neutral-500">{{ file.fileType }}</td>
@@ -64,24 +63,22 @@
</div>
<!-- All Files type of grid -->
<ul class="flex justify-start items-start gap-4 flex-wrap overflow-y-scroll overflow-x-hidden max-h-[calc(100vh_-_480px)] scrollbar" v-else>
<li class="w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:text-primary hover:bg-primary/50 hover:border-primary duration-300"
v-for="(file, index) in allFiles" :key="file.id">
<a href="" class="h-full flex flex-col justify-between">
<div>
<figure class="mb-2">
<IconDataFormat class="w-8 h-8 hover:fill-primary"></IconDataFormat>
</figure>
<h3 class="text-sm font-medium mb-2">
{{ file.name }}
</h3>
<p class="text-sm text-neutral-500 whitespace-nowrap break-keep text-ellipsis overflow-hidden">
{{ file.parentLog }}
</p>
</div>
<p class="text-sm text-neutral-500">
{{ file.updated_at }}
<li class="w-[216px] h-[168px] p-4 border rounded border-neutral-300 hover:text-primary hover:bg-primary/50 hover:border-primary duration-300 flex flex-col justify-between cursor-pointer"
v-for="(file, index) in allFiles" :key="file.id" @dblclick="enterDiscover(file)">
<div>
<figure class="mb-2">
<IconDataFormat class="w-8 h-8 hover:fill-primary"></IconDataFormat>
</figure>
<h3 class="text-sm font-medium mb-2">
{{ file.name }}
</h3>
<p class="text-sm text-neutral-500 whitespace-nowrap break-keep text-ellipsis overflow-hidden">
{{ file.parentLog }}
</p>
</a>
</div>
<p class="text-sm text-neutral-500">
{{ file.updated_at }}
</p>
</li>
</ul>
</section>
@@ -181,12 +178,20 @@
},
methods: {
/**
* switch sort
* sort's switch
* @param {string} value
*/
switchSort(value) {
this.sortReverseOrder = (this.sortKey === value)? !this.sortReverseOrder: false;
this.sortKey = value;
},
/**
* 選擇該 files 進入 Discover/Compare/Design 頁面
* @param {object} file
*/
enterDiscover(file){
this.$router.push({ name: 'Discover', params: { logId: file.id }});
},
},
mounted() {
this.store.fetchEventLog();