From 38e760ab66cc89cb9f85d2c1de08f6ad84c5c319 Mon Sep 17 00:00:00 2001 From: Kelani Tolulope Date: Wed, 8 May 2024 12:24:08 +0100 Subject: [PATCH] Add options to extended drilldown filtering --- .../components/Common/RecordListFilter.vue | 29 +- .../src/components/PageBlocks/ChartBase.vue | 37 ++- .../components/PageBlocks/RecordListBase.vue | 290 +++++++++++++++--- client/web/compose/src/lib/record-filter.js | 21 ++ .../src/themes/corteza-base/custom.scss | 6 + locale/en/corteza-webapp-compose/block.yaml | 13 + 6 files changed, 327 insertions(+), 69 deletions(-) diff --git a/client/web/compose/src/components/Common/RecordListFilter.vue b/client/web/compose/src/components/Common/RecordListFilter.vue index 91e6a7f75..215ac598a 100644 --- a/client/web/compose/src/components/Common/RecordListFilter.vue +++ b/client/web/compose/src/components/Common/RecordListFilter.vue @@ -48,24 +48,6 @@ :key="`${groupIndex}-${index}`" class="pb-2" > - -
- {{ $t('recordList.filter.where') }} -
- - -
- @@ -156,7 +138,7 @@ @@ -181,15 +163,10 @@
- +
{ this.chart = chart + + if (this.isDrillDownEnabled) { + const { moduleID, dimensions = [] } = this.chart.config.reports[0] || {} + + this.findModuleByID({ namespace: this.namespace, moduleID }).then(chartModule => { + if (!chartModule) { + return + } + + const { field } = dimensions[0] || {} + const { name, label } = chartModule.fields.find(({ name }) => name === field) || {} + this.filter.field = { name, label } + }) + } }).catch(this.toastErrorHandler(this.$t('chart.loadFailed'))) }, @@ -158,12 +181,11 @@ export default { } // Get recordListID that is linked - let { moduleID, dimensions, filter } = this.filter + let { moduleID, dimensions, filter, field } = this.filter + const { name, label } = field || {} // Construct filter const dimensionFilter = dimensions ? `(${dimensions} = '${drillDownValue}')` : '' - filter = filter ? `(${filter})` : '' - const prefilter = [dimensionFilter, filter].filter(f => f).join(' AND ') if (drillDown.blockID) { // Use linked record list to display drill down data @@ -172,8 +194,15 @@ export default { // Construct its uniqueID to identify it const recordListUniqueID = [pageID, recordID, drillDown.blockID, false].map(v => v || NoID).join('-') - this.$root.$emit(`drill-down-recordList:${recordListUniqueID}`, prefilter) + this.$root.$emit(`drill-down-recordList:${recordListUniqueID}`, { + prefilter: dimensionFilter, + name: name || label || dimensions, + value: drillDownValue, + }) } else { + filter = filter ? `(${filter})` : '' + const prefilter = [dimensionFilter, filter].filter(f => f).join(' AND ') + const { title } = this.block const { fields = [] } = this.options.drillDown.recordListOptions || {} diff --git a/client/web/compose/src/components/PageBlocks/RecordListBase.vue b/client/web/compose/src/components/PageBlocks/RecordListBase.vue index 05e8a27ef..211d2bbad 100644 --- a/client/web/compose/src/components/PageBlocks/RecordListBase.vue +++ b/client/web/compose/src/components/PageBlocks/RecordListBase.vue @@ -89,7 +89,6 @@ >
-
- {{ $t('recordList.filter.filters.active') }} - - - +
+
+ + {{ f.label || f.name }} + + + + {{ $t(`recordList.filter.operatorLabels.${formatActiveFilterOperator(f.operator)}`) }} + + + + + + {{ $t('recordList.filter.nil') }} + + + + + +
+
+ + + {{ $t('recordList.filter.conditions.or') }} + +
{ + return this.createDefaultFilter(f.condition, { name: f.name, kind: f.kind, isMulti: f.isMulti }, f.value, f.operator) + }), + groupCondition: recordListFilter.length && recordListFilter.length - 1 !== i ? 'AND' : undefined, + } + groupFilter.filter = groupFilter.filter.sort((a, b) => a.name.localeCompare(b.name)) + + const grouped = {} + + groupFilter.filter.forEach(filter => { + if (!grouped[filter.name]) { + grouped[filter.name] = [] + } + grouped[filter.name].push(filter) + }) + + groupFilter.filter = [] + + Object.keys(grouped).forEach((key, index) => { + const group = grouped[key] + group.forEach((filter, idx) => { + if (idx === 0) { + filter.condition = index === 0 ? 'Where' : 'AND' + } else { + filter.condition = 'OR' + } + groupFilter.filter.push(filter) + }) + }) + + groupedData.push(groupFilter) + } + + groupedData = groupedData.filter(({ filter }) => filter.length) + + return groupedData + }, }, watch: { @@ -1658,7 +1787,7 @@ export default { this.selected = [] // Compute query based on query, prefilter and recordListFilter - const query = queryToFilter(this.query, this.drillDownFilter || this.prefilter, this.fields.map(({ moduleField }) => moduleField), this.recordListFilter) + const query = queryToFilter(this.query, this.drillDownFilter || this.prefilter, this.fields.map(({ moduleField }) => moduleField), this.groupRecordListFilter) const { moduleID, namespaceID } = this.recordListModule @@ -1750,7 +1879,6 @@ export default { removeItem(`record-list-filters-${this.uniqueID}`) } else { this.recordListFilter = currentFilters - this.activeFilters = [...new Set(currentFilters.map(f => f.name).filter(f => !!f))] } } catch (e) { // Land here if the filter is corrupted @@ -1821,12 +1949,67 @@ export default { this.refresh(true) }, - setDrillDownFilter (drillDownFilter) { - if (!this.drillDownFilter) { - this.activeFilters.push(this.$t('recordList.drillDown.filter.label')) + createDefaultFilter (condition, field = {}, value = undefined, operator = undefined) { + const fields = [...this.recordListModule.fields, ...this.recordListModule.systemFields()] + const moduleField = (fields.find(({ name }) => name === field.name) || {}) + + const record = !this.isBetweenOperator(operator) + ? { recordID: '0', values: { [moduleField.name]: value } } + : [ + { recordID: '0', values: { [moduleField.name]: value.start } }, + { recordID: '0', values: { [moduleField.name]: value.end } }, + ] + + if (moduleField.isSystem) { + if (!this.isBetweenOperator(operator)) { + record[moduleField.name] = value + } else { + record[0][moduleField.name] = value.start + record[1][moduleField.name] = value.end + } } - this.drillDownFilter = drillDownFilter + return { + condition, + name: moduleField.name, + operator: operator || (moduleField.isMulti ? 'IN' : '='), + value, + kind: moduleField.kind, + label: moduleField.label || moduleField.name, + field: moduleField, + record: !this.isBetweenOperator(operator) + ? new compose.Record(this.recordListModule, { ...record }) + : [ + new compose.Record(this.recordListModule, { ...record[0] }), + new compose.Record(this.recordListModule, { ...record[1] }), + ], + } + }, + + setDrillDownFilter ({ prefilter: drillDownFilter, name: fieldName, value: fieldValue }) { + if (drillDownFilter) { + const field = (this.recordListModule.fields.find(f => f.name === fieldName) || {}) + + if (!this.recordListFilter.length) { + this.recordListFilter = [ + { + groupCondition: undefined, + filter: [ + this.createDefaultFilter('Where', field, fieldValue, true), + ], + }, + ] + } else { + // move to a separate func. + const { filter } = this.recordListFilter[0] + if (!filter.length || (filter.length && !filter[0].name)) { + this.recordListFilter[0].filter = [] + this.recordListFilter[0].filter.push(this.createDefaultFilter('Where', field, fieldValue)) + } else { + this.recordListFilter[0].filter.push(this.createDefaultFilter('OR', field, fieldValue)) + } + } + } this.pullRecords(true) }, @@ -1929,7 +2112,7 @@ export default { } this.recordListFilter = this.recordListFilter.concat(filter) - this.activeFilters.push(name) + this.refresh(true) if (this.$refs.filterPresets) { @@ -1937,14 +2120,9 @@ export default { } }, - removeFilter (filterIndex) { - this.activeFilters.splice(filterIndex, 1) - - if (this.drillDownFilter && !this.activeFilters.includes(this.$t('recordList.drillDown.filter.label'))) { - this.setDrillDownFilter(undefined) - } - - this.recordListFilter = this.recordListFilter.filter(({ name }) => !name || this.activeFilters.includes(name)) + removeFilter (groupIndex, filterIndex) { + this.recordListFilter = this.groupRecordListFilter + this.recordListFilter[groupIndex].filter = (this.recordListFilter[groupIndex].filter || []).filter((_, index) => index !== filterIndex) this.setStorageRecordListFilter() this.refresh(true) @@ -1972,7 +2150,6 @@ export default { this.ctr = 0 this.items = [] this.showingDeletedRecords = false - this.activeFilters = [] this.customPresetFilters = [] this.currentCustomPresetFilter = undefined this.showCustomPresetFilterModal = false @@ -2141,4 +2318,39 @@ td:hover .inline-actions { .record-list-footer { font-family: var(--font-medium); } + +.active-filter { + white-space: nowrap; + font-family: var(--font-normal); + + .field-label { + font-family: var(--font-medium); + } + + &-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + margin: 0; + } + + &-item { + vertical-align: middle; + margin: 0; + } + + &-close-btn { + vertical-align: middle; + opacity: 0.5; + + svg { + height: 0.8rem; + } + + &:hover { + opacity: 1; + } + } +} diff --git a/client/web/compose/src/lib/record-filter.js b/client/web/compose/src/lib/record-filter.js index e9cd35ad3..452068677 100644 --- a/client/web/compose/src/lib/record-filter.js +++ b/client/web/compose/src/lib/record-filter.js @@ -218,3 +218,24 @@ export function isFieldInFilter (fieldName, filter = '') { return filterCases.some(filterCase => filter.includes(filterCase)) } + +export function formatActiveFilterOperator (op) { + const operators = { + '=': 'equal', + '!=': 'notEqual', + 'IN': 'in', + 'NOT IN': 'notIn', + '>': 'greaterThan', + '<': 'lessThan', + 'LIKE': 'like', + 'NOT LIKE': 'notLike', + 'BETWEEN': 'between', + 'NOT BETWEEN': 'notBetween', + } + + return operators[op] || 'is' +} + +export function isBetweenOperator (op) { + return ['BETWEEN', 'NOT BETWEEN'].includes(op) +} diff --git a/client/web/compose/src/themes/corteza-base/custom.scss b/client/web/compose/src/themes/corteza-base/custom.scss index b9fd1a855..bb74ebe22 100644 --- a/client/web/compose/src/themes/corteza-base/custom.scss +++ b/client/web/compose/src/themes/corteza-base/custom.scss @@ -37,3 +37,9 @@ } } } + +.value-only { + .col-form-label { + padding-bottom: 0px !important; + } +} diff --git a/locale/en/corteza-webapp-compose/block.yaml b/locale/en/corteza-webapp-compose/block.yaml index d7df7a009..2f85c8f4a 100644 --- a/locale/en/corteza-webapp-compose/block.yaml +++ b/locale/en/corteza-webapp-compose/block.yaml @@ -368,9 +368,22 @@ recordList: addField: Add new filter field byValue: Filter records based on field value addFilterToPreset: Save as preset + nil: 'NULL' conditions: and: AND or: OR + operatorLabels: + is: is + equal: is equal + notEqual: is not equal + in: contains + notIn: does not contain + greaterThan: is greater than + lessThan: is less than + like: is like + notLike: not like + between: is between + notBetween: is not between deletedRecords: Deleted records field: Filter field fieldPlaceholder: Pick a field