Browse Source

Merge pull request #6853 from nocodb/enhancement/rollup

enhancement: rollup
pull/7189/head
Raju Udava 12 months ago committed by GitHub
parent
commit
2389b7f88d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 91
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  2. 67
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  3. 41
      packages/nc-gui/components/virtual-cell/Rollup.vue

91
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -2,21 +2,39 @@
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { ListItem as AntListItem } from 'ant-design-vue' import type { ListItem as AntListItem } from 'ant-design-vue'
import jsep from 'jsep' import jsep from 'jsep'
import type { ColumnType, FormulaType } from 'nocodb-sdk' import type { ColumnType, FormulaType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, jsepCurlyHook, substituteColumnIdWithAliasInFormula, validateDateWithUnknownFormat } from 'nocodb-sdk' import {
UITypes,
isLinksOrLTAR,
isNumericCol,
isSystemColumn,
jsepCurlyHook,
substituteColumnIdWithAliasInFormula,
validateDateWithUnknownFormat,
} from 'nocodb-sdk'
import { import {
MetaInj, MetaInj,
NcAutocompleteTree, NcAutocompleteTree,
computed,
formulaList, formulaList,
formulaTypes, formulaTypes,
formulas, formulas,
getUIDTIcon, getUIDTIcon,
getWordUntilCaret, getWordUntilCaret,
iconMap, iconMap,
inject,
insertAtCursor, insertAtCursor,
isDate,
nextTick,
onMounted, onMounted,
ref,
storeToRefs,
useBase,
useColumnCreateStoreOrThrow, useColumnCreateStoreOrThrow,
useDebounceFn, useDebounceFn,
useI18n,
useMetas,
useNocoEe,
useVModel, useVModel,
} from '#imports' } from '#imports'
@ -34,6 +52,10 @@ const { setAdditionalValidations, validateInfos, sqlUi, column } = useColumnCrea
const { t } = useI18n() const { t } = useI18n()
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { predictFunction: _predictFunction } = useNocoEe() const { predictFunction: _predictFunction } = useNocoEe()
enum JSEPNode { enum JSEPNode {
@ -53,6 +75,23 @@ const meta = inject(MetaInj, ref())
const supportedColumns = computed( const supportedColumns = computed(
() => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [], () => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [],
) )
const { metas } = useMetas()
const refTables = computed(() => {
if (!tables.value || !tables.value.length || !meta.value || !meta.value.columns) {
return []
}
const _refTables = meta.value.columns
.filter((column) => isLinksOrLTAR(column) && !column.system && column.source_id === meta.value?.source_id)
.map((column) => ({
col: column.colOptions,
column,
...tables.value.find((table) => table.id === (column.colOptions as LinkToAnotherRecordType).fk_related_model_id),
}))
.filter((table) => (table.col as LinkToAnotherRecordType)?.fk_related_model_id === table.id && !table.mm)
return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[]
})
const validators = { const validators = {
formula_raw: [ formula_raw: [
@ -500,6 +539,53 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
} }
break break
case UITypes.Rollup: {
const rollupFunction = col.colOptions.rollup_function
if (['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct'].includes(rollupFunction)) {
// these functions produce a numeric value, which can be used in numeric functions
if (expectedType !== formulaTypes.NUMERIC) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: formulaTypes.NUMERIC,
expectedType,
}),
)
}
} else {
// the value is based on the foreign rollup column type
const selectedTable = refTables.value.find((t) => t.column.id === col.colOptions.fk_relation_column_id)
const refTableColumns = metas.value[selectedTable.id].columns.filter(
(c: ColumnType) =>
vModel.value.fk_lookup_column_id === c.id ||
(!isSystemColumn(c) && c.id !== vModel.value.id && c.uidt !== UITypes.Links),
)
const childFieldColumn = refTableColumns.find(
(column: ColumnType) => column.id === col.colOptions.fk_rollup_column_id,
)
const abstractType = sqlUi.value.getAbstractType(childFieldColumn)
if (expectedType === formulaTypes.DATE && !isDate(childFieldColumn, sqlUi.value.getAbstractType(childFieldColumn))) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: abstractType,
expectedType,
}),
)
} else if (expectedType === formulaTypes.NUMERIC && !isNumericCol(childFieldColumn)) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: abstractType,
expectedType,
}),
)
}
}
break
}
// not supported // not supported
case UITypes.ForeignKey: case UITypes.ForeignKey:
case UITypes.Attachment: case UITypes.Attachment:
@ -507,7 +593,6 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.Time: case UITypes.Time:
case UITypes.Percent: case UITypes.Percent:
case UITypes.Duration: case UITypes.Duration:
case UITypes.Rollup:
case UITypes.Lookup: case UITypes.Lookup:
case UITypes.Barcode: case UITypes.Barcode:
case UITypes.Button: case UITypes.Button:

