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. 37
      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. 48
      packages/nocodb/src/db/BaseModelSqlv2.ts
  6. 2
      packages/nocodb/src/models/Sort.ts
  7. 4
      packages/nocodb/src/schema/swagger.json

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

@ -57,6 +57,7 @@ function addEmptyRow(group: Group, addAfter?: number, metaValue = meta.value) {
}
return acc
}, {} as Record<string, any>)
group.count = group.count + 1
group.rows.splice(addAfter, 0, {
row: {
@ -77,18 +78,30 @@ const formattedData = computed(() => {
return [] as Row[]
})
const { deleteRow, deleteSelectedRows, deleteRangeOfRows, updateOrSaveRow, bulkUpdateRows, selectedAllRecords, removeRowIfNew } =
useData({
meta,
viewMeta: view,
formattedData,
paginationData: ref(vGroup.value.paginationData),
callbacks: {
changePage: (p: number) => props.loadGroupPage(vGroup.value, p),
loadData: () => props.loadGroupData(vGroup.value, true),
globalCallback: () => props.redistributeRows?.(),
},
})
const {
deleteRow: _deleteRow,
deleteSelectedRows,
deleteRangeOfRows,
updateOrSaveRow,
bulkUpdateRows,
selectedAllRecords,
removeRowIfNew,
} = useData({
meta,
viewMeta: view,
formattedData,
paginationData: ref(vGroup.value.paginationData),
callbacks: {
changePage: (p: number) => props.loadGroupPage(vGroup.value, p),
loadData: () => props.loadGroupData(vGroup.value, true),
globalCallback: () => props.redistributeRows?.(),
},
})
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 }) => {
await props.loadGroupData(vGroup.value, true, {

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

@ -210,7 +210,7 @@ eventBus.on(async (event, column) => {
@click.stop
>
<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"
: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 { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { message } from 'ant-design-vue'
@ -207,6 +207,18 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
}, 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) {
try {
group = group || rootGroup.value
@ -246,7 +258,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: `${nestedWhere}`,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
sort: `${getSortParams(groupby.sort)}${groupby.column.title}`,
column_name: groupby.column.title,
} as any)
: await api.public.dataGroupBy(
@ -256,7 +268,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params,
where: nestedWhere,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
sort: `${getSortParams(groupby.sort)}${groupby.column.title}`,
column_name: groupby.column.title,
sortsArr: sorts.value,
filtersArr: nestedFilters.value,

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

@ -1,6 +1,13 @@
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) {
case UITypes.Year:
case UITypes.Number:
@ -20,16 +27,16 @@ export const getSortDirectionOptions = (uidt: UITypes | string) => {
return [
{ text: '1 → 9', value: 'asc' },
{ text: '9 → 1', value: 'desc' },
]
].concat(groupByOptions)
case UITypes.Checkbox:
return [
{ text: '▢ → ✓', value: 'asc' },
{ text: '✓ → ▢', value: 'desc' },
]
].concat(groupByOptions)
default:
return [
{ text: 'A → Z', value: 'asc' },
{ text: 'Z → A', value: 'desc' },
]
].concat(groupByOptions)
}
}

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

@ -811,17 +811,33 @@ class BaseModelSqlv2 {
return qb.toQuery();
}, this.dbDriver.raw(`??`, [columnName]).toQuery());
qb.orderBy(
sanitize(this.dbDriver.raw(finalStatement)),
sort.direction,
sort.direction === 'desc' ? 'LAST' : 'FIRST',
);
if (!['asc', 'desc'].includes(sort.direction)) {
qb.orderBy(
'count',
sort.direction === 'count-desc' ? 'desc' : 'asc',
sort.direction === 'count-desc' ? 'LAST' : 'FIRST',
);
} else {
qb.orderBy(
sanitize(this.dbDriver.raw(finalStatement)),
sort.direction,
sort.direction === 'desc' ? 'LAST' : 'FIRST',
);
}
} else {
qb.orderBy(
column.id,
sort.direction,
sort.direction === 'desc' ? 'LAST' : 'FIRST',
);
if (!['asc', 'desc'].includes(sort.direction)) {
qb.orderBy(
'count',
sort.direction === 'count-desc' ? 'desc' : 'asc',
sort.direction === 'count-desc' ? 'LAST' : 'FIRST',
);
} else {
qb.orderBy(
column.id,
sort.direction,
sort.direction === 'desc' ? 'LAST' : 'FIRST',
);
}
}
}
@ -6612,7 +6628,6 @@ export function extractSortsObject(
if (!_sorts?.length) return;
let sorts = _sorts;
if (!Array.isArray(sorts)) sorts = sorts.split(/\s*,\s*/);
return sorts.map((s) => {
@ -6620,13 +6635,20 @@ export function extractSortsObject(
if (s.startsWith('-')) {
sort.direction = 'desc';
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
else sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id;
else {
sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id;
}
if (throwErrorIfInvalid && !sort.fk_column_id)
NcError.fieldNotFound(s.replace(/^[+-]/, ''));
return new Sort(sort);
});
}

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

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

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

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

Loading…
Cancel
Save