Dynamic filter options, Vuex store to pass them, improved code structure
This commit is contained in:
		| @@ -19,6 +19,7 @@ | |||||||
|     "single-spa-vue": "^2.5.1", |     "single-spa-vue": "^2.5.1", | ||||||
|     "systemjs-webpack-interop": "^2.3.7", |     "systemjs-webpack-interop": "^2.3.7", | ||||||
|     "vue": "^3.2.39", |     "vue": "^3.2.39", | ||||||
|  |     "vuex": "next", | ||||||
|     "vue-select": "beta", |     "vue-select": "beta", | ||||||
|     "vuetify": "^3.0.0-beta.0", |     "vuetify": "^3.0.0-beta.0", | ||||||
|     "webfontloader": "^1.0.0" |     "webfontloader": "^1.0.0" | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <template> | <template> | ||||||
|   <v-select |   <v-select | ||||||
|       v-model="filter" |       v-model="filterValue" | ||||||
|       :options="options" |       :options="options" | ||||||
|       class="ag-custom-component-popup" |       @open="updateOptions" | ||||||
|   ></v-select> |   ></v-select> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -15,19 +15,31 @@ export default { | |||||||
|     vSelect |     vSelect | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     options: { |     field: { | ||||||
|     }, |     }, | ||||||
|     changeValue: { |     changeValue: { | ||||||
|  |     }, | ||||||
|  |     filter: { | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       filter: null, |       options: [] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   watch: { |   computed: { | ||||||
|     filter(value) { |     filterValue: { | ||||||
|       this.changeValue(value) |       get() { | ||||||
|  |         return this.filter | ||||||
|  |       }, | ||||||
|  |       set(newValue) { | ||||||
|  |         this.changeValue(newValue) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     updateOptions() { | ||||||
|  |       this.options = this.$store.state.filterOptions[this.field] ?? [] | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ export default { | |||||||
|       Combobox |       Combobox | ||||||
|     }, |     }, | ||||||
|     template: `<Combobox |     template: `<Combobox | ||||||
|       :options="params.options" |       :field="params.field" | ||||||
|  |       :filter="filter" | ||||||
|       :change-value="updateFilter" |       :change-value="updateFilter" | ||||||
|     />`, |     />`, | ||||||
|     data: function () { |     data: function () { | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								src/components/Grid/Main/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/components/Grid/Main/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import ComboboxFilter from "../../Filter/ComboboxFilter"; | ||||||
|  |  | ||||||
|  | const config = { | ||||||
|  |     defaultColDef: { | ||||||
|  |         width: 120, | ||||||
|  |         initialPinned: true, | ||||||
|  |         resizable: true, | ||||||
|  |         enableCellChangeFlash: true | ||||||
|  |     }, | ||||||
|  |     columnDefs: [ | ||||||
|  |         { | ||||||
|  |             field: '@timestamp', | ||||||
|  |             width: 70, | ||||||
|  |             sortable: true | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'kubernetes.namespace', | ||||||
|  |             headerName: 'namespace', | ||||||
|  |             tooltipValueGetter: (params) => params.value, | ||||||
|  |             filter: ComboboxFilter, | ||||||
|  |             filterParams: { | ||||||
|  |                 options: [], | ||||||
|  |                 field: 'kubernetes.namespace', | ||||||
|  |                 parentColumn: null, | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'kubernetes.pod.name', | ||||||
|  |             headerName: 'pod', | ||||||
|  |             tooltipValueGetter: (params) => params.value, | ||||||
|  |             filter: ComboboxFilter, | ||||||
|  |             filterParams: { | ||||||
|  |                 options: [], | ||||||
|  |                 field: 'kubernetes.pod.name', | ||||||
|  |                 parentColumn: 'kubernetes.namespace', | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'kubernetes.container.name', | ||||||
|  |             headerName: 'container', | ||||||
|  |             tooltipValueGetter: (params) => params.value, | ||||||
|  |             filter: ComboboxFilter, | ||||||
|  |             filterParams: { | ||||||
|  |                 options: [], | ||||||
|  |                 field: 'kubernetes.container.name', | ||||||
|  |                 parentColumn: 'kubernetes.pod.name', | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'message', | ||||||
|  |             tooltipValueGetter: (params) => params.value, | ||||||
|  |             width: 500, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'stream', | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default config | ||||||
| @@ -24,6 +24,10 @@ import "ag-grid-community/styles//ag-grid.css"; | |||||||
| import "ag-grid-community/styles//ag-theme-material.css"; | import "ag-grid-community/styles//ag-theme-material.css"; | ||||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||||
| import ComboboxFilter from "./Filter/ComboboxFilter.js"; | import ComboboxFilter from "./Filter/ComboboxFilter.js"; | ||||||
|  | import flattenObj from "../helpers/flattenObj"; | ||||||
|  | import parseEventData from "../helpers/parseEventData"; | ||||||
|  | import { mapActions } from 'vuex'; | ||||||
|  | import config from "./Grid/Main/config"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
| @@ -34,103 +38,123 @@ export default { | |||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       examineLogContent: null, |       examineLogContent: null, | ||||||
|  |       ...config, | ||||||
|       gridApi: null, |       gridApi: null, | ||||||
|       gridColumnApi: null, |       gridColumnApi: null, | ||||||
|       defaultColDef: { |  | ||||||
|         width: 50, |  | ||||||
|         initialPinned: true, |  | ||||||
|         resizable: true, |  | ||||||
|         enableCellChangeFlash: true |  | ||||||
|       }, |  | ||||||
|       currentRowCount: 0, |  | ||||||
|       comboBoxOptions: {}, |       comboBoxOptions: {}, | ||||||
|       viewRowCount: 20, |       es: null, | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     columnDefs() { |     filterQuery() { | ||||||
|       return [ |       return this.$store.state.filterQuery | ||||||
|         { |     }, | ||||||
|           field: '@timestamp', |   }, | ||||||
|           width: 70, |   watch: { | ||||||
|           sortable: true |     filterQuery() { | ||||||
|         }, |       this.setupStream() | ||||||
|         { |     }, | ||||||
|           field: 'kubernetes.namespace', |  | ||||||
|           headerName: 'namespace', |  | ||||||
|           tooltipValueGetter: (params) => params.value, |  | ||||||
|           filter: ComboboxFilter, |  | ||||||
|           filterParams: { |  | ||||||
|             options: this.comboBoxOptions['kubernetes.namespace'], |  | ||||||
|             field: 'kubernetes.namespace', |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           field: 'kubernetes.pod.name', |  | ||||||
|           headerName: 'pod', |  | ||||||
|           tooltipValueGetter: (params) => params.value, |  | ||||||
|           filter: ComboboxFilter, |  | ||||||
|           filterParams: { |  | ||||||
|             options: this.comboBoxOptions['kubernetes.pod.name'], |  | ||||||
|             field: 'kubernetes.pod.name', |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           field: 'kubernetes.container.name', |  | ||||||
|           headerName: 'container', |  | ||||||
|           tooltipValueGetter: (params) => params.value, |  | ||||||
|           filter: ComboboxFilter, |  | ||||||
|           filterParams: { |  | ||||||
|             options: this.comboBoxOptions['kubernetes.container.name'], |  | ||||||
|             field: 'kubernetes.container.name', |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           field: 'message', |  | ||||||
|           tooltipValueGetter: (params) => params.value, |  | ||||||
|           width: 500, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           field: 'stream', |  | ||||||
|         }, |  | ||||||
|       ]; |  | ||||||
|     } |  | ||||||
|   }, |   }, | ||||||
|   created() { |   created() { | ||||||
|     this.setupStream() |     // TODO: monitor actual URL | ||||||
|  |     this.setFilterQuery([]) | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     ...mapActions({ | ||||||
|  |       setFilterOptions: 'setFilterOptions', | ||||||
|  |       setFilterQuery: 'setFilterQuery', | ||||||
|  |     }), | ||||||
|     setupStream() { |     setupStream() { | ||||||
|       let es = new EventSource('/events'); |       this.es && this.es.close(); | ||||||
|  |       let url = new URL('/events', window.location.href); | ||||||
|  |       this.filterQuery.map((e) => { | ||||||
|  |         url.searchParams.append(e.key, e.value); | ||||||
|  |       }) | ||||||
|  |       let es = new EventSource(url.toString()); | ||||||
|       es.onmessage = (e) => this.handleReceiveMessage(e) |       es.onmessage = (e) => this.handleReceiveMessage(e) | ||||||
|       es.addEventListener("filters", (e) => this.handleReceiveFilters(e)) |       es.addEventListener("filters", (e) => this.handleReceiveFilters(e)) | ||||||
|  |       this.es = es | ||||||
|     }, |     }, | ||||||
|     onGridReady(params) { |     onGridReady(params) { | ||||||
|       this.gridApi = params.api; |       this.gridApi = params.api; | ||||||
|       this.gridColumnApi = params.columnApi; |       this.gridColumnApi = params.columnApi; | ||||||
|  |       this.gridColumnApi.applyColumnState({ | ||||||
|  |         state: [{ | ||||||
|  |             colId: '@timestamp', | ||||||
|  |             sort: 'desc' | ||||||
|  |           }] | ||||||
|  |       }); | ||||||
|  |       this.gridApi.addGlobalListener((type, event) => { | ||||||
|  |         if (type === 'filterChanged') { | ||||||
|  |           let changedColumn = event.columns[0] ? (event.columns[0].colId) : null | ||||||
|  |           let query = [] | ||||||
|  |           let gridColumns = event.columnApi.columnModel.gridColumns | ||||||
|  |           gridColumns.map((column) => { | ||||||
|  |             // Reset child column filter if parent changed | ||||||
|  |             let parentColumn = column?.colDef?.filterParams?.parentColumn | ||||||
|  |             if (parentColumn && changedColumn === parentColumn) { | ||||||
|  |               let filterInstance = this.gridApi.getFilterInstance(column.colId); | ||||||
|  |               column.filterActive = null | ||||||
|  |               filterInstance.updateFilter(null) | ||||||
|  |               this.gridApi.onFilterChanged(); | ||||||
|  |             } | ||||||
|  |             if (column.filterActive) { | ||||||
|  |               query.push({ | ||||||
|  |                 key: column.colId, | ||||||
|  |                 value: column.filterActive | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |           this.setFilterQuery(query) | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|     }, |     }, | ||||||
|     handleReceiveMessage (event) { |     handleReceiveMessage (event) { | ||||||
|       const eventData = this.parseEventData(event.data); |       const eventData = parseEventData(event.data); | ||||||
|       const res = this.gridApi.applyTransaction({ |       const res = this.gridApi.applyTransaction({ | ||||||
|         add: [eventData] |         add: [eventData] | ||||||
|       }); |       }); | ||||||
|       const rowNode = res.add[0] |       const rowNode = res.add[0] | ||||||
|       this.gridApi.flashCells({ rowNodes: [rowNode]}); |       this.gridApi.flashCells({ rowNodes: [rowNode]}); | ||||||
|       this.gridApi.sizeColumnsToFit() |  | ||||||
|     }, |     }, | ||||||
|     handleReceiveFilters (event) { |     handleReceiveFilters (event) { | ||||||
|       this.comboBoxOptions = this.parseEventData(event.data); |       let data = parseEventData(event.data); | ||||||
|     }, |       let opts = this.comboBoxOptions | ||||||
|     parseEventData (eventData) { |       for (let k in data) { | ||||||
|       try { |         if (!(k in opts)) { | ||||||
|         let json = JSON.parse(eventData) |           opts[k] = [] | ||||||
|         if (!json.message) { |         } | ||||||
|           json.message = JSON.stringify(json.json) |         // TODO: proper merging | ||||||
|  |         if ((data[k].parentKey) || (opts[k].length === 0)) { | ||||||
|  |           opts[k].push(data[k]) | ||||||
|         } |         } | ||||||
|         return json |  | ||||||
|       } catch (e) { |  | ||||||
|         console.error(e, eventData) |  | ||||||
|       } |       } | ||||||
|  |       this.comboBoxOptions = opts | ||||||
|  |  | ||||||
|  |       let correctOptions = {}; | ||||||
|  |       for (let column in opts) { | ||||||
|  |           correctOptions[column] = [] | ||||||
|  |           let columnDef = this.columnDefs.find((columnDef) => { | ||||||
|  |             return columnDef.field === column | ||||||
|  |           }); | ||||||
|  |           let parentColumnName = columnDef?.filterParams?.parentColumn; | ||||||
|  |           let possibleColumnOptions = opts[column].filter((k) => { | ||||||
|  |             return k.parentKey === parentColumnName | ||||||
|  |           }) | ||||||
|  |           if (possibleColumnOptions.length === 1) { | ||||||
|  |             correctOptions[column] = possibleColumnOptions[0].options | ||||||
|  |           } else if (possibleColumnOptions.length > 1) { | ||||||
|  |             let filterInstance = this.gridApi.getFilterInstance(parentColumnName) | ||||||
|  |             possibleColumnOptions.forEach((opt) => { | ||||||
|  |               if (filterInstance && (opt.parentValue === filterInstance.filter)) { | ||||||
|  |                 correctOptions[column] = opt.options | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       this.gridApi.sizeColumnsToFit() | ||||||
|  |  | ||||||
|  |       this.setFilterOptions(correctOptions) | ||||||
|     }, |     }, | ||||||
|     openExamineLog (row) { |     openExamineLog (row) { | ||||||
|       const selectedRow = row.data |       const selectedRow = row.data | ||||||
| @@ -152,19 +176,5 @@ export default { | |||||||
|   }, |   }, | ||||||
| } | } | ||||||
|  |  | ||||||
| const flattenObj = (ob) => { |  | ||||||
|   let result = {}; |  | ||||||
|   for (const i in ob) { |  | ||||||
|     if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) { |  | ||||||
|       const temp = flattenObj(ob[i]); |  | ||||||
|       for (const j in temp) { |  | ||||||
|         result[i + '.' + j] = temp[j]; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|       result[i] = ob[i]; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return result; |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								src/helpers/flattenObj.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/helpers/flattenObj.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | const flattenObj = (ob) => { | ||||||
|  |     let result = {}; | ||||||
|  |     for (const i in ob) { | ||||||
|  |         if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) { | ||||||
|  |             const temp = flattenObj(ob[i]); | ||||||
|  |             for (const j in temp) { | ||||||
|  |                 result[i + '.' + j] = temp[j]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             result[i] = ob[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default flattenObj; | ||||||
							
								
								
									
										13
									
								
								src/helpers/parseEventData.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/helpers/parseEventData.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | const parseEventData = (eventData) => { | ||||||
|  |     try { | ||||||
|  |         let json = JSON.parse(eventData) | ||||||
|  |         if (!json.message && json.json) { | ||||||
|  |             json.message = JSON.stringify(json.json) | ||||||
|  |         } | ||||||
|  |         return json | ||||||
|  |     } catch (e) { | ||||||
|  |         console.error(e, eventData) | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default parseEventData; | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| import { createApp } from 'vue' | import { createApp } from 'vue' | ||||||
|  | import store from "./stores"; | ||||||
| import App from './App.vue' | import App from './App.vue' | ||||||
| import vuetify from './plugins/vuetify' | import vuetify from './plugins/vuetify' | ||||||
| import { loadFonts } from './plugins/webfontloader' | import { loadFonts } from './plugins/webfontloader' | ||||||
| @@ -6,6 +7,8 @@ import './assets/main.css' | |||||||
| import 'vue-select/dist/vue-select.css'; | import 'vue-select/dist/vue-select.css'; | ||||||
| loadFonts() | loadFonts() | ||||||
|  |  | ||||||
| createApp(App) | const app = createApp(App); | ||||||
|   .use(vuetify) | app.use(store); | ||||||
|   .mount('#app') | app.use(vuetify); | ||||||
|  | app.mount("#app"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +0,0 @@ | |||||||
| import { ref, computed } from 'vue' |  | ||||||
| import { defineStore } from 'pinia' |  | ||||||
|  |  | ||||||
| export const useCounterStore = defineStore('counter', () => { |  | ||||||
|   const count = ref(0) |  | ||||||
|   const doubleCount = computed(() => count.value * 2) |  | ||||||
|   function increment() { |  | ||||||
|     count.value++ |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   return { count, doubleCount, increment } |  | ||||||
| }) |  | ||||||
							
								
								
									
										26
									
								
								src/stores/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/stores/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import { createStore } from "vuex" | ||||||
|  |  | ||||||
|  | const store = createStore({ | ||||||
|  |   state: { | ||||||
|  |     filterOptions: {}, | ||||||
|  |     filterQuery: [] | ||||||
|  |   }, | ||||||
|  |   actions: { | ||||||
|  |     setFilterOptions(context, payload) { | ||||||
|  |       context.commit("SET_FILTER_OPTIONS", payload); | ||||||
|  |     }, | ||||||
|  |     setFilterQuery(context, payload) { | ||||||
|  |       context.commit("SET_FILTER_QUERY", payload); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   mutations: { | ||||||
|  |     SET_FILTER_OPTIONS(state, payload) { | ||||||
|  |       state.filterOptions = payload | ||||||
|  |     }, | ||||||
|  |     SET_FILTER_QUERY(state, payload) { | ||||||
|  |       state.filterQuery = payload | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default store | ||||||
		Reference in New Issue
	
	Block a user