Browse Source

feat: createdBy & lastModifiedBy

pull/7373/head
mertmit 11 months ago
parent
commit
97b956f4ee
  1. 22
      packages/nc-gui/components/cell/ReadOnlyUser.vue
  2. 2
      packages/nc-gui/components/smartsheet/Form.vue
  3. 3
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  4. 2
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  5. 2
      packages/nc-gui/components/smartsheet/grid/GroupBy.vue
  6. 16
      packages/nc-gui/components/smartsheet/grid/Table.vue
  7. 3
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  8. 2
      packages/nc-gui/components/template/Editor.vue
  9. 2
      packages/nc-gui/components/virtual-cell/Lookup.vue
  10. 2
      packages/nc-gui/composables/useData.ts
  11. 4
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  12. 2
      packages/nc-gui/composables/useMultiSelect/index.ts
  13. 8
      packages/nc-gui/composables/useViewGroupBy.ts
  14. 2
      packages/nc-gui/utils/cell.ts
  15. 8
      packages/nc-gui/utils/columnUtils.ts
  16. 40
      packages/nc-gui/utils/filterUtils.ts
  17. 16
      packages/nocodb-sdk/src/lib/UITypes.ts
  18. 3
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  19. 1
      packages/nocodb-sdk/src/lib/index.ts
  20. 46
      packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
  21. 46
      packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
  22. 55
      packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
  23. 55
      packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
  24. 50
      packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
  25. 194
      packages/nocodb/src/db/BaseModelSqlv2.ts
  26. 4
      packages/nocodb/src/db/conditionV2.ts
  27. 2
      packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
  28. 4
      packages/nocodb/src/db/sortV2.ts
  29. 2
      packages/nocodb/src/helpers/getAst.ts
  30. 4
      packages/nocodb/src/modules/datas/helpers.ts
  31. 2
      packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts
  32. 6
      packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts
  33. 8
      packages/nocodb/src/schema/swagger-v2.json
  34. 8
      packages/nocodb/src/schema/swagger.json
  35. 4
      packages/nocodb/src/services/api-docs/swagger/getSwaggerColumnMetas.ts
  36. 4
      packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts
  37. 45
      packages/nocodb/src/services/columns.service.ts
  38. 46
      packages/nocodb/src/services/tables.service.ts
  39. 2
      packages/nocodb/src/version-upgrader/NcUpgrader.ts
  40. 125
      packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedByUpgrader.ts

22
packages/nc-gui/components/cell/ReadOnlyUser.vue

@ -0,0 +1,22 @@
<script setup lang="ts">
import { ActiveCellInj, EditModeInj, ReadonlyInj, provide, ref } from '#imports'
interface Props {
modelValue?: string | null
}
defineProps<Props>()
provide(ReadonlyInj, ref(true))
provide(EditModeInj, ref(true))
provide(ActiveCellInj, ref(true))
</script>
<template>
<div class="relative">
<LazyCellUser class="z-0" :model-value="modelValue" />
<div class="w-full h-full z-1 absolute top-0 left-0"></div>
</div>
</template>

2
packages/nc-gui/components/smartsheet/Form.vue

@ -43,6 +43,8 @@ const hiddenColTypes = [
UITypes.SpecificDBType,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
]
const { isMobileMode, user } = useGlobal()