67
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -1,8 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from '@vue/runtime-core' import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType, UITypes } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, TableType, UITypes } from 'nocodb-sdk'
import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import { isLinksOrLTAR, isNumericCol, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, inject, ref, storeToRefs, useBase, useColumnCreateStoreOrThrow, useMetas, useVModel } from '#imports' import type { Ref } from '#imports'
import {
MetaInj,
computed,
h,
inject,
ref,
resolveComponent,
storeToRefs,
useBase,
useColumnCreateStoreOrThrow,
useI18n,
useMetas,
useVModel,
watch,
} from '#imports'
const props = defineProps<{ const props = defineProps<{
value: any value: any
@ -17,6 +32,7 @@ const meta = inject(MetaInj, ref())
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow() const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const baseStore = useBase() const baseStore = useBase()
const { tables } = storeToRefs(baseStore) const { tables } = storeToRefs(baseStore)
const { metas } = useMetas() const { metas } = useMetas()
@ -29,17 +45,6 @@ setAdditionalValidations({
rollup_function: [{ required: true, message: t('general.required') }], rollup_function: [{ required: true, message: t('general.required') }],
}) })
const aggrFunctionsList = [
{ text: t('datatype.Count'), value: 'count' },
{ text: t('general.min'), value: 'min' },
{ text: t('general.max'), value: 'max' },
{ text: t('general.avg'), value: 'avg' },
{ text: t('general.sum'), value: 'sum' },
{ text: t('general.countDistinct'), value: 'countDistinct' },
{ text: t('general.sumDistinct'), value: 'sumDistinct' },
{ text: t('general.avgDistinct'), value: 'avgDistinct' },
]
if (!vModel.value.fk_relation_column_id) vModel.value.fk_relation_column_id = null if (!vModel.value.fk_relation_column_id) vModel.value.fk_relation_column_id = null
if (!vModel.value.fk_rollup_column_id) vModel.value.fk_rollup_column_id = null if (!vModel.value.fk_rollup_column_id) vModel.value.fk_rollup_column_id = null
if (!vModel.value.rollup_function) vModel.value.rollup_function = null if (!vModel.value.rollup_function) vModel.value.rollup_function = null
@ -94,6 +99,40 @@ const cellIcon = (column: ColumnType) =>
h(isVirtualCol(column) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), { h(isVirtualCol(column) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), {
columnMeta: column, columnMeta: column,
}) })
const aggFunctionsList: Ref<Record<string, string>[]> = ref([])
watch(
() => vModel.value.fk_rollup_column_id,
() => {
const childFieldColumn = columns.value?.find((column: ColumnType) => column.id === vModel.value.fk_rollup_column_id)
const showNumericFunctions = isNumericCol(childFieldColumn)
const nonNumericFunctions = [
// functions for non-numeric types,
// e.g. count / min / max / countDistinct date field
{ text: t('datatype.Count'), value: 'count' },
{ text: t('general.min'), value: 'min' },
{ text: t('general.max'), value: 'max' },
{ text: t('general.countDistinct'), value: 'countDistinct' },
]
const numericFunctions = showNumericFunctions
? [
{ text: t('general.avg'), value: 'avg' },
{ text: t('general.sum'), value: 'sum' },
{ text: t('general.sumDistinct'), value: 'sumDistinct' },
{ text: t('general.avgDistinct'), value: 'avgDistinct' },
]
: []
aggFunctionsList.value = [...nonNumericFunctions, ...numericFunctions]
if (!showNumericFunctions && ['avg', 'sum', 'sumDistinct', 'avgDistinct'].includes(vModel.value.rollup_function)) {
// when the previous roll up function was numeric type and the current child field is non-numeric
// reset rollup function with a non-numeric type
vModel.value.rollup_function = aggFunctionsList.value[0].value
}
},
)
</script> </script>
<template> <template>
@ -141,7 +180,7 @@ const cellIcon = (column: ColumnType) =>
dropdown-class-name="nc-dropdown-rollup-function" dropdown-class-name="nc-dropdown-rollup-function"
@change="onDataTypeChange" @change="onDataTypeChange"
> >
<a-select-option v-for="(func, index) of aggrFunctionsList" :key="index" :value="func.value"> <a-select-option v-for="(func, index) of aggFunctionsList" :key="index" :value="func.value">
{{ func.text }} {{ func.text }}
</a-select-option> </a-select-option>
</a-select> </a-select>

41
packages/nc-gui/components/virtual-cell/Rollup.vue

@ -1,18 +1,51 @@
<script setup lang="ts"> <script setup lang="ts">
import { CellValueInj, inject, useShowNotEditableWarning } from '#imports' import type { ColumnType, LinkToAnotherRecordType, RollupType } from 'nocodb-sdk'
import { CellValueInj, ColumnInj, MetaInj, computed, inject, isRollup, ref, useMetas, useShowNotEditableWarning } from '#imports'
const { metas } = useMetas()
const value = inject(CellValueInj) const value = inject(CellValueInj)
const column = inject(ColumnInj)!
const meta = inject(MetaInj, ref())
const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activateShowEditNonEditableFieldWarning } = const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activateShowEditNonEditableFieldWarning } =
useShowNotEditableWarning() useShowNotEditableWarning()
const relationColumnOptions = computed<LinkToAnotherRecordType | null>(() => {
if ((column?.value?.colOptions as RollupType)?.fk_relation_column_id) {
return meta?.value?.columns?.find((c) => c.id === (column?.value?.colOptions as RollupType)?.fk_relation_column_id)
?.colOptions as LinkToAnotherRecordType
}
return null
})
const relatedTableMeta = computed(
() =>
relationColumnOptions.value?.fk_related_model_id && metas.value?.[relationColumnOptions.value?.fk_related_model_id as string],
)
const colOptions = computed(() => column.value?.colOptions)
const childColumn = computed(() => {
if (relatedTableMeta.value?.columns) {
if (isRollup(column.value)) {
return relatedTableMeta.value?.columns.find(
(c: ColumnType) => c.id === (colOptions.value as RollupType).fk_rollup_column_id,
)
}
}
return ''
})
</script> </script>
<template> <template>
<div @dblclick="activateShowEditNonEditableFieldWarning"> <div @dblclick="activateShowEditNonEditableFieldWarning">
<span class="text-center pl-3"> <div v-if="['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct'].includes(colOptions.rollup_function)">
{{ value }} {{ value }}
</span> </div>
<LazySmartsheetCell v-else v-model="value" :column="childColumn" :edit-enabled="false" :read-only="true" />
<div> <div>
<div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs"> <div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.info.computedFieldEditWarning') }} {{ $t('msg.info.computedFieldEditWarning') }}

Loading…
Cancel
Save