diff --git a/src/store/modules/logquery/index.ts b/src/store/modules/logquery/index.ts index b3653702..5b64d08a 100644 --- a/src/store/modules/logquery/index.ts +++ b/src/store/modules/logquery/index.ts @@ -26,13 +26,50 @@ type ColumnsMap = { } const useLogQueryStore = defineStore('logQuery', () => { + /** sql state */ + // current query result sql const sql = ref(``) + // editing sql const editingSql = ref('') - // const columns = shallowRef>([]) - const displayedColumns = useStorage('logquery-table-column-visible', {}) - const rows = shallowRef>([]) + const inputTableName = ref('') // table after query + const editingTableName = ref('') // table in editing + // table schema map const tableMap = ref({}) + // computed queried columns schema + const columns = computed(() => { + if (!inputTableName.value) { + return [] + } + return tableMap.value[inputTableName.value] || [] + }) + const tsColumn = shallowRef() + // multiple relative to s, one of 1000 1000 * 1000 1000 * 1000 * 1000 + type Multiple = 1000 | 1000000 | 1000000000 + const multipleRe = /timestamp\((\d)\)/ + const getTsColumn = (tableName: string) => { + const fields = tableMap.value[tableName] || [] + const field = fields.filter((column) => column.data_type.toLowerCase().indexOf('timestamp') > -1)[0] + if (!field) { + return null + } + const timescale = multipleRe.exec(field.data_type) + if (!timescale) return null + return { + multiple: (1000 ** (Number(timescale[1]) / 3)) as Multiple, + ...field, + } + } + // editing ts column + const editingTsColumn = computed(() => { + return getTsColumn(editingTableName.value) + }) + /** for table */ + // column visible + const displayedColumns = useStorage('logquery-table-column-visible', {}) + // table rows + const rows = shallowRef>([]) + // selected row key for detail view const selectedRowKey = ref(-1) const currRow = computed(() => { if (selectedRowKey.value > -1) { @@ -41,31 +78,24 @@ const useLogQueryStore = defineStore('logQuery', () => { return null }) + /** toolbar */ + // time select range time const rangeTime = ref>([]) + // time select relative time const time = ref(10) - const inputTableName = ref('') // table after query - const editingTableName = ref('') // table in editing - const columns = computed(() => { - if (!inputTableName.value) { - return [] - } - return tableMap.value[inputTableName.value] - }) + // for make all query const queryNum = ref(0) + // for table query const tableIndex = ref(0) + // editor type const editorType = ref('builder') + // query Columns differ from columns, keep orders with query const queryColumns = shallowRef>([]) - const queryForm = reactive({ - conditions: [] as Array, - orderBy: 'DESC', - }) - - const limit = ref(1000) const queryLoading = ref(false) const refresh = ref(false) - // unix seconds + // always return two element array, because time select component use two variable to implement relative time and range time const unifiedRange = computed(() => { if (time.value && time.value > 0) { return [dayjs().subtract(time.value, 'minute').unix(), dayjs().unix()] @@ -73,32 +103,43 @@ const useLogQueryStore = defineStore('logQuery', () => { return rangeTime.value.map((v) => Number(v)) }) + // convert to time unit by query table, ts column may have different timestamp accuracy const getRelativeRange = (multiple: number) => { if (time.value && time.value > 0) { return [`now() - Interval '${time.value}m'`, 'now()'] } return rangeTime.value.map((v) => Number(v) * multiple) } - // multiple relative to s, one of 1000 1000 * 1000 1000 * 1000 * 1000 - type Multiple = 1000 | 1000000 | 1000000000 - const multipleRe = /timestamp\((\d)\)/ + const dataLoadFlag = ref(0) - const tsColumn = shallowRef() - const getTsColumn = (tableName: string) => { - const fields = tableMap.value[tableName] || [] - const field = fields.filter((column) => column.data_type.toLowerCase().indexOf('timestamp') > -1)[0] - if (!field) { - return null - } - const timescale = multipleRe.exec(field.data_type) - if (!timescale) return null - return { - multiple: (1000 ** (Number(timescale[1]) / 3)) as Multiple, - ...field, + /** for sql builder */ + const queryForm = reactive({ + conditions: [] as Array, + orderBy: 'DESC', + }) + type TypeKey = keyof typeof typeMap + const opMap = { + String: ['=', 'contains', 'not contains', '!=', 'like'], + Number: ['=', '!=', '>', '>=', '<', '<='], + Time: ['>', '>=', '<', '<='], + } + type OpKey = keyof typeof opMap + + // get Operator List by field + function getOpByField(field: string): string[] { + const fields = tableMap.value[editingTableName.value] + const index = fields.findIndex((f) => f.name === field) + if (index === -1) { + return [] } + const type = fields[index].data_type as TypeKey + const opKey = typeMap[type] as OpKey + return opMap[opKey] || [] } + const limit = ref(1000) + // query handler const query = () => { queryLoading.value = true return editorAPI @@ -121,11 +162,11 @@ const useLogQueryStore = defineStore('logQuery', () => { return columns.value[index] } - const getColumn = (name: string) => { - const allColumns = tableMap.value[inputTableName.value] - const index = allColumns.findIndex((column) => column.name === name) - return allColumns[index] - } + // const getColumn = (name: string) => { + // const allColumns = tableMap.value[inputTableName.value] + // const index = allColumns.findIndex((column) => column.name === name) + // return allColumns[index] + // } const mergeColumn = useLocalStorage('logquery-merge-column', true) const showKeys = useLocalStorage('logquery-show-keys', true) @@ -231,21 +272,22 @@ const useLogQueryStore = defineStore('logQuery', () => { } } if (unifiedRange.value.length === 2) { - if (tsColumn.value) { - const { multiple } = tsColumn.value + if (editingTsColumn.value) { + const { multiple } = editingTsColumn.value const [start, end] = getRelativeRange(multiple) let prefix = ' AND' if (!where.length) { prefix = '' } - where.push(`${prefix} ${tsColumn.value.name} >= ${start} AND ${tsColumn.value.name} < ${end}`) + where.push(`${prefix} ${editingTsColumn.value.name} >= ${start} AND ${editingTsColumn.value.name} < ${end}`) } } return where } + // construct editing sql watch( - [queryForm, unifiedRange, limit, editingTableName], + [queryForm, unifiedRange, limit, editingTableName, editingTsColumn], () => { if (!editingTableName.value) { return @@ -253,14 +295,14 @@ const useLogQueryStore = defineStore('logQuery', () => { if (editorType.value !== 'builder') { return } - tsColumn.value = getTsColumn(editingTableName.value) let str = `SELECT * FROM ${editingTableName.value}` const where = buildCondition() if (where.length) { str += ` WHERE ${where.join('')}` } - if (tsColumn.value) { - str += ` ORDER BY ${tsColumn.value?.name} ${queryForm.orderBy}` + const tmpTsColumn = editingTsColumn.value + if (tmpTsColumn) { + str += ` ORDER BY ${tmpTsColumn.name} ${queryForm.orderBy}` } str += ` LIMIT ${limit.value}` editingSql.value = str @@ -271,31 +313,13 @@ const useLogQueryStore = defineStore('logQuery', () => { } ) + // reset displayedColumn when columns change watch(columns, () => { if (!displayedColumns.value[inputTableName.value]) { displayedColumns.value[inputTableName.value] = columns.value.map((c) => c.name) } }) - type TypeKey = keyof typeof typeMap - const opMap = { - String: ['=', 'contains', 'not contains', '!=', 'like'], - Number: ['=', '!=', '>', '>=', '<', '<='], - Time: ['>', '>=', '<', '<='], - } - type OpKey = keyof typeof opMap - - function getOpByField(field: string): string[] { - const fields = tableMap.value[editingTableName.value] - const index = fields.findIndex((f) => f.name === field) - if (index === -1) { - return [] - } - const type = fields[index].data_type as TypeKey - const opKey = typeMap[type] as OpKey - return opMap[opKey] || [] - } - function reset() { editingTableName.value = '' inputTableName.value = '' @@ -317,6 +341,7 @@ const useLogQueryStore = defineStore('logQuery', () => { inputTableName, editingTableName, tsColumn, + editingTsColumn, time, unifiedRange, queryNum, @@ -326,7 +351,6 @@ const useLogQueryStore = defineStore('logQuery', () => { editorType, queryForm, buildCondition, - getColumn, editingSql, displayedColumns, limit, @@ -339,7 +363,6 @@ const useLogQueryStore = defineStore('logQuery', () => { queryColumns, getOpByField, reset, - getTsColumn, } }) diff --git a/src/views/dashboard/logs/query/ChartContainer.vue b/src/views/dashboard/logs/query/ChartContainer.vue index 87835f5a..76c7b401 100644 --- a/src/views/dashboard/logs/query/ChartContainer.vue +++ b/src/views/dashboard/logs/query/ChartContainer.vue @@ -6,7 +6,7 @@ a-card template(#icon) icon-down template(#content) - a-doption(value="count") Write Count + a-doption(value="count") Row Count a-dsubmenu(trigger="hover" position="lt") template(#default) | Frequency Distribution @@ -36,7 +36,7 @@ a-card } const menuStr = computed(() => { if (currChart.value === 'count') { - return 'Write Count ' + return 'Row Count' } return 'Frequency Distribution' }) diff --git a/src/views/dashboard/logs/query/CountChart.vue b/src/views/dashboard/logs/query/CountChart.vue index 0832b49b..deb74658 100644 --- a/src/views/dashboard/logs/query/CountChart.vue +++ b/src/views/dashboard/logs/query/CountChart.vue @@ -142,6 +142,7 @@ VCharts( function countQuery() { if (!countSql.value) { + data.value = [] return } editorAPI.runSQL(countSql.value).then((result) => { diff --git a/src/views/dashboard/logs/query/ExportLog.vue b/src/views/dashboard/logs/query/ExportLog.vue index 3c37af92..0f069d82 100644 --- a/src/views/dashboard/logs/query/ExportLog.vue +++ b/src/views/dashboard/logs/query/ExportLog.vue @@ -1,7 +1,8 @@ + + diff --git a/src/views/dashboard/logs/query/Pagination.vue b/src/views/dashboard/logs/query/Pagination.vue index 8060abb0..e6c96fb4 100644 --- a/src/views/dashboard/logs/query/Pagination.vue +++ b/src/views/dashboard/logs/query/Pagination.vue @@ -109,7 +109,7 @@ a-space(v-if="pages.length") function loadPage(start: number, end: number, pageIndex: number) { pages.value[pageIndex].loading = true const tsName = tsColumn.value?.name as string - const pageSql = addTsCondition(sql.value, tsName, start, end + 1) + const pageSql = addTsCondition(sql.value, tsName, start, Number(end) + 1) queryPage(pageSql) .then(() => { const index = pages.value.findIndex((page) => page.start === start && page.end === end) diff --git a/src/views/dashboard/logs/query/SQLBuilder.vue b/src/views/dashboard/logs/query/SQLBuilder.vue index 66d14265..e4a6e3e9 100644 --- a/src/views/dashboard/logs/query/SQLBuilder.vue +++ b/src/views/dashboard/logs/query/SQLBuilder.vue @@ -47,7 +47,7 @@ a-form( icon-plus(style="cursor: pointer; font-size: 14px" @click="addCondition") a-form-item(label="ORDER BY") a-space - a-typography-text(v-if="tsColumn" code) {{ tsColumn.name }} + a-typography-text(v-if="editingTsColumn" code) {{ editingTsColumn.name }} a-select(v-model="form.orderBy" style="width: auto" :options="['DESC', 'ASC']") | LIMIT a-input-number( @@ -66,7 +66,7 @@ a-form( const { tableMap, inputTableName, - tsColumn, + editingTsColumn, queryForm: form, limit, editingTableName, diff --git a/src/views/dashboard/logs/query/TableData.vue b/src/views/dashboard/logs/query/TableData.vue index bab09765..069793fa 100644 --- a/src/views/dashboard/logs/query/TableData.vue +++ b/src/views/dashboard/logs/query/TableData.vue @@ -9,7 +9,7 @@ :pagination="false" :row-selection="rowSelection" :bordered="false" - :class="{ wrap_table: wrapLine, single_column: mergeColumn, multiple_column: !mergeColumn, builder_type: editorType === 'builder' }" + :class="{ wrap_table: wrapLine, single_column: mergeColumn, multiple_column: !mergeColumn, builder_type: editorType === 'builder', compact: isCompact }" ) template(#columns) template(v-for="column in tableColumns") @@ -29,7 +29,7 @@ a-space(size="mini" :style="{ cursor: 'pointer' }" @click="changeTsView") svg.icon-12 use(href="#time-index") - | {{ tsColumn.name }} + | {{ tsColumn && tsColumn.name }} a-table-column( v-else-if="mergeColumn" :data-index="column.dataIndex" @@ -37,16 +37,12 @@ :header-cell-style="column.headerCellStyle" ) template(#cell="{ record }") - span.entity-field.clickable( - v-for="field in getEntryFields(record)" - @click="(event) => handleContextMenu(record, field[0], event)" - ) - template(v-if="showKeys") - span(style="color: var(--color-text-3)") - | {{ field[0] }}: - | {{ field[1] }} - template(v-else) - | {{ field[1] }} + span.entity-field(v-for="field in getEntryFields(record)") + span(v-if="showKeys" style="color: var(--color-text-3)") + | {{ field[0] }}: + | {{ field[1] }} + svg.td-config-icon(@click="(event) => handleContextMenu(record, field[0], event)") + use(href="#menu") a-table-column.clickable( v-else :data-index="column.dataIndex" @@ -54,7 +50,9 @@ :header-cell-style="column.headerCellStyle" ) template(#cell="{ record }") - span.clickable(@click="(event) => handleContextMenu(record, column.dataIndex, event)") {{ record[column.dataIndex] }} + span {{ record[column.dataIndex] }} + svg.td-config-icon(@click="(event) => handleContextMenu(record, column.dataIndex, event)") + use(href="#menu") LogDetail(v-model:visible="detailVisible") a-dropdown#td-context( @@ -244,10 +242,11 @@ if (editorType.value !== 'builder') { return } + const rect = event.target.getBoundingClientRect() triggerCell.value = [row, columnName] event.preventDefault() filterOptions.value = getOpByField(columnName) - contextMenuPosition.value = { x: event.clientX, y: event.clientY } + contextMenuPosition.value = { x: rect.left, y: rect.y } contextMenuVisible.value = true } const hideContextMenu = () => { @@ -340,4 +339,42 @@ position: absolute; z-index: 999999; } + .td-config-icon { + margin-left: 3px; + cursor: pointer; + visibility: hidden; + width: 12px; + height: 12px; + color: var(--color-primary); + } + .multiple_column:not(.arco-table-empty) { + :deep(.arco-table-td-content) { + position: relative; + width: auto; + padding-right: 15px; + } + .td-config-icon { + position: absolute; + right: 0; + top: 5px; + } + } + .compact.multiple_column { + :deep(.arco-table-td-content) { + padding-right: 12px; + } + .td-config-icon { + top: 4px; + } + } + .compact .td-config-icon { + width: 9px; + height: 9px; + } + .multiple_column.builder_type :deep(.arco-table-cell:hover) .td-config-icon { + visibility: visible; + } + .single_column.builder_type .entity-field:hover .td-config-icon { + visibility: visible; + } diff --git a/src/views/dashboard/logs/query/Toolbar.vue b/src/views/dashboard/logs/query/Toolbar.vue index 66c95128..ad8350c7 100644 --- a/src/views/dashboard/logs/query/Toolbar.vue +++ b/src/views/dashboard/logs/query/Toolbar.vue @@ -76,8 +76,9 @@ queryLoading, refresh, editingTableName, + editingTsColumn, } = storeToRefs(useLogQueryStore()) - const { getRelativeRange, getTsColumn } = useLogQueryStore() + const { getRelativeRange } = useLogQueryStore() let refreshTimeout = -1 function mayRefresh() { @@ -102,12 +103,12 @@ return } inputTableName.value = editingTableName.value - + tsColumn.value = editingTsColumn.value if (editorType.value === 'text') { inputTableName.value = parseTable(editingSql.value) limit.value = parseLimit(editingSql.value) - tsColumn.value = getTsColumn(inputTableName.value) + if (tsColumn.value) { const { multiple } = tsColumn.value const [start, end] = getRelativeRange(multiple) diff --git a/src/views/dashboard/logs/query/index.vue b/src/views/dashboard/logs/query/index.vue index cf4e4c8f..4df51bbf 100644 --- a/src/views/dashboard/logs/query/index.vue +++ b/src/views/dashboard/logs/query/index.vue @@ -35,7 +35,7 @@ a-checkbox(v-model="compact" type="button" size="small") | Compact Mode a-checkbox(v-model="wrap" size="small") - span(style="color: var(--color-text-2)") {{ $t('logquery.wrapLines') }} + span {{ $t('logquery.wrapLines') }} a-space a-trigger(v-if="columns.length" trigger="click" :unmount-on-close="false")