3
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import { isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk'
import { isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk'
import {
ActiveCellInj,
CellValueInj,
@ -118,6 +118,7 @@ onUnmounted(() => {
<LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup(column)" />
<LazyCellReadOnlyDateTimePicker v-else-if="isCreatedOrLastModifiedTimeCol(column)" :model-value="modelValue" />
<LazyCellReadOnlyUser v-else-if="isCreatedOrLastModifiedByCol(column)" :model-value="modelValue" />
</template>
</div>
</template>

2
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -86,6 +86,8 @@ const onlyNameUpdateOnEditColumns = [
UITypes.Links,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
]
// To close column type dropdown on escape and

2
packages/nc-gui/components/smartsheet/grid/GroupBy.vue

@ -192,6 +192,8 @@ const shouldRenderCell = (column) =>
UITypes.DateTime,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(column?.uidt)
</script>

16
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -2,7 +2,15 @@
import axios from 'axios'
import { nextTick } from '@vue/runtime-core'
import { type ColumnReqType, type ColumnType, type PaginatedType, type TableType, type ViewType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isCreatedOrLastModifiedTimeCol, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import {
UITypes,
ViewTypes,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isSystemColumn,
isVirtualCol,
} from 'nocodb-sdk'
import { useColumnDrag } from './useColumnDrag'
import usePaginationShortcuts from './usePaginationShortcuts'
@ -1011,7 +1019,8 @@ const showFillHandle = computed(
isLookup(fields.value[activeCell.col]) ||
isRollup(fields.value[activeCell.col]) ||
isFormula(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedTimeCol(fields.value[activeCell.col])
isCreatedOrLastModifiedTimeCol(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedByCol(fields.value[activeCell.col])
),
)
@ -1574,7 +1583,8 @@ onKeyStroke('ArrowDown', onDown)
(isLookup(columnObj) ||
isRollup(columnObj) ||
isFormula(columnObj) ||
isCreatedOrLastModifiedTimeCol(columnObj)) &&
isCreatedOrLastModifiedTimeCol(columnObj) ||
isCreatedOrLastModifiedByCol(columnObj)) &&
hasEditPermission &&
isCellSelected(rowIndex, colIndex),
'!border-r-blue-400 !border-r-3': toBeDroppedColId === columnObj.id,

3
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -65,6 +65,9 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
case UITypes.CreatedTime:
case UITypes.LastModifiedTime:
return { icon: iconMap.datetime, color: 'text-grey' }
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
return { icon: iconMap.account, color: 'text-grey' }
}
return { icon: iconMap.generic, color: 'text-grey' }

2
packages/nc-gui/components/template/Editor.vue

@ -105,6 +105,8 @@ const uiTypeOptions = ref<Option[]>(
UITypes.ID,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Barcode,
UITypes.Button,
].includes(UITypes[uiType]),

2
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -171,6 +171,8 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
UITypes.MultiSelect,
UITypes.SingleSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(lookupColumn.uidt),
'min-h-0 min-w-0': isAttachment(lookupColumn),
}"

2
packages/nc-gui/composables/useData.ts

@ -244,6 +244,7 @@ export function useData(args: {
col.uidt === UITypes.Checkbox ||
col.uidt === UITypes.User ||
col.uidt === UITypes.LastModifiedTime ||
col.uidt === UITypes.LastModifiedBy ||
col.uidt === UITypes.Lookup ||
col.au ||
(col.cdf && / on update /i.test(col.cdf)))
@ -393,6 +394,7 @@ export function useData(args: {
UITypes.Lookup,
UITypes.Rollup,
UITypes.LinkToAnotherRecord,
UITypes.LastModifiedBy,
].includes(col.uidt),
)
) {

4
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -195,7 +195,9 @@ export default function convertCellData(
return validVals.join(',')
}
case UITypes.User: {
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy: {
let parsedVal
try {
try {

2
packages/nc-gui/composables/useMultiSelect/index.ts

@ -112,7 +112,7 @@ export function useMultiSelect(
textToCopy = !!textToCopy
}
if (columnObj.uidt === UITypes.User) {
if (columnObj.uidt === UITypes.User || columnObj.uidt === UITypes.CreatedBy || columnObj.uidt === UITypes.LastModifiedBy) {
if (textToCopy && Array.isArray(textToCopy)) {
textToCopy = textToCopy
.map((user: UserFieldRecordType) => {

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

@ -90,7 +90,7 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE
}
if (col.uidt === UITypes.User) {
if (col.uidt === UITypes.User || col.uidt === UITypes.CreatedBy || col.uidt === UITypes.LastModifiedBy) {
if (!value) {
return GROUP_BY_VARS.NULL
}
@ -161,7 +161,11 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
acc += `${acc.length ? '~and' : ''}(${curr.title},${curr.key === GROUP_BY_VARS.TRUE ? 'checked' : 'notchecked'})`
} else if ([UITypes.Date, UITypes.DateTime].includes(curr.column_uidt as UITypes)) {
acc += `${acc.length ? '~and' : ''}(${curr.title},eq,exactDate,${curr.key})`
} else if (curr.column_uidt === UITypes.User) {
} else if (
curr.column_uidt === UITypes.User ||
curr.column_uidt === UITypes.CreatedBy ||
curr.column_uidt === UITypes.LastModifiedBy
) {
try {
const value = JSON.parse(curr.key)
acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${value.map((v: any) => v.id).join(',')})`

2
packages/nc-gui/utils/cell.ts

@ -17,6 +17,8 @@ export const isDateTime = (column: ColumnType, abstractType: any) =>
abstractType === 'datetime' || column.uidt === UITypes.DateTime
export const isReadonlyDateTime = (column: ColumnType, _abstractType: any) =>
column.uidt === UITypes.CreatedTime || column.uidt === UITypes.LastModifiedTime
export const isReadonlyUser = (column: ColumnType, _abstractType: any) =>
column.uidt === UITypes.CreatedBy || column.uidt === UITypes.LastModifiedBy
export const isJSON = (column: ColumnType) => column.uidt === UITypes.JSON
export const isEnum = (column: ColumnType) => column.uidt === UITypes.SingleSelect
export const isSingleSelect = (column: ColumnType) => column.uidt === UITypes.SingleSelect

8
packages/nc-gui/utils/columnUtils.ts

@ -146,6 +146,14 @@ const uiTypes = [
name: UITypes.LastModifiedTime,
icon: iconMap.datetime,
},
{
name: UITypes.CreatedBy,
icon: iconMap.account,
},
{
name: UITypes.LastModifiedBy,
icon: iconMap.account,
},
]
const getUIDTIcon = (uidt: UITypes | string) => {

40
packages/nc-gui/utils/filterUtils.ts

@ -95,13 +95,27 @@ export const comparisonOpList = (
text: getEqText(fieldUiType),
value: 'eq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment, UITypes.User],
excludedTypes: [
UITypes.Checkbox,
UITypes.MultiSelect,
UITypes.Attachment,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
],
},
{
text: getNeqText(fieldUiType),
value: 'neq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment, UITypes.User],
excludedTypes: [
UITypes.Checkbox,
UITypes.MultiSelect,
UITypes.Attachment,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
],
},
{
text: getLikeText(fieldUiType),
@ -112,6 +126,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
@ -128,6 +144,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
@ -144,6 +162,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
@ -163,6 +183,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
@ -183,6 +205,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
@ -202,6 +226,8 @@ export const comparisonOpList = (
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
@ -215,25 +241,25 @@ export const comparisonOpList = (
text: 'contains all of',
value: 'allof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.User],
includedTypes: [UITypes.MultiSelect, UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy],
},
{
text: 'contains any of',
value: 'anyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect, UITypes.User],
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect, UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy],
},
{
text: 'does not contain all of',
value: 'nallof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.User],
includedTypes: [UITypes.MultiSelect, UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy],
},
{
text: 'does not contain any of',
value: 'nanyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect, UITypes.User],
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect, UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy],
},
{
text: getGtText(fieldUiType),
@ -382,7 +408,7 @@ export const comparisonSubOpList = (
text: 'yesterday',
value: 'yesterday',
ignoreVal: true,
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTim, UITypes.LastModifiedTime, UITypes.CreatedTimee])],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime, UITypes.LastModifiedTime, UITypes.CreatedTime])],
},
{
text: 'one week ago',

16
packages/nocodb-sdk/src/lib/UITypes.ts

@ -40,6 +40,8 @@ enum UITypes {
Button = 'Button',
Links = 'Links',
User = 'User',
CreatedBy = 'CreatedBy',
LastModifiedBy = 'LastModifiedBy',
}
export const numericUITypes = [
@ -85,6 +87,8 @@ export function isVirtualCol(
UITypes.Links,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
// UITypes.Count,
].includes(<UITypes>(typeof col === 'object' ? col?.uidt : col));
}
@ -100,6 +104,18 @@ export function isCreatedOrLastModifiedTimeCol(
);
}
export function isCreatedOrLastModifiedByCol(
col:
| UITypes
| { readonly uidt: UITypes | string }
| ColumnReqType
| ColumnType
) {
return [UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
<UITypes>(typeof col === 'object' ? col?.uidt : col)
);
}
export function isLinksOrLTAR(
colOrUidt: ColumnType | { uidt: UITypes | string } | UITypes | string
) {

3
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -1514,6 +1514,9 @@ async function extractColumnIdentifierType({
case UITypes.PhoneNumber:
case UITypes.Email:
case UITypes.URL:
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
res.dataType = FormulaDataTypes.STRING;
break;
// numeric

1
packages/nocodb-sdk/src/lib/index.ts

@ -14,6 +14,7 @@ export {
isVirtualCol,
isLinksOrLTAR,
isCreatedOrLastModifiedTimeCol,
isCreatedOrLastModifiedByCol,
} from '~/lib/UITypes';
export { default as CustomAPI, FileType } from '~/lib/CustomAPI';
export { default as TemplateGenerator } from '~/lib/TemplateGenerator';

46
packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts

@ -135,6 +135,52 @@ export class MssqlUi {
uicn: '',
system: true,
},
{
column_name: 'created_by',
title: 'CreatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.CreatedBy,
uip: '',
uicn: '',
system: true,
},
{
column_name: 'updated_by',
title: 'UpdatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.LastModifiedBy,
uip: '',
uicn: '',
system: true,
},
];
}

46
packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts

@ -139,6 +139,52 @@ export class MysqlUi {
uicn: '',
system: true,
},
{
column_name: 'created_by',
title: 'CreatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.CreatedBy,
uip: '',
uicn: '',
system: true,
},
{
column_name: 'updated_by',
title: 'UpdatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.LastModifiedBy,
uip: '',
uicn: '',
system: true,
},
];
}

55
packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts

@ -197,6 +197,52 @@ export class PgUi {
uicn: '',
system: true,
},
{
column_name: 'created_by',
title: 'CreatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.CreatedBy,
uip: '',
uicn: '',
system: true,
},
{
column_name: 'updated_by',
title: 'UpdatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.LastModifiedBy,
uip: '',
uicn: '',
system: true,
},
];
}
@ -1568,7 +1614,7 @@ export class PgUi {
}
}
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@ -1709,7 +1755,7 @@ export class PgUi {
return colProp;
}
static getDataTypeListForUiType(col: { uidt?: UITypes }, idType: IDType) {
static getDataTypeListForUiType(col: { uidt?: UITypes; }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@ -1941,6 +1987,11 @@ export class PgUi {
'timestamp with time zone',
];
case 'User':
case 'CreatedBy':
case 'LastModifiedBy':
return ['character varying'];
case 'AutoNumber':
return [
'int',

55
packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts

@ -133,6 +133,52 @@ export class SnowflakeUi {
uicn: '',
system: true,
},
{
column_name: 'created_by',
title: 'CreatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.CreatedBy,
uip: '',
uicn: '',
system: true,
},
{
column_name: 'updated_by',
title: 'UpdatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.LastModifiedBy,
uip: '',
uicn: '',
system: true,
},
];
}
@ -660,7 +706,7 @@ export class SnowflakeUi {
}
}
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@ -800,7 +846,7 @@ export class SnowflakeUi {
return colProp;
}
static getDataTypeListForUiType(col: { uidt: UITypes }, idType: IDType) {
static getDataTypeListForUiType(col: { uidt: UITypes; }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@ -955,6 +1001,11 @@ export class SnowflakeUi {
case 'LastModifiedTime':
return ['TIMESTAMP'];
case 'User':
case 'CreatedBy':
case 'LastModifiedBy':
return ['VARCHAR'];
case 'AutoNumber':
return ['NUMBER', 'INT', 'INTEGER', 'BIGINT'];

50
packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts

@ -121,6 +121,52 @@ export class SqliteUi {
uicn: '',
system: true,
},
{
column_name: 'created_by',
title: 'CreatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.CreatedBy,
uip: '',
uicn: '',
system: true,
},
{
column_name: 'updated_by',
title: 'UpdatedBy',
dt: 'varchar',
dtx: 'specificType',
ct: 'varchar',
nrqd: true,
rqd: false,
ck: false,
pk: false,
un: false,
ai: false,
clen: 45,
np: null,
ns: null,
dtxp: '',
dtxs: '',
altered: 1,
uidt: UITypes.LastModifiedBy,
uip: '',
uicn: '',
system: true,
},
];
}
@ -510,7 +556,7 @@ export class SqliteUi {
}
}
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@ -651,7 +697,7 @@ export class SqliteUi {
return colProp;
}
static getDataTypeListForUiType(col: { uidt: UITypes }, idType?: IDType) {
static getDataTypeListForUiType(col: { uidt: UITypes; }, idType?: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {

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

@ -8,6 +8,7 @@ import { nocoExecute } from 'nc-help';
import {
AuditOperationSubTypes,
AuditOperationTypes,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isSystemColumn,
@ -103,7 +104,11 @@ function checkColumnRequired(
}
export async function getColumnName(column: Column<any>, columns?: Column[]) {
if (!isCreatedOrLastModifiedTimeCol(column)) return column.column_name;
if (
!isCreatedOrLastModifiedTimeCol(column) &&
!isCreatedOrLastModifiedByCol(column)
)
return column.column_name;
columns = columns || (await Column.list({ fk_model_id: column.fk_model_id }));
switch (column.uidt) {
@ -122,6 +127,20 @@ export async function getColumnName(column: Column<any>, columns?: Column[]) {
return lastModifiedTimeSystemCol.column_name;
return column.column_name || 'updated_at';
}
case UITypes.CreatedBy: {
const createdBySystemCol = columns.find(
(col) => col.system && col.uidt === UITypes.CreatedBy,
);
if (createdBySystemCol) return createdBySystemCol.column_name;
return column.column_name || 'created_by';
}
case UITypes.LastModifiedBy: {
const lastModifiedBySystemCol = columns.find(
(col) => col.system && col.uidt === UITypes.LastModifiedBy,
);
if (lastModifiedBySystemCol) return lastModifiedBySystemCol.column_name;
return column.column_name || 'updated_by';
}
default:
return column.column_name;
}
@ -739,7 +758,16 @@ class BaseModelSqlv2 {
const column = groupByColumns[sort.fk_column_id];
if (column.uidt === UITypes.User) {
if (
[UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
column.uidt as UITypes,
)
) {
const columnName = await getColumnName(
column,
await this.model.getColumns(),
);
const baseUsers = await BaseUser.getUsersList({
base_id: column.base_id,
});
@ -751,7 +779,7 @@ class BaseModelSqlv2 {
user.display_name || user.email,
]);
return qb.toQuery();
}, this.dbDriver.raw(`??`, [column.column_name]).toQuery());
}, this.dbDriver.raw(`??`, [columnName]).toQuery());
qb.orderBy(
sanitize(this.dbDriver.raw(finalStatement)),
@ -2314,6 +2342,18 @@ class BaseModelSqlv2 {
).builder.as(sanitize(column.id)),
);
break;
case UITypes.CreatedBy:
case UITypes.LastModifiedBy: {
const columnName = await getColumnName(
column,
await this.model.getColumns(),
);
res[sanitize(column.id || columnName)] = sanitize(
`${alias || this.tnPath}.${columnName}`,
);
break;
}
default:
if (this.isPg) {
if (column.dt === 'bytea') {
@ -2368,7 +2408,7 @@ class BaseModelSqlv2 {
await this.beforeInsert(insertObj, trx, cookie);
}
await this.prepareNocoData(insertObj, true);
await this.prepareNocoData(insertObj, true, cookie);
let response;
// const driver = trx ? trx : this.dbDriver;
@ -2613,7 +2653,7 @@ class BaseModelSqlv2 {
await this.beforeUpdate(data, trx, cookie);
await this.prepareNocoData(updateObj);
await this.prepareNocoData(updateObj, false, cookie);
const prevData = await this.readByPk(
id,
@ -2734,7 +2774,7 @@ class BaseModelSqlv2 {
await this.beforeInsert(insertObj, this.dbDriver, cookie);
await this.prepareNocoData(insertObj, true);
await this.prepareNocoData(insertObj, true, cookie);
let response;
const query = this.dbDriver(this.tnPath).insert(insertObj);
@ -2962,7 +3002,11 @@ class BaseModelSqlv2 {
for (let i = 0; i < this.model.columns.length; ++i) {
const col = this.model.columns[i];
if (col.title in d && isCreatedOrLastModifiedTimeCol(col)) {
if (
col.title in d &&
(isCreatedOrLastModifiedTimeCol(col) ||
isCreatedOrLastModifiedByCol(col))
) {
NcError.badRequest(
`Column "${col.title}" is auto generated and cannot be updated`,
);
@ -3085,7 +3129,7 @@ class BaseModelSqlv2 {
}
}
await this.prepareNocoData(insertObj, true);
await this.prepareNocoData(insertObj, true, cookie);
// prepare nested link data for insert only if it is single record insertion
if (isSingleRecordInsertion) {
@ -3229,7 +3273,7 @@ class BaseModelSqlv2 {
continue;
}
if (!raw) {
await this.prepareNocoData(d);
await this.prepareNocoData(d, false, cookie);
const oldRecord = await this.readByPk(pkValues);
if (!oldRecord) {
@ -3847,7 +3891,11 @@ class BaseModelSqlv2 {
for (let i = 0; i < this.model.columns.length; ++i) {
const column = this.model.columns[i];
if (column.title in data && isCreatedOrLastModifiedTimeCol(column)) {
if (
column.title in data &&
(isCreatedOrLastModifiedTimeCol(column) ||
isCreatedOrLastModifiedByCol(column))
) {
NcError.badRequest(
`Column "${column.title}" is auto generated and cannot be updated`,
);
@ -4018,13 +4066,15 @@ class BaseModelSqlv2 {
);
}
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [childId],
cookie,
});
await this.updateLastModifiedTime({
await this.updateLastModified({
model: childTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -4046,9 +4096,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -4070,9 +4121,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [childId],
cookie,
});
}
break;
@ -4168,13 +4220,15 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [childId],
cookie,
});
await this.updateLastModifiedTime({
await this.updateLastModified({
model: childTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -4194,9 +4248,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -4216,9 +4271,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [childId],
cookie,
});
}
break;
@ -4666,11 +4722,19 @@ class BaseModelSqlv2 {
for (const col of columns) {
if (col.uidt === UITypes.Lookup) {
if ((await this.getNestedUidt(col)) === UITypes.User) {
if (
[UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
(await this.getNestedUidt(col)) as UITypes,
)
) {
userColumns.push(col);
}
} else {
if (col.uidt === UITypes.User) {
if (
[UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
col.uidt,
)
) {
userColumns.push(col);
}
}
@ -5130,13 +5194,15 @@ class BaseModelSqlv2 {
raw: true,
});
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: childIds,
cookie,
});
await this.updateLastModifiedTime({
await this.updateLastModified({
model: childTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -5213,9 +5279,10 @@ class BaseModelSqlv2 {
}
await this.execAndParse(updateQb, null, { raw: true });
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -5260,9 +5327,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -5406,13 +5474,15 @@ class BaseModelSqlv2 {
);
await this.execAndParse(delQb, null, { raw: true });
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: childIds,
cookie,
});
await this.updateLastModifiedTime({
await this.updateLastModified({
model: childTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -5495,9 +5565,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [rowId],
cookie,
});
}
break;
@ -5545,9 +5616,10 @@ class BaseModelSqlv2 {
{ raw: true },
);
await this.updateLastModifiedTime({
await this.updateLastModified({
model: parentTable,
rowIds: [childIds[0]],
cookie,
});
}
break;
@ -5634,26 +5706,40 @@ class BaseModelSqlv2 {
}
}
async updateLastModifiedTime({
async updateLastModified({
rowIds,
cookie,
model = this.model,
knex = this.dbDriver,
}: {
rowIds: any | any[];
cookie?: { user?: any };
model?: Model;
knex?: XKnex;
}) {
const columnName = await model.getColumns().then((columns) => {
return columns.find(
(c) => c.uidt === UITypes.LastModifiedTime && c.system,
)?.column_name;
});
const columns = await model.getColumns();
if (!columnName) return;
const updateObject = {};
const qb = knex(model.table_name).update({
[columnName]: Noco.ncMeta.now(),
});
const lastModifiedTimeColumn = columns.find(
(c) => c.uidt === UITypes.LastModifiedTime && c.system,
);
const lastModifiedByColumn = columns.find(
(c) => c.uidt === UITypes.LastModifiedBy && c.system,
);
if (lastModifiedTimeColumn) {
updateObject[lastModifiedTimeColumn.column_name] = Noco.ncMeta.now();
}
if (lastModifiedByColumn) {
updateObject[lastModifiedByColumn.column_name] = cookie?.user?.id;
}
if (Object.keys(updateObject).length === 0) return;
const qb = knex(model.table_name).update(updateObject);
for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) {
qb.orWhere(await this._wherePk(rowId));
@ -5662,7 +5748,7 @@ class BaseModelSqlv2 {
await this.execAndParse(qb, null, { raw: true });
}
async prepareNocoData(data, isInsertData = false) {
async prepareNocoData(data, isInsertData = false, cookie?: { user?: any }) {
if (
this.model.columns.some((c) =>
[
@ -5670,19 +5756,25 @@ class BaseModelSqlv2 {
UITypes.User,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(c.uidt),
)
) {
for (const column of this.model.columns) {
if (
isInsertData &&
column.uidt === UITypes.CreatedTime &&
column.system
) {
data[column.column_name] = Noco.ncMeta.now();
}
if (column.uidt === UITypes.LastModifiedTime && column.system) {
data[column.column_name] = isInsertData ? null : Noco.ncMeta.now();
if (column.system) {
if (isInsertData) {
if (column.uidt === UITypes.CreatedTime) {
data[column.column_name] = Noco.ncMeta.now();
} else if (column.uidt === UITypes.CreatedBy) {
data[column.column_name] = cookie?.user?.id;
}
}
if (column.uidt === UITypes.LastModifiedTime) {
data[column.column_name] = isInsertData ? null : Noco.ncMeta.now();
} else if (column.uidt === UITypes.LastModifiedBy) {
data[column.column_name] = isInsertData ? null : cookie?.user?.id;
}
}
if (column.uidt === UITypes.Attachment) {
if (data[column.column_name]) {
@ -5699,7 +5791,11 @@ class BaseModelSqlv2 {
}
}
}
} else if (column.uidt === UITypes.User) {
} else if (
[UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
column.uidt,
)
) {
if (data[column.column_name]) {
const userIds = [];

4
packages/nocodb/src/db/conditionV2.ts

@ -453,7 +453,9 @@ const parseConditionV2 = async (
builder,
);
} else if (
column.uidt === UITypes.User &&
[UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(
column.uidt,
) &&
['like', 'nlike'].includes(filter.comparison_op)
) {
const baseUsers = await BaseUser.getUsersList({

2
packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts

@ -695,6 +695,8 @@ async function _formulaQueryBuilder(
}
break;
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
{
const base = await Base.get(model.base_id);
const baseUsers = await BaseUser.getUsersList({

4
packages/nocodb/src/db/sortV2.ts

@ -139,7 +139,9 @@ export default async function sortV2(
}
break;
}
case UITypes.User: {
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy: {
const base = await Base.get(model.base_id);
const baseUsers = await BaseUser.getUsersList({
base_id: base.id,

2
packages/nocodb/src/helpers/getAst.ts

@ -1,4 +1,5 @@
import {
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isSystemColumn,
RelationTypes,
@ -156,6 +157,7 @@ const getAst = async ({
isRequested =
!isSystemColumn(col) ||
(isCreatedOrLastModifiedTimeCol(col) && col.system) ||
(isCreatedOrLastModifiedByCol(col) && col.system) ||
col.pk;
} else if (allowedCols && (!includePkByDefault || !col.pk)) {
isRequested =

4
packages/nocodb/src/modules/datas/helpers.ts

@ -171,7 +171,9 @@ export async function serializeCellValue({
)
.join(', ');
}
case UITypes.User: {
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy: {
let data = value;
try {
if (typeof value === 'string') {

2
packages/nocodb/src/modules/jobs/jobs/export-import/export.service.ts

@ -454,6 +454,8 @@ export class ExportService {
}
break;
case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
if (v) {
const userIds = [];
for (const user of v as { id: string }[]) {

6
packages/nocodb/src/modules/jobs/jobs/export-import/import.service.ts

@ -768,6 +768,8 @@ export class ImportService {
a.uidt === UITypes.QrCode ||
a.uidt === UITypes.CreatedTime ||
a.uidt === UITypes.LastModifiedTime ||
a.uidt === UITypes.CreatedBy ||
a.uidt === UITypes.LastModifiedBy ||
a.uidt === UITypes.Barcode,
),
);
@ -897,7 +899,9 @@ export class ImportService {
}
} else if (
col.uidt === UITypes.CreatedTime ||
col.uidt === UITypes.LastModifiedTime
col.uidt === UITypes.LastModifiedTime ||
col.uidt === UITypes.CreatedBy ||
col.uidt === UITypes.LastModifiedBy
) {
if (col.system) continue;
const freshModelData = await this.columnsService.columnAdd({

8
packages/nocodb/src/schema/swagger-v2.json

@ -12127,7 +12127,9 @@
"Year",
"QrCode",
"Links",
"User"
"User",
"CreatedBy",
"LastModifiedBy"
],
"type": "string"
},
@ -15345,7 +15347,9 @@
"Year",
"QrCode",
"Links",
"User"
"User",
"CreatedBy",
"LastModifiedBy"
],
"type": "string",
"description": "UI Data Type"

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

@ -17344,7 +17344,9 @@
"Year",
"QrCode",
"Links",
"User"
"User",
"CreatedBy",
"LastModifiedBy"
],
"type": "string"
},
@ -20565,7 +20567,9 @@
"Year",
"QrCode",
"Links",
"User"
"User",
"CreatedBy",
"LastModifiedBy"
],
"type": "string",
"description": "UI Data Type"

4
packages/nocodb/src/services/api-docs/swagger/getSwaggerColumnMetas.ts

@ -56,6 +56,10 @@ export default async (
case UITypes.CreatedTime:
field.type = 'string';
break;
case UITypes.LastModifiedBy:
case UITypes.CreatedBy:
field.type = 'object';
break;
default:
field.virtual = false;
SwaggerTypes.setSwaggerType(c, field, dbType);

4
packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts

@ -49,6 +49,10 @@ export default async (
case UITypes.CreatedTime:
field.type = 'string';
break;
case UITypes.LastModifiedBy:
case UITypes.CreatedBy:
field.type = 'object';
break;
default:
field.virtual = false;
SwaggerTypes.setSwaggerType(c, field, dbType);

45
packages/nocodb/src/services/columns.service.ts

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import {
AppEvents,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isVirtualCol,
@ -173,6 +174,7 @@ export class ColumnsService {
if (
!isVirtualCol(param.column) &&
!isCreatedOrLastModifiedTimeCol(param.column) &&
!isCreatedOrLastModifiedByCol(param.column) &&
!(await Column.checkTitleAvailable({
column_name: param.column.column_name,
fk_model_id: column.fk_model_id,
@ -198,6 +200,7 @@ export class ColumnsService {
};
if (
isCreatedOrLastModifiedTimeCol(column) ||
isCreatedOrLastModifiedByCol(column) ||
[
UITypes.Lookup,
UITypes.Rollup,
@ -296,7 +299,12 @@ export class ColumnsService {
`Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`,
);
} else if (
[UITypes.CreatedTime, UITypes.LastModifiedTime].includes(colBody.uidt)
[
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(colBody.uidt)
) {
// allow updating of title only
await Column.update(param.columnId, {
@ -1687,6 +1695,8 @@ export class ColumnsService {
break;
case UITypes.CreatedTime:
case UITypes.LastModifiedTime:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
{
let columnName: string;
const columns = await table.getColumns();
@ -1697,10 +1707,26 @@ export class ColumnsService {
);
if (!existingColumn) {
columnName =
colBody.uidt === UITypes.CreatedTime
? 'created_at'
: 'updated_at';
let columnTitle;
switch (colBody.uidt) {
case UITypes.CreatedTime:
columnName = 'created_at';
columnTitle = 'CreatedAt';
break;
case UITypes.LastModifiedTime:
columnName = 'updated_at';
columnTitle = 'UpdatedAt';
break;
case UITypes.CreatedBy:
columnName = 'created_by';
columnTitle = 'CreatedBy';
break;
case UITypes.LastModifiedBy:
columnName = 'updated_by';
columnTitle = 'UpdatedBy';
break;
}
// todo: check type as well
const dbColumn = columns.find((c) => c.column_name === columnName);
@ -1741,10 +1767,7 @@ export class ColumnsService {
await sqlMgr.sqlOpPlus(source, 'tableUpdate', tableUpdateBody);
}
const title = getUniqueColumnAliasName(
table.columns,
UITypes.CreatedTime ? 'CreatedAt' : 'UpdatedAt',
);
const title = getUniqueColumnAliasName(table.columns, columnTitle);
await Column.insert({
...colBody,
@ -2209,9 +2232,11 @@ export class ColumnsService {
/* falls through to default */
}
// on delete create time or last modified time, keep the column in table and delete the column from meta
// on deleting created/last modified columns, keep the column in table and delete the column from meta
case UITypes.CreatedTime:
case UITypes.LastModifiedTime:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
{
await Column.delete(param.columnId, ncMeta);
}

46
packages/nocodb/src/services/tables.service.ts

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import DOMPurify from 'isomorphic-dompurify';
import {
AppEvents,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isVirtualCol,
@ -384,21 +385,49 @@ export class TablesService {
source = base.sources.find((b) => b.id === param.sourceId);
}
// add CreatedBy and LastModifiedBy system columns if missing in request payload
// add CreatedTime and LastModifiedTime system columns if missing in request payload
{
for (const uidt of [UITypes.CreatedTime, UITypes.LastModifiedTime]) {
for (const uidt of [
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
]) {
const col = tableCreatePayLoad.columns.find(
(c) => c.uidt === uidt,
) as ColumnType;
let columnName, columnTitle;
switch (uidt) {
case UITypes.CreatedTime:
columnName = 'created_at';
columnTitle = 'CreatedAt';
break;
case UITypes.LastModifiedTime:
columnName = 'updated_at';
columnTitle = 'UpdatedAt';
break;
case UITypes.CreatedBy:
columnName = 'created_by';
columnTitle = 'CreatedBy';
break;
case UITypes.LastModifiedBy:
columnName = 'updated_by';
columnTitle = 'UpdatedBy';
break;
}
const colName = getUniqueColumnName(
tableCreatePayLoad.columns as any[],
uidt === UITypes.CreatedTime ? 'created_at' : 'updated_at',
columnName,
);
const colAlias = getUniqueColumnAliasName(
tableCreatePayLoad.columns as any[],
uidt === UITypes.CreatedTime ? 'CreatedAt' : 'UpdatedAt',
columnTitle,
);
if (!col || !col.system) {
tableCreatePayLoad.columns.push({
...(await getColumnPropsFromUIDT({ uidt } as any, source)),
@ -514,7 +543,8 @@ export class TablesService {
for (const column of param.table.columns) {
if (
!isVirtualCol(column) ||
(isCreatedOrLastModifiedTimeCol(column) && (column as any).system)
(isCreatedOrLastModifiedTimeCol(column) && (column as any).system) ||
(isCreatedOrLastModifiedByCol(column) && (column as any).system)
) {
const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
@ -549,7 +579,11 @@ export class TablesService {
param.table.columns
// exclude alias columns from column list
?.filter((c) => {
return !isCreatedOrLastModifiedTimeCol(c) || (c as any).system;
return (
!isCreatedOrLastModifiedTimeCol(c) ||
!isCreatedOrLastModifiedByCol(c) ||
(c as any).system
);
})
.map(async (c) => ({
...(await getColumnPropsFromUIDT(c as any, source)),

2
packages/nocodb/src/version-upgrader/NcUpgrader.ts

@ -17,6 +17,7 @@ import ncProjectConfigUpgrader from './ncProjectConfigUpgrader';
import ncXcdbLTARUpgrader from './ncXcdbLTARUpgrader';
import ncXcdbLTARIndexUpgrader from './ncXcdbLTARIndexUpgrader';
import ncXcdbCreatedAndUpdatedTimeUpgrader from './ncXcdbCreatedAndUpdatedTimeUpgrader';
import ncXcdbCreatedAndUpdatedByUpgrader from './ncXcdbCreatedAndUpdatedByUpgrader';
import type { MetaService } from '~/meta/meta.service';
import type { NcConfig } from '~/interface/config';
@ -146,6 +147,7 @@ export default class NcUpgrader {
{ name: '0108002', handler: ncXcdbLTARUpgrader },
{ name: '0111002', handler: ncXcdbLTARIndexUpgrader },
{ name: '0111004', handler: ncXcdbCreatedAndUpdatedTimeUpgrader },
{ name: '0111005', handler: ncXcdbCreatedAndUpdatedByUpgrader },
];
}
}

125
packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedByUpgrader.ts

@ -0,0 +1,125 @@
import { UITypes } from 'nocodb-sdk';
import { Logger } from '@nestjs/common';
import type { NcUpgraderCtx } from './NcUpgrader';
import type { MetaService } from '~/meta/meta.service';
import { MetaTable } from '~/utils/globals';
import { Column, Model, Source } from '~/models';
import {
getUniqueColumnAliasName,
getUniqueColumnName,
} from '~/helpers/getUniqueName';
import getColumnPropsFromUIDT from '~/helpers/getColumnPropsFromUIDT';
import ProjectMgrv2 from '~/db/sql-mgr/v2/ProjectMgrv2';
import { Altered } from '~/services/columns.service';
// An upgrader for adding created_by and updated_by columns to all tables as system column
const logger = new Logger('ncXcdbCreatedAndUpdatedByUpgrader');
async function upgradeModels({
ncMeta,
source,
}: {
ncMeta: MetaService;
source: any;
}) {
const models = await Model.list(
{
base_id: source.base_id,
source_id: source.id,
},
ncMeta,
);
await Promise.all(
models.map(async (model: any) => {
if (model.mm) return;
const newColumns = [];
const columns = await model.getColumns(ncMeta);
const oldColumns = columns.map((c) => ({ ...c, cn: c.column_name }));
newColumns.push({
...(await getColumnPropsFromUIDT(
{
uidt: UITypes.CreatedBy,
column_name: getUniqueColumnName(columns, 'created_by'),
title: getUniqueColumnAliasName(columns, 'CreatedBy'),
},
source,
)),
cdf: null,
system: true,
altered: Altered.NEW_COLUMN,
});
newColumns.push({
...(await getColumnPropsFromUIDT(
{
uidt: UITypes.LastModifiedBy,
column_name: getUniqueColumnName(columns, 'updated_by'),
title: getUniqueColumnAliasName(columns, 'UpdatedBy'),
},
source,
)),
cdf: null,
system: true,
altered: Altered.NEW_COLUMN,
});
// update column in db
const tableUpdateBody = {
...model,
tn: model.table_name,
originalColumns: oldColumns,
columns: [...columns, ...newColumns].map((c) => ({
...c,
cn: c.column_name,
})),
};
const sqlMgr = ProjectMgrv2.getSqlMgr({ id: source.base_id }, ncMeta);
await sqlMgr.sqlOpPlus(source, 'tableUpdate', tableUpdateBody);
for (const newColumn of newColumns) {
await Column.insert(
{
...newColumn,
system: 1,
fk_model_id: model.id,
},
ncMeta,
);
}
logger.log(`Upgraded model ${model.name} from source ${source.name}`);
}),
);
}
export default async function ({ ncMeta }: NcUpgraderCtx) {
// get all xcdb sources
const sources = await ncMeta.metaList2(null, null, MetaTable.BASES, {
xcCondition: {
_or: [
{
is_meta: {
eq: 1,
},
},
{
is_local: {
eq: 1,
},
},
],
},
});
// iterate and upgrade each base
for (const source of sources) {
logger.log(`Upgrading source ${source.name}`);
// update the meta props
await upgradeModels({ ncMeta, source: new Source(source) });
}
}
Loading…
Cancel
Save