Browse Source

feat: sort groups by records count (#8436)

* feat: group-by count sort

* fix: refactor

* fix: add new row requires manual pageload

* fix: use + for ascending sort

* fix: use tilde instead of count

* fix: groupby sort count not working in shared view

* fix: update group row count

* fix: remove imports
pull/8445/head
Anbarasu 7 months ago committed by GitHub
parent
commit
ceefe5cff9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 17
      packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
  2. 2
      packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue
  3. 18
      packages/nc-gui/composables/useViewGroupBy.ts
  4. 15
      packages/nc-gui/utils/sortUtils.ts
  5. 28
      packages/nocodb/src/db/BaseModelSqlv2.ts
  6. 2
      packages/nocodb/src/models/Sort.ts
  7. 4
      packages/nocodb/src/schema/swagger.json

17
packages/nc-gui/components/smartsheet/grid/GroupByTable.vue

@ -57,6 +57,7 @@ function addEmptyRow(group: Group, addAfter?: number, metaValue = meta.value) {
} }
return acc return acc
}, {} as Record<string, any>) }, {} as Record<string, any>)
group.count = group.count + 1
group.rows.splice(addAfter, 0, { group.rows.splice(addAfter, 0, {
row: { row: {
@ -77,8 +78,15 @@ const formattedData = computed(() => {
return [] as Row[] return [] as Row[]
}) })
const { deleteRow, deleteSelectedRows, deleteRangeOfRows, updateOrSaveRow, bulkUpdateRows, selectedAllRecords, removeRowIfNew } = const {
useData({ deleteRow: _deleteRow,
deleteSelectedRows,
deleteRangeOfRows,
updateOrSaveRow,
bulkUpdateRows,
selectedAllRecords,
removeRowIfNew,
} = useData({
meta, meta,
viewMeta: view, viewMeta: view,
formattedData, formattedData,
@ -90,6 +98,11 @@ const { deleteRow, deleteSelectedRows, deleteRangeOfRows, updateOrSaveRow, bulkU
}, },
}) })
const deleteRow = async (rowIndex: number) => {
vGroup.value.count = vGroup.value.count - 1
await _deleteRow(rowIndex)
}
const reloadTableData = async (params: void | { shouldShowLoading?: boolean | undefined; offset?: number | undefined }) => { const reloadTableData = async (params: void | { shouldShowLoading?: boolean | undefined; offset?: number | undefined }) => {
await props.loadGroupData(vGroup.value, true, { await props.loadGroupData(vGroup.value, true, {
...(params?.offset !== undefined ? { offset: params.offset } : {}), ...(params?.offset !== undefined ? { offset: params.offset } : {}),

2
packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue

@ -210,7 +210,7 @@ eventBus.on(async (event, column) => {
@click.stop @click.stop
> >
<a-select-option <a-select-option
v-for="(option, j) of getSortDirectionOptions(getColumnUidtByID(group.fk_column_id))" v-for="(option, j) of getSortDirectionOptions(getColumnUidtByID(group.fk_column_id), true)"
:key="j" :key="j"
:value="option.value" :value="option.value"
> >

18
packages/nc-gui/composables/useViewGroupBy.ts

@ -1,5 +1,5 @@
import { UITypes } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
@ -207,6 +207,18 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
}, existing) }, existing)
} }
const getSortParams = (sort: string) => {
if (sort === 'asc') {
return '+'
} else if (sort === 'desc') {
return '-'
} else if (sort === 'count-asc') {
return '~+'
} else if (sort === 'count-desc') {
return '~-'
}
}
async function loadGroups(params: any = {}, group?: Group) { async function loadGroups(params: any = {}, group?: Group) {
try { try {
group = group || rootGroup.value group = group || rootGroup.value
@ -246,7 +258,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: `${nestedWhere}`, where: `${nestedWhere}`,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, sort: `${getSortParams(groupby.sort)}${groupby.column.title}`,
column_name: groupby.column.title, column_name: groupby.column.title,
} as any) } as any)
: await api.public.dataGroupBy( : await api.public.dataGroupBy(
@ -256,7 +268,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
limit: group.paginationData.pageSize ?? groupByGroupLimit.value, limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params, ...params,
where: nestedWhere, where: nestedWhere,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, sort: `${getSortParams(groupby.sort)}${groupby.column.title}`,
column_name: groupby.column.title, column_name: groupby.column.title,
sortsArr: sorts.value, sortsArr: sorts.value,
filtersArr: nestedFilters.value, filtersArr: nestedFilters.value,

15
packages/nc-gui/utils/sortUtils.ts

@ -1,6 +1,13 @@
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
export const getSortDirectionOptions = (uidt: UITypes | string) => { export const getSortDirectionOptions = (uidt: UITypes | string, isGroupBy?: boolean) => {
const groupByOptions = isGroupBy
? [
{ text: 'Count (9 → 1)', value: 'count-desc' },
{ text: 'Count (1 → 9)', value: 'count-asc' },
]
: []
switch (uidt) { switch (uidt) {
case UITypes.Year: case UITypes.Year:
case UITypes.Number: case UITypes.Number:
@ -20,16 +27,16 @@ export const getSortDirectionOptions = (uidt: UITypes | string) => {
return [ return [
{ text: '1 → 9', value: 'asc' }, { text: '1 → 9', value: 'asc' },
{ text: '9 → 1', value: 'desc' }, { text: '9 → 1', value: 'desc' },
] ].concat(groupByOptions)
case UITypes.Checkbox: case UITypes.Checkbox:
return [ return [
{ text: '▢ → ✓', value: 'asc' }, { text: '▢ → ✓', value: 'asc' },
{ text: '✓ → ▢', value: 'desc' }, { text: '✓ → ▢', value: 'desc' },
] ].concat(groupByOptions)
default: default:
return [ return [
{ text: 'A → Z', value: 'asc' }, { text: 'A → Z', value: 'asc' },
{ text: 'Z → A', value: 'desc' }, { text: 'Z → A', value: 'desc' },
] ].concat(groupByOptions)
} }
} }

28
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -811,11 +811,26 @@ class BaseModelSqlv2 {
return qb.toQuery(); return qb.toQuery();
}, this.dbDriver.raw(`??`, [columnName]).toQuery()); }, this.dbDriver.raw(`??`, [columnName]).toQuery());
if (!['asc', 'desc'].includes(sort.direction)) {
qb.orderBy(
'count',
sort.direction === 'count-desc' ? 'desc' : 'asc',
sort.direction === 'count-desc' ? 'LAST' : 'FIRST',
);
} else {
qb.orderBy( qb.orderBy(
sanitize(this.dbDriver.raw(finalStatement)), sanitize(this.dbDriver.raw(finalStatement)),
sort.direction, sort.direction,
sort.direction === 'desc' ? 'LAST' : 'FIRST', sort.direction === 'desc' ? 'LAST' : 'FIRST',
); );
}
} else {
if (!['asc', 'desc'].includes(sort.direction)) {
qb.orderBy(
'count',
sort.direction === 'count-desc' ? 'desc' : 'asc',
sort.direction === 'count-desc' ? 'LAST' : 'FIRST',
);
} else { } else {
qb.orderBy( qb.orderBy(
column.id, column.id,
@ -824,6 +839,7 @@ class BaseModelSqlv2 {
); );
} }
} }
}
// group by using the column aliases // group by using the column aliases
qb.groupBy(...groupBySelectors); qb.groupBy(...groupBySelectors);
@ -6612,7 +6628,6 @@ export function extractSortsObject(
if (!_sorts?.length) return; if (!_sorts?.length) return;
let sorts = _sorts; let sorts = _sorts;
if (!Array.isArray(sorts)) sorts = sorts.split(/\s*,\s*/); if (!Array.isArray(sorts)) sorts = sorts.split(/\s*,\s*/);
return sorts.map((s) => { return sorts.map((s) => {
@ -6620,13 +6635,20 @@ export function extractSortsObject(
if (s.startsWith('-')) { if (s.startsWith('-')) {
sort.direction = 'desc'; sort.direction = 'desc';
sort.fk_column_id = aliasColObjMap[s.slice(1)]?.id; sort.fk_column_id = aliasColObjMap[s.slice(1)]?.id;
} else if (s.startsWith('~-')) {
sort.direction = 'count-desc';
sort.fk_column_id = aliasColObjMap[s.slice(2)]?.id;
} else if (s.startsWith('~+')) {
sort.direction = 'count-asc';
sort.fk_column_id = aliasColObjMap[s.slice(2)]?.id;
} }
// replace + at the beginning if present // replace + at the beginning if present
else sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id; else {
sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id;
}
if (throwErrorIfInvalid && !sort.fk_column_id) if (throwErrorIfInvalid && !sort.fk_column_id)
NcError.fieldNotFound(s.replace(/^[+-]/, '')); NcError.fieldNotFound(s.replace(/^[+-]/, ''));
return new Sort(sort); return new Sort(sort);
}); });
} }

2
packages/nocodb/src/models/Sort.ts

@ -17,7 +17,7 @@ export default class Sort {
fk_view_id: string; fk_view_id: string;
fk_column_id?: string; fk_column_id?: string;
direction?: 'asc' | 'desc'; direction?: 'asc' | 'desc' | 'count-desc' | 'count-asc';
base_id?: string; base_id?: string;
source_id?: string; source_id?: string;

4
packages/nocodb/src/schema/swagger.json

@ -23351,7 +23351,9 @@
"description": "Sort direction", "description": "Sort direction",
"enum": [ "enum": [
"asc", "asc",
"desc" "desc",
"count-desc",
"count-asc"
], ],
"example": "desc" "example": "desc"
}, },

Loading…
Cancel
Save