From 761e7152a228388f528282f0438df7a78ae67371 Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Fri, 22 Dec 2023 02:03:16 +0530 Subject: [PATCH 001/273] feat: hook and dropdown menu for user sort management --- .../nc-gui/components/account/UserList.vue | 39 ++++- .../nc-gui/components/account/UserMenu.vue | 89 ++++++++++++ packages/nc-gui/composables/useUserSorts.ts | 133 ++++++++++++++++++ packages/nc-gui/lang/en.json | 4 +- packages/nc-gui/lib/types.ts | 6 + 5 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 packages/nc-gui/components/account/UserMenu.vue create mode 100644 packages/nc-gui/composables/useUserSorts.ts diff --git a/packages/nc-gui/components/account/UserList.vue b/packages/nc-gui/components/account/UserList.vue index c5d5293651..a516ffc5a8 100644 --- a/packages/nc-gui/components/account/UserList.vue +++ b/packages/nc-gui/components/account/UserList.vue @@ -2,7 +2,16 @@ import { OrgUserRoles } from 'nocodb-sdk' import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk' import type { User } from '#imports' -import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useDebounceFn, useNuxtApp } from '#imports' +import { + extractSdkResponseErrorMsg, + iconMap, + useApi, + useCopy, + useDashboard, + useDebounceFn, + useNuxtApp, + useUserSorts, +} from '#imports' const { api, isLoading } = useApi() @@ -19,8 +28,14 @@ const { user: loggedInUser } = useGlobal() const { copy } = useCopy() +const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } = useUserSorts() + const users = ref([]) +const sortedUsers = computed(() => { + return handleGetSortsData(users.value, sorts.value) as UserType[] +}) + const currentPage = ref(1) const currentLimit = ref(10) @@ -64,6 +79,7 @@ const loadUsers = useDebounceFn(async (page = currentPage.value, limit = current onMounted(() => { loadUsers() + loadSorts() }) const updateRole = async (userId: string, roles: string) => { @@ -176,10 +192,21 @@ const openDeleteModal = (user: UserType) => {
-
- {{ $t('labels.email') }} +
+ + {{ $t('labels.email') }} + + +
+
+ + {{ $t('objects.role') }} + +
-
{{ $t('objects.role') }}
{{ $t('labels.action') }}
@@ -193,7 +220,7 @@ const openDeleteModal = (user: UserType) => {
{
diff --git a/packages/nc-gui/components/account/UserMenu.vue b/packages/nc-gui/components/account/UserMenu.vue new file mode 100644 index 0000000000..af99ebc0e6 --- /dev/null +++ b/packages/nc-gui/components/account/UserMenu.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/packages/nc-gui/composables/useUserSorts.ts b/packages/nc-gui/composables/useUserSorts.ts new file mode 100644 index 0000000000..15ccccd3e1 --- /dev/null +++ b/packages/nc-gui/composables/useUserSorts.ts @@ -0,0 +1,133 @@ +import rfdc from 'rfdc' +import type { UsersSortType } from '~/lib' +import { useGlobal } from '#imports' + +/** + * Hook for managing user sorts and sort configurations. + * @returns An object containing reactive values and functions related to user sorts. + */ +export function useUserSorts() { + const clone = rfdc() + + const { user } = useGlobal() + + const sorts = ref([]) + + // Key for storing user sort configurations in local storage + const userSortConfigKey = 'userSortConfig' + + // Default user ID if no user found (fallback) + const defaultUserId = 'default' + + /** + * Computed property that returns a record of sort directions based on the current sort configurations. + * @type {ComputedRef>} + */ + const sortDirection: ComputedRef> = computed(() => { + return sorts.value.reduce((acc, curr) => { + acc = { ...acc, [curr.field]: curr.direction } + return acc + }, {} as Record) + }) + + /** + * Loads user sort configurations from local storage based on the current user ID. + */ + function loadSorts(): void { + try { + // Retrieve sort configuration from local storage + const storedConfig = localStorage.getItem(userSortConfigKey) + + const sortConfig = storedConfig ? JSON.parse(storedConfig) : {} + sorts.value = sortConfig + + // Load user-specific sort configurations or default configurations + sorts.value = user.value?.id ? sortConfig[user.value.id] || [] : sortConfig[defaultUserId] || [] + } catch (error) { + console.error('Error while retrieving sort configuration from local storage:', error) + // Set sorts to an empty array in case of an error + sorts.value = [] + } + } + + /** + * Saves or updates a user sort configuration and updates local storage. + * @param {UsersSortType} newSortConfig - The new sort configuration to save or update. + */ + function saveOrUpdate(newSortConfig: UsersSortType): void { + try { + const fieldIndex = sorts.value.findIndex((sort) => sort.field === newSortConfig.field) + if (newSortConfig.direction) { + if (fieldIndex !== -1) { + // Update the direction if the field exists + sorts.value = [ + ...clone(sorts.value).map((sort) => { + if (sort.field === newSortConfig.field) { + sort.direction = newSortConfig.direction + } + return sort + }), + ] + } else { + // Add a new sort configuration if the field does not exist + sorts.value = [...clone(sorts.value), newSortConfig] + } + } else { + if (fieldIndex !== -1) { + // Remove the sort configuration if the field exists and direction is not present + sorts.value = [...clone(sorts.value).filter((sort) => sort.field !== newSortConfig.field)] + } + } + + // Update local storage with the new sort configurations + const storedConfig = localStorage.getItem(userSortConfigKey) + const sortConfig = storedConfig ? JSON.parse(storedConfig) : {} + + if (user.value?.id) { + // Save or delete user-specific sort configurations + if (sorts.value.length) { + sortConfig[user.value.id] = sorts.value + } else { + delete sortConfig[user.value.id] + } + } else { + // Save or delete default user sort configurations + sortConfig[defaultUserId] = sorts.value + } + + localStorage.setItem(userSortConfigKey, JSON.stringify(sortConfig)) + } catch (error) { + console.error('Error while retrieving sort configuration from local storage:', error) + } + } + + /** + * Sorts and returns a deep copy of an array of objects based on the provided sort configurations. + * + * @param data - The array of objects to be sorted. + * @param sortsConfig - The array of sort configurations. + * @returns A new array containing sorted objects. + * @template T - The type of objects in the input array. + */ + function handleGetSortsData>(data: T[], sortsConfig: UsersSortType[] = sorts.value): T[] { + const sortedData = clone(data).sort((a, b) => { + let sortCondition = 0 + + for (const { field, direction } of sortsConfig) { + if (a[field]) continue + + if (direction === 'asc') { + sortCondition = sortCondition || a[field]?.localeCompare(b[field]) + } else if (direction === 'desc') { + sortCondition = sortCondition || b[field]?.localeCompare(a[field]) + } + } + + return sortCondition + }) + + return sortedData + } + + return { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } +} diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index b2875c9f0e..ae26001223 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -189,7 +189,8 @@ "shift": "Shift", "enter": "Enter", "seconds": "Seconds", - "paste": "Paste" + "paste": "Paste", + "resetSort": "Reset Sort" }, "objects": { "workspace": "Workspace", @@ -1212,7 +1213,6 @@ "thankYou": "Thank you!", "submittedFormData": "You have successfully submitted the form data.", "editingSystemKeyNotSupported": "Editing system key not supported" - }, "error": { "nameRequired": "Name Required", diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index 7643f681ce..0cde7912e3 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -176,6 +176,11 @@ interface SidebarTableNode extends TableType { isViewsLoading?: boolean } +interface UsersSortType { + field: string + direction?: 'asc' | 'desc' +} + export type { User, ProjectMetaInfo, @@ -201,4 +206,5 @@ export type { ViewPageType, NcButtonSize, SidebarTableNode, + UsersSortType, } From b4bc820be2cbd232c3f8d65a3f440bb56aade2ce Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Fri, 22 Dec 2023 02:18:14 +0530 Subject: [PATCH 002/273] fix: array for continue issue - user sort management --- packages/nc-gui/composables/useUserSorts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useUserSorts.ts b/packages/nc-gui/composables/useUserSorts.ts index 15ccccd3e1..2faa0e05aa 100644 --- a/packages/nc-gui/composables/useUserSorts.ts +++ b/packages/nc-gui/composables/useUserSorts.ts @@ -114,7 +114,7 @@ export function useUserSorts() { let sortCondition = 0 for (const { field, direction } of sortsConfig) { - if (a[field]) continue + if (!a[field]) continue if (direction === 'asc') { sortCondition = sortCondition || a[field]?.localeCompare(b[field]) From a87b8c9bd3d27da2c8c75b12a410b592f121c88e Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Fri, 22 Dec 2023 10:47:52 +0530 Subject: [PATCH 003/273] fix: update handleGetSortsData for single level of user sort --- packages/nc-gui/composables/useUserSorts.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/nc-gui/composables/useUserSorts.ts b/packages/nc-gui/composables/useUserSorts.ts index 2faa0e05aa..9fe603d4ca 100644 --- a/packages/nc-gui/composables/useUserSorts.ts +++ b/packages/nc-gui/composables/useUserSorts.ts @@ -61,16 +61,18 @@ export function useUserSorts() { if (fieldIndex !== -1) { // Update the direction if the field exists sorts.value = [ - ...clone(sorts.value).map((sort) => { - if (sort.field === newSortConfig.field) { - sort.direction = newSortConfig.direction - } - return sort - }), + ...clone(sorts.value) + .map((sort) => { + if (sort.field === newSortConfig.field) { + sort.direction = newSortConfig.direction + } + return sort + }) + .filter((sort) => sort.field !== newSortConfig.field), // For now it is only single level of sorting so remove another sort field ] } else { - // Add a new sort configuration if the field does not exist - sorts.value = [...clone(sorts.value), newSortConfig] + // Add a new sort configuration + sorts.value = [newSortConfig] } } else { if (fieldIndex !== -1) { From fcbb1123e9e8b15d6bd41416500774f13073a9fe Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Mon, 25 Dec 2023 12:02:01 +0530 Subject: [PATCH 004/273] fix: sort user by roles --- .../nc-gui/components/account/UserList.vue | 2 +- .../components/project/AccessSettings.vue | 12 ++++- packages/nc-gui/composables/useUserSorts.ts | 52 ++++++++++++++++--- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/packages/nc-gui/components/account/UserList.vue b/packages/nc-gui/components/account/UserList.vue index a516ffc5a8..57c756a348 100644 --- a/packages/nc-gui/components/account/UserList.vue +++ b/packages/nc-gui/components/account/UserList.vue @@ -28,7 +28,7 @@ const { user: loggedInUser } = useGlobal() const { copy } = useCopy() -const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } = useUserSorts() +const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } = useUserSorts('Org') const users = ref([]) diff --git a/packages/nc-gui/components/project/AccessSettings.vue b/packages/nc-gui/components/project/AccessSettings.vue index 3bed047889..d5f4b4c718 100644 --- a/packages/nc-gui/components/project/AccessSettings.vue +++ b/packages/nc-gui/components/project/AccessSettings.vue @@ -9,7 +9,7 @@ import { } from 'nocodb-sdk' import type { WorkspaceUserRoles } from 'nocodb-sdk' import InfiniteLoading from 'v3-infinite-loading' -import { isEeUI, storeToRefs } from '#imports' +import { isEeUI, storeToRefs, useUserSorts } from '#imports' const basesStore = useBases() const { getProjectUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore @@ -17,6 +17,8 @@ const { activeProjectId } = storeToRefs(basesStore) const { orgRoles, baseRoles } = useRoles() +const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } = useUserSorts('Project') + const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN]) interface Collaborators { @@ -36,6 +38,11 @@ const isLoading = ref(false) const isSearching = ref(false) const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([]) +const sortedCollaborators = computed(() => { + console.log('collaborator', sorts.value, collaborators.value) + return handleGetSortsData([...collaborators.value], sorts.value) +}) + const loadCollaborators = async () => { try { currentPage.value += 1 @@ -150,6 +157,7 @@ onMounted(async () => { } else if (currentRoleIndex !== -1) { accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1) } + loadSorts() } catch (e: any) { message.error(await extractSdkResponseErrorMsg(e)) } finally { @@ -192,7 +200,7 @@ onMounted(async () => {
diff --git a/packages/nc-gui/composables/useUserSorts.ts b/packages/nc-gui/composables/useUserSorts.ts index 9fe603d4ca..a7410ebe1d 100644 --- a/packages/nc-gui/composables/useUserSorts.ts +++ b/packages/nc-gui/composables/useUserSorts.ts @@ -1,12 +1,13 @@ import rfdc from 'rfdc' import type { UsersSortType } from '~/lib' import { useGlobal } from '#imports' +import { extractRolesObj, ProjectRoles, OrgUserRoles, WorkspaceUserRoles } from 'nocodb-sdk' /** * Hook for managing user sorts and sort configurations. * @returns An object containing reactive values and functions related to user sorts. */ -export function useUserSorts() { +export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { const clone = rfdc() const { user } = useGlobal() @@ -68,7 +69,7 @@ export function useUserSorts() { } return sort }) - .filter((sort) => sort.field !== newSortConfig.field), // For now it is only single level of sorting so remove another sort field + .filter((sort) => sort.field === newSortConfig.field), // For now it is only single level of sorting so remove another sort field ] } else { // Add a new sort configuration @@ -112,16 +113,53 @@ export function useUserSorts() { * @template T - The type of objects in the input array. */ function handleGetSortsData>(data: T[], sortsConfig: UsersSortType[] = sorts.value): T[] { - const sortedData = clone(data).sort((a, b) => { + let userRoleOrder: string[] = [] + if (roleType === 'Workspace') { + userRoleOrder = Object.values(WorkspaceUserRoles) + } else if (roleType === 'Org') { + userRoleOrder = Object.values(OrgUserRoles) + } else if (roleType === 'Project') { + userRoleOrder = Object.values(ProjectRoles) + } + data = clone(data) + // let superUserIndex = data.findIndex((user) => user?.roles?.includes('super')) + // let superUser = superUserIndex !== -1 ? data.splice(superUserIndex, 1) : null + // console.log('super', superUser) + const sortedData = data.sort((a, b) => { let sortCondition = 0 for (const { field, direction } of sortsConfig) { if (!a[field]) continue - if (direction === 'asc') { - sortCondition = sortCondition || a[field]?.localeCompare(b[field]) - } else if (direction === 'desc') { - sortCondition = sortCondition || b[field]?.localeCompare(a[field]) + if (field === 'roles') { + for (const role of userRoleOrder) { + const indexA = a?.roles?.split(',')?.indexOf(role) ?? -1 + const indexB = b?.roles?.split(',')?.indexOf(role) ?? -1 + + // if (indexA === -1) { + // sortCondition = sortCondition || direction === 'asc' ? 1 : -1 // Role A is missing, so it should come last + // break + // } + + // if (indexB === -1) { + // sortCondition = sortCondition || direction === 'asc' ? -1 : 1 // Role B is missing, so it should come last + // break + // } + + if (direction === 'asc') { + sortCondition = sortCondition || indexA - indexB + break + } else if (direction === 'desc') { + sortCondition = sortCondition || indexB - indexA + break + } + } + } else { + if (direction === 'asc') { + sortCondition = sortCondition || a[field]?.localeCompare(b[field]) + } else if (direction === 'desc') { + sortCondition = sortCondition || b[field]?.localeCompare(a[field]) + } } } From 114da7e2c00477437280f4c8da03cb1cdaeb1ca8 Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Wed, 27 Dec 2023 09:13:58 +0530 Subject: [PATCH 005/273] fix: user roles sort issue --- .../nc-gui/components/account/UserList.vue | 11 ++ .../components/project/AccessSettings.vue | 21 +++- packages/nc-gui/composables/useUserSorts.ts | 112 +++++++----------- packages/nc-gui/lib/types.ts | 2 +- 4 files changed, 72 insertions(+), 74 deletions(-) diff --git a/packages/nc-gui/components/account/UserList.vue b/packages/nc-gui/components/account/UserList.vue index 57c756a348..ddb661451c 100644 --- a/packages/nc-gui/components/account/UserList.vue +++ b/packages/nc-gui/components/account/UserList.vue @@ -12,6 +12,7 @@ import { useNuxtApp, useUserSorts, } from '#imports' +import rfdc from 'rfdc' const { api, isLoading } = useApi() @@ -33,6 +34,7 @@ const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortsData } = us const users = ref([]) const sortedUsers = computed(() => { + console.log('users', users.value) return handleGetSortsData(users.value, sorts.value) as UserType[] }) @@ -54,6 +56,8 @@ const pagination = reactive({ position: ['bottomCenter'], }) +const clone = rfdc() + const loadUsers = useDebounceFn(async (page = currentPage.value, limit = currentLimit.value) => { currentPage.value = page try { @@ -89,6 +93,13 @@ const updateRole = async (userId: string, roles: string) => { } as OrgUserReqType) message.success(t('msg.success.roleUpdated')) + users.value = clone(users.value).map((user) => { + if (user.id === userId) { + user['roles'] = roles + } + return user + }) + $e('a:org-user:role-updated', { role: roles }) } catch (e: any) { message.error(await extractSdkResponseErrorMsg(e)) diff --git a/packages/nc-gui/components/project/AccessSettings.vue b/packages/nc-gui/components/project/AccessSettings.vue index d5f4b4c718..a1a7f96539 100644 --- a/packages/nc-gui/components/project/AccessSettings.vue +++ b/packages/nc-gui/components/project/AccessSettings.vue @@ -39,8 +39,7 @@ const isSearching = ref(false) const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([]) const sortedCollaborators = computed(() => { - console.log('collaborator', sorts.value, collaborators.value) - return handleGetSortsData([...collaborators.value], sorts.value) + return handleGetSortsData(collaborators.value, sorts.value) }) const loadCollaborators = async () => { @@ -91,6 +90,10 @@ const loadListData = async ($state: any) => { $state.loaded() } +const updateCollaboratorLocalState = ()=>{ + +} + const updateCollaborator = async (collab: any, roles: ProjectRoles) => { try { if ( @@ -193,9 +196,19 @@ onMounted(async () => {
-
{{ $t('objects.users') }}
+
+ + {{ $t('objects.users') }} + + +
{{ $t('title.dateJoined') }}
-
{{ $t('general.access') }}
+
+ + {{ $t('general.access') }} + + +
diff --git a/packages/nc-gui/composables/useUserSorts.ts b/packages/nc-gui/composables/useUserSorts.ts index a7410ebe1d..9287b8199b 100644 --- a/packages/nc-gui/composables/useUserSorts.ts +++ b/packages/nc-gui/composables/useUserSorts.ts @@ -12,7 +12,7 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { const { user } = useGlobal() - const sorts = ref([]) + const sorts = ref({}) // Key for storing user sort configurations in local storage const userSortConfigKey = 'userSortConfig' @@ -25,10 +25,10 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { * @type {ComputedRef>} */ const sortDirection: ComputedRef> = computed(() => { - return sorts.value.reduce((acc, curr) => { - acc = { ...acc, [curr.field]: curr.direction } - return acc - }, {} as Record) + if (sorts.value.field) { + return { [sorts.value.field]: sorts.value.direction } as Record + } + return {} as Record }) /** @@ -43,11 +43,11 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { sorts.value = sortConfig // Load user-specific sort configurations or default configurations - sorts.value = user.value?.id ? sortConfig[user.value.id] || [] : sortConfig[defaultUserId] || [] + sorts.value = user.value?.id ? sortConfig[user.value.id] || {} : sortConfig[defaultUserId] || {} } catch (error) { console.error('Error while retrieving sort configuration from local storage:', error) - // Set sorts to an empty array in case of an error - sorts.value = [] + // Set sorts to an empty obj in case of an error + sorts.value = {} } } @@ -57,29 +57,10 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { */ function saveOrUpdate(newSortConfig: UsersSortType): void { try { - const fieldIndex = sorts.value.findIndex((sort) => sort.field === newSortConfig.field) - if (newSortConfig.direction) { - if (fieldIndex !== -1) { - // Update the direction if the field exists - sorts.value = [ - ...clone(sorts.value) - .map((sort) => { - if (sort.field === newSortConfig.field) { - sort.direction = newSortConfig.direction - } - return sort - }) - .filter((sort) => sort.field === newSortConfig.field), // For now it is only single level of sorting so remove another sort field - ] - } else { - // Add a new sort configuration - sorts.value = [newSortConfig] - } + if (newSortConfig.field && newSortConfig.direction) { + sorts.value = { ...newSortConfig } } else { - if (fieldIndex !== -1) { - // Remove the sort configuration if the field exists and direction is not present - sorts.value = [...clone(sorts.value).filter((sort) => sort.field !== newSortConfig.field)] - } + sorts.value = {} } // Update local storage with the new sort configurations @@ -88,7 +69,7 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { if (user.value?.id) { // Save or delete user-specific sort configurations - if (sorts.value.length) { + if (sorts.value.field) { sortConfig[user.value.id] = sorts.value } else { delete sortConfig[user.value.id] @@ -108,11 +89,11 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { * Sorts and returns a deep copy of an array of objects based on the provided sort configurations. * * @param data - The array of objects to be sorted. - * @param sortsConfig - The array of sort configurations. + * @param sortsConfig - The object of sort configurations. * @returns A new array containing sorted objects. * @template T - The type of objects in the input array. */ - function handleGetSortsData>(data: T[], sortsConfig: UsersSortType[] = sorts.value): T[] { + function handleGetSortsData>(data: T[], sortsConfig: UsersSortType = sorts.value): T[] { let userRoleOrder: string[] = [] if (roleType === 'Workspace') { userRoleOrder = Object.values(WorkspaceUserRoles) @@ -121,51 +102,44 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') { } else if (roleType === 'Project') { userRoleOrder = Object.values(ProjectRoles) } + data = clone(data) - // let superUserIndex = data.findIndex((user) => user?.roles?.includes('super')) - // let superUser = superUserIndex !== -1 ? data.splice(superUserIndex, 1) : null + + let superUserIndex = data.findIndex((user) => user?.roles?.includes('super')) + let superUser = superUserIndex !== -1 ? data.splice(superUserIndex, 1) : null // console.log('super', superUser) - const sortedData = data.sort((a, b) => { - let sortCondition = 0 - - for (const { field, direction } of sortsConfig) { - if (!a[field]) continue - - if (field === 'roles') { - for (const role of userRoleOrder) { - const indexA = a?.roles?.split(',')?.indexOf(role) ?? -1 - const indexB = b?.roles?.split(',')?.indexOf(role) ?? -1 - - // if (indexA === -1) { - // sortCondition = sortCondition || direction === 'asc' ? 1 : -1 // Role A is missing, so it should come last - // break - // } - - // if (indexB === -1) { - // sortCondition = sortCondition || direction === 'asc' ? -1 : 1 // Role B is missing, so it should come last - // break - // } - - if (direction === 'asc') { - sortCondition = sortCondition || indexA - indexB - break - } else if (direction === 'desc') { - sortCondition = sortCondition || indexB - indexA - break - } + let sortedData = data.sort((a, b) => { + switch (sortsConfig.field) { + case 'roles': { + const roleA = a?.roles?.split(',')[0] + const roleB = b?.roles?.split(',')[0] + + if (sortsConfig.direction === 'asc') { + return userRoleOrder.indexOf(roleA) - userRoleOrder.indexOf(roleB) + } else if (sortsConfig.direction === 'desc') { + return userRoleOrder.indexOf(roleB) - userRoleOrder.indexOf(roleA) } - } else { - if (direction === 'asc') { - sortCondition = sortCondition || a[field]?.localeCompare(b[field]) - } else if (direction === 'desc') { - sortCondition = sortCondition || b[field]?.localeCompare(a[field]) + } + case 'email': { + if (sortsConfig.direction === 'asc') { + return a[sortsConfig.field]?.localeCompare(b[sortsConfig.field]) + } else if (sortsConfig.direction === 'desc') { + return b[sortsConfig.field]?.localeCompare(a[sortsConfig.field]) } } } - return sortCondition + return 0 }) + if (superUser && superUser.length) { + if (sortsConfig.direction === 'desc') { + sortedData = [...sortedData, superUser[0]] + } else { + sortedData = [superUser[0], ...sortedData] + } + } + return sortedData } diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index 0cde7912e3..be506296ab 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -177,7 +177,7 @@ interface SidebarTableNode extends TableType { } interface UsersSortType { - field: string + field?: 'email' | 'roles' direction?: 'asc' | 'desc' } From 6020999f85b17acc2fcf989427a36c441f08e82b Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:51:09 +0530 Subject: [PATCH 006/273] fix: lint erros --- packages/nc-gui/components/account/UserList.vue | 8 ++++---- packages/nc-gui/components/account/UserMenu.vue | 2 +- .../components/project/AccessSettings.vue | 8 ++------ packages/nc-gui/composables/useUserSorts.ts | 17 ++++++++--------- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/nc-gui/components/account/UserList.vue b/packages/nc-gui/components/account/UserList.vue index ddb661451c..2730d0fd56 100644 --- a/packages/nc-gui/components/account/UserList.vue +++ b/packages/nc-gui/components/account/UserList.vue @@ -1,6 +1,7 @@ diff --git a/packages/nc-gui/components/cell/TimePicker.vue b/packages/nc-gui/components/cell/TimePicker.vue index 3757f4a131..34081a1e1e 100644 --- a/packages/nc-gui/components/cell/TimePicker.vue +++ b/packages/nc-gui/components/cell/TimePicker.vue @@ -133,7 +133,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { :bordered="false" use12-hours format="HH:mm" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :placeholder="placeholder" :allow-clear="!readOnly && !localState && !isPk" @@ -148,7 +148,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { diff --git a/packages/nc-gui/components/cell/YearPicker.vue b/packages/nc-gui/components/cell/YearPicker.vue index 3b7ab61455..090845cb17 100644 --- a/packages/nc-gui/components/cell/YearPicker.vue +++ b/packages/nc-gui/components/cell/YearPicker.vue @@ -119,7 +119,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { :tabindex="0" picker="year" :bordered="false" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :placeholder="placeholder" :allow-clear="(!readOnly && !localState && !isPk) || isEditColumn" @@ -136,7 +136,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { diff --git a/packages/nc-gui/components/virtual-cell/components/ListItem.vue b/packages/nc-gui/components/virtual-cell/components/ListItem.vue index 8dd8ef034a..0e83c33e08 100644 --- a/packages/nc-gui/components/virtual-cell/components/ListItem.vue +++ b/packages/nc-gui/components/virtual-cell/components/ListItem.vue @@ -103,7 +103,7 @@ const attachments: ComputedRef = computed(() => {
- + {{ row[relatedTableDisplayValueProp] }}
Date: Thu, 4 Jan 2024 11:07:53 +0000 Subject: [PATCH 073/273] fix: expanded links add record list items display value is not rendered as cell display format --- .../components/smartsheet/grid/GroupBy.vue | 24 ++++++----- .../components/ListChildItems.vue | 2 + .../virtual-cell/components/ListItem.vue | 20 +++++++++- .../virtual-cell/components/ListItems.vue | 2 + packages/nc-gui/composables/useLTARStore.ts | 40 ++++++++++++++++++- packages/nocodb-sdk/src/lib/dateTimeHelper.ts | 9 +++-- 6 files changed, 80 insertions(+), 17 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue index 510c902cb2..2bf3e6512c 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue @@ -1,7 +1,6 @@ diff --git a/packages/nocodb-sdk/src/lib/UITypes.ts b/packages/nocodb-sdk/src/lib/UITypes.ts index f9bffcb6b6..51b943dd1c 100644 --- a/packages/nocodb-sdk/src/lib/UITypes.ts +++ b/packages/nocodb-sdk/src/lib/UITypes.ts @@ -83,6 +83,8 @@ export function isVirtualCol( UITypes.Rollup, UITypes.Lookup, UITypes.Links, + UITypes.CreateTime, + UITypes.LastModifiedTime, // UITypes.Count, ].includes((typeof col === 'object' ? col?.uidt : col)); } From 4e243342b68daaffbe7f85abf97a6bfc3a304136 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 090/273] chore: cleanup --- packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts | 1 + .../nocodb/src/services/columns.service.ts | 45 +------------------ 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts index f9df9bdc28..64457eea79 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts @@ -1691,6 +1691,7 @@ export class PgUi { break; case 'LastModifiedTime': colProp.cdf = 'now()'; + colProp.au = true; break; case 'AutoNumber': colProp.dt = 'int'; diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index d2ed5d5143..59ec3b4f62 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1,11 +1,4 @@ import { Injectable } from '@nestjs/common'; -import type { - ColumnReqType, - LinkToAnotherColumnReqType, - LinkToAnotherRecordType, - RelationTypes, - UserType, -} from 'nocodb-sdk'; import { AppEvents, isCreatedTimeOrUpdatedTimeCol, @@ -18,13 +11,6 @@ import { } from 'nocodb-sdk'; import { pluralize, singularize } from 'inflection'; import hash from 'object-hash'; -import type { - ColumnReqType, - LinkToAnotherColumnReqType, - LinkToAnotherRecordType, - RelationTypes, - UserType, -} from 'nocodb-sdk'; import type SqlMgrv2 from '~/db/sql-mgr/v2/SqlMgrv2'; import type { Base, LinkToAnotherRecordColumn } from '~/models'; import type { @@ -38,7 +24,6 @@ import type CustomKnex from '~/db/CustomKnex'; import type SqlClient from '~/db/sql-client/lib/SqlClient'; import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; import type { NcRequest } from '~/interface/config'; -import { Column, FormulaColumn, KanbanView, Model, Source } from '~/models'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; import ProjectMgrv2 from '~/db/sql-mgr/v2/ProjectMgrv2'; @@ -318,34 +303,6 @@ export class ColumnsService { ...column, title: colBody.title, }); - - if (!existingColumn) { - const sqlClient = await reuseOrSave('sqlClient', reuse, async () => - NcConnectionMgrv2.getSqlClient(source), - ); - const dbColumns = ( - await sqlClient.columnList({ - tn: table.table_name, - schema: source.getConfig()?.schema, - }) - )?.data?.list; - - // todo: check type as well - const dbColumn = dbColumns.find( - (c) => - c.column_name === - (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), - ); - - if (!dbColumn) { - // create column in db - } - - await Column.insert({ - ...colBody, - fk_model_id: table.id, - }); - } } else if ( [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt) ) { @@ -1763,7 +1720,7 @@ export class ColumnsService { if (!dbColumn) { // create column in db - const column = sqlClient + const column = sqlClient; } await Column.insert({ From 6105f747c0f285924dd93630296b1bee75463fe5 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 091/273] feat: create column if missing in table(created/updated time) --- .../nocodb/src/services/columns.service.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 59ec3b4f62..9453dd257b 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1720,7 +1720,30 @@ export class ColumnsService { if (!dbColumn) { // create column in db - const column = sqlClient; + const tableUpdateBody = { + ...table, + tn: table.table_name, + originalColumns: table.columns.map((c) => ({ + ...c, + cn: c.column_name, + })), + columns: [ + ...table.columns.map((c) => ({ ...c, cn: c.column_name })), + { + ...colBody, + cn: UITypes.CreateTime ? 'created_at' : 'updated_at', + altered: Altered.NEW_COLUMN, + }, + ], + }; + + const sqlClient = await reuseOrSave('sqlClient', reuse, async () => + NcConnectionMgrv2.getSqlClient(source), + ); + const sqlMgr = await reuseOrSave('sqlMgr', reuse, async () => + ProjectMgrv2.getSqlMgr({ id: source.base_id }), + ); + await sqlMgr.sqlOpPlus(source, 'tableUpdate', tableUpdateBody); } await Column.insert({ From fed12471c77a0d88bd63d5a1bab8ad3cc45186a5 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 092/273] feat: icon and color(created/updated time) --- .../nc-gui/components/smartsheet/header/VirtualCellIcon.ts | 3 +++ packages/nocodb/src/services/columns.service.ts | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts b/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts index e7e71286c4..cfa9f45d6b 100644 --- a/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts +++ b/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts @@ -62,6 +62,9 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => { return { icon: iconMap.rollup, color: 'text-grey' } case UITypes.Count: return { icon: CountIcon, color: 'text-grey' } + case UITypes.CreateTime: + case UITypes.LastModifiedTime: + return { icon: iconMap.datetime, color: 'text-grey' } } return { icon: iconMap.generic, color: 'text-grey' } diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 9453dd257b..7836078e44 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1716,7 +1716,6 @@ export class ColumnsService { c.column_name === (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), ); - if (!dbColumn) { // create column in db @@ -1736,10 +1735,6 @@ export class ColumnsService { }, ], }; - - const sqlClient = await reuseOrSave('sqlClient', reuse, async () => - NcConnectionMgrv2.getSqlClient(source), - ); const sqlMgr = await reuseOrSave('sqlMgr', reuse, async () => ProjectMgrv2.getSqlMgr({ id: source.base_id }), ); From 1d0e83134234b696759fa3c7dfa7ac75bc941bf1 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 093/273] feat: condition(created/updated time) --- packages/nocodb/src/db/conditionV2.ts | 37 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/nocodb/src/db/conditionV2.ts b/packages/nocodb/src/db/conditionV2.ts index f31a313b1a..76f318d7e9 100644 --- a/packages/nocodb/src/db/conditionV2.ts +++ b/packages/nocodb/src/db/conditionV2.ts @@ -550,7 +550,14 @@ const parseConditionV2 = async ( ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + if ( + [ + UITypes.Date, + UITypes.DateTime, + UITypes.CreateTime, + UITypes.LastModifiedTime, + ].includes(column.uidt) + ) { let now = dayjs(new Date()); const dateFormatFromMeta = column?.meta?.date_format; if (dateFormatFromMeta && isDateMonthFormat(dateFormatFromMeta)) { @@ -661,7 +668,13 @@ const parseConditionV2 = async ( qb = qb.where(knex.raw('BINARY ?? = ?', [field, val])); } } else { - if (column.uidt === UITypes.DateTime) { + if ( + [ + UITypes.DateTime, + UITypes.CreateTime, + UITypes.LastModifiedTime, + ].includes(column.uidt) + ) { if (qb.client.config.client === 'pg') { qb = qb.where(knex.raw('??::date = ?', [field, val])); } else { @@ -945,9 +958,13 @@ const parseConditionV2 = async ( qb = qb.whereNull(customWhereClause || field); if ( !isNumericCol(column.uidt) && - ![UITypes.Date, UITypes.DateTime, UITypes.Time].includes( - column.uidt, - ) + ![ + UITypes.Date, + UITypes.CreateTime, + UITypes.LastModifiedTime, + UITypes.DateTime, + UITypes.Time, + ].includes(column.uidt) ) { qb = qb.orWhere(field, ''); } @@ -963,9 +980,13 @@ const parseConditionV2 = async ( qb = qb.whereNotNull(customWhereClause || field); if ( !isNumericCol(column.uidt) && - ![UITypes.Date, UITypes.DateTime, UITypes.Time].includes( - column.uidt, - ) + ![ + UITypes.Date, + UITypes.DateTime, + UITypes.CreateTime, + UITypes.LastModifiedTime, + UITypes.Time, + ].includes(column.uidt) ) { qb = qb.whereNot(field, ''); } From 2ecd6c1c217fcd334b239e1598a6e50552ba413f Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 094/273] feat: method for updating last modified time programmatically --- packages/nocodb/src/db/BaseModelSqlv2.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index f43e14e51a..ac09368923 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3846,7 +3846,7 @@ class BaseModelSqlv2 { } // method for validating otpions if column is single/multi select - private async validateOptions( + private async avalidateOptions( column: Column, insertOrUpdateObject: Record, ) { @@ -5515,6 +5515,20 @@ class BaseModelSqlv2 { } } + async updateLastModifiedTime({ rowIds }: { rowIds: any | any[] }) { + const columnName = 'updated_at'; + + const qb = this.dbDriver(this.tnPath).update({ + [columnName]: this.dbDriver.fn.now(), + }); + + for(const rowId of rowIds) { + qb.orWhere(await this._wherePk(rowId)); + } + + await qb; + } + async prepareNocoData(data) { if ( this.model.columns.some((c) => From 9a3a53c0d1c31705d0c6c9986135b91e99bb4c35 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:19 +0000 Subject: [PATCH 095/273] fix: typo correction --- packages/nocodb/src/db/BaseModelSqlv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index ac09368923..7d6ff370d3 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3846,7 +3846,7 @@ class BaseModelSqlv2 { } // method for validating otpions if column is single/multi select - private async avalidateOptions( + private async validateOptions( column: Column, insertOrUpdateObject: Record, ) { From 08b867ad2260cbd1e719801970a0b28b1f5bd287 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 096/273] feat: update last modified time when updating --- packages/nocodb/src/db/BaseModelSqlv2.ts | 89 +++++++++++++++++++++++- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 7d6ff370d3..ddee9ba865 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3974,6 +3974,15 @@ class BaseModelSqlv2 { { raw: true }, ); } + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [childId], + }); + await this.updateLastModifiedTime({ + model: childTable, + rowIds: [rowId], + }); } break; case RelationTypes.HAS_MANY: @@ -3993,6 +4002,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [rowId], + }); } break; case RelationTypes.BELONGS_TO: @@ -4012,6 +4026,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [childId], + }); } break; } @@ -4105,6 +4124,15 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [childId], + }); + await this.updateLastModifiedTime({ + model: childTable, + rowIds: [rowId], + }); } break; case RelationTypes.HAS_MANY: @@ -4122,6 +4150,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [rowId], + }); } break; case RelationTypes.BELONGS_TO: @@ -4139,6 +4172,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [childId], + }); } break; } @@ -5048,6 +5086,15 @@ class BaseModelSqlv2 { await this.execAndParse(this.dbDriver(vTn).insert(insertData), null, { raw: true, }); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: childIds, + }); + await this.updateLastModifiedTime({ + model: childTable, + rowIds: [rowId], + }); } break; case RelationTypes.HAS_MANY: @@ -5122,6 +5169,11 @@ class BaseModelSqlv2 { ); } await this.execAndParse(updateQb, null, { raw: true }); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [rowId], + }); } break; case RelationTypes.BELONGS_TO: @@ -5164,6 +5216,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [rowId], + }); } break; } @@ -5305,6 +5362,15 @@ class BaseModelSqlv2 { : childIds, ); await this.execAndParse(delQb, null, { raw: true }); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: childIds, + }); + await this.updateLastModifiedTime({ + model: childTable, + rowIds: [rowId], + }); } break; case RelationTypes.HAS_MANY: @@ -5385,6 +5451,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [rowId], + }); } break; case RelationTypes.BELONGS_TO: @@ -5430,6 +5501,11 @@ class BaseModelSqlv2 { null, { raw: true }, ); + + await this.updateLastModifiedTime({ + model: parentTable, + rowIds: [childIds[0]], + }); } break; } @@ -5515,14 +5591,21 @@ class BaseModelSqlv2 { } } - async updateLastModifiedTime({ rowIds }: { rowIds: any | any[] }) { + async updateLastModifiedTime({ + rowIds, + model = this.model, + knex = this.dbDriver, + }: { + rowIds: any | any[]; + model?: Model; + }) { const columnName = 'updated_at'; - const qb = this.dbDriver(this.tnPath).update({ + const qb = knex(model.table_name).update({ [columnName]: this.dbDriver.fn.now(), }); - for(const rowId of rowIds) { + for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) { qb.orWhere(await this._wherePk(rowId)); } From 2e7974ce31ac70797ce90a1624e67033f2349e8b Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 097/273] fix: missing prop in type --- packages/nocodb/src/db/BaseModelSqlv2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index ddee9ba865..ac1c71c3ff 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -5598,6 +5598,7 @@ class BaseModelSqlv2 { }: { rowIds: any | any[]; model?: Model; + knex?: XKnex; }) { const columnName = 'updated_at'; From 2a500f1f1bc2899db4f84bbc03e9e8c5d957bc98 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 098/273] fix: use execAndParse --- packages/nocodb/src/db/BaseModelSqlv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index ac1c71c3ff..e96136e6d6 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -5610,7 +5610,7 @@ class BaseModelSqlv2 { qb.orWhere(await this._wherePk(rowId)); } - await qb; + await this.execAndParse(qb, null, { raw: true }); } async prepareNocoData(data) { From 0007251dbb9f60b05c585ab22b1157229e1ddd6b Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 099/273] feat: populate system columns for created/updated --- packages/nocodb/src/db/BaseModelSqlv2.ts | 8 ++++++-- .../nocodb/src/services/columns.service.ts | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index e96136e6d6..25837c2008 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -5600,10 +5600,14 @@ class BaseModelSqlv2 { model?: Model; knex?: XKnex; }) { - const columnName = 'updated_at'; + const columnName = await model.getColumns().then((columns) => { + return columns.find((c) => c.uidt === UITypes.LastModifiedTime)?.column_name; + }); + + if(!columnName) return; const qb = knex(model.table_name).update({ - [columnName]: this.dbDriver.fn.now(), + [columnName]: Noco.ncMeta.now(), }); for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) { diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 7836078e44..e1c45a84e3 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1692,10 +1692,10 @@ export class ColumnsService { // else create a new column in meta and db const existingColumn = await table.getColumns().then((col) => { return col.find( - (c) => - c.uidt === colBody.uidt || - c.column_name === - (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), + (c) => c.uidt === colBody.uidt && c.system, + // || + // c.column_name === + // (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), ); }); @@ -1741,6 +1741,17 @@ export class ColumnsService { await sqlMgr.sqlOpPlus(source, 'tableUpdate', tableUpdateBody); } + const title = getUniqueColumnAliasName( + table.columns, + UITypes.CreateTime ? 'CreatedAt' : 'UpdatedAt', + ); + + await Column.insert({ + ...colBody, + title, + system: 1, + fk_model_id: table.id, + }); await Column.insert({ ...colBody, fk_model_id: table.id, From e58e221047a7a666f034ba78b0022562e666da8b Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 100/273] feat: add upgrader for migrating existing --- .../nocodb/src/version-upgrader/NcUpgrader.ts | 2 + .../ncXcdbCreatedAndUpdatedTimeUpgrader.ts | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedTimeUpgrader.ts diff --git a/packages/nocodb/src/version-upgrader/NcUpgrader.ts b/packages/nocodb/src/version-upgrader/NcUpgrader.ts index 804dc427f7..9c8c19976d 100644 --- a/packages/nocodb/src/version-upgrader/NcUpgrader.ts +++ b/packages/nocodb/src/version-upgrader/NcUpgrader.ts @@ -16,6 +16,7 @@ import ncHookUpgrader from './ncHookUpgrader'; import ncProjectConfigUpgrader from './ncProjectConfigUpgrader'; import ncXcdbLTARUpgrader from './ncXcdbLTARUpgrader'; import ncXcdbLTARIndexUpgrader from './ncXcdbLTARIndexUpgrader'; +import ncXcdbCreatedAndUpdatedTimeUpgrader from './ncXcdbCreatedAndUpdatedTimeUpgrader'; import type { MetaService } from '~/meta/meta.service'; import type { NcConfig } from '~/interface/config'; @@ -144,6 +145,7 @@ export default class NcUpgrader { { name: '0107004', handler: ncProjectConfigUpgrader }, { name: '0108002', handler: ncXcdbLTARUpgrader }, { name: '0111002', handler: ncXcdbLTARIndexUpgrader }, + { name: '0111004', handler: ncXcdbCreatedAndUpdatedTimeUpgrader }, ]; } } diff --git a/packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedTimeUpgrader.ts b/packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedTimeUpgrader.ts new file mode 100644 index 0000000000..912554418b --- /dev/null +++ b/packages/nocodb/src/version-upgrader/ncXcdbCreatedAndUpdatedTimeUpgrader.ts @@ -0,0 +1,66 @@ +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 } from '~/models'; + +// An upgrader for upgrading created_at and updated_at columns +// to system column and convert to new uidt CreateTime and LastModifiedTime + +const logger = new Logger('XcdbCreatedAndUpdatedTimeUpgrader'); + +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) => { + const columns = await model.getColumns(ncMeta); + for (const column of columns) { + if (column.column_name === 'created_at') { + await Column.update(column.id, { + uidt: UITypes.CreateTime, + system: true, + }); + } + if (column.uidt === 'updated_at') { + await Column.update(column.id, { + uidt: UITypes.LastModifiedTime, + system: true, + }); + } + } + logger.log(`Upgraded model ${model.name} from source ${source.name}`); + }), + ); +} + +// database to virtual relation and create an index for it +export default async function ({ ncMeta }: NcUpgraderCtx) { + // get all xcdb sources + const sources = await ncMeta.metaList2(null, null, MetaTable.BASES, { + condition: { + is_meta: 1, + }, + orderBy: {}, + }); + + // iterate and upgrade each base + for (const source of sources) { + logger.log(`Upgrading source ${source.name}`); + // update the meta props + await upgradeModels({ ncMeta, source }); + } +} From 3e4f59d11f6149d411ca50871f485ead0591a9ab Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 101/273] feat: create column with different name if already column exist --- packages/nocodb/src/db/BaseModelSqlv2.ts | 31 ++++++++--- .../nocodb/src/services/columns.service.ts | 55 ++++++++++--------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 25837c2008..04f9054947 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -101,12 +101,23 @@ function checkColumnRequired( return !fields || fields.includes(column.title); } -export function getColumnName(column: Column) { +export function getColumnName(column: Column, columns: Column[] = []) { switch (column.uidt) { - case UITypes.CreateTime: - return 'created_at'; - case UITypes.LastModifiedTime: - return 'updated_at'; + case UITypes.CreateTime: { + const createdTimeSystemCol = columns.find( + (col) => col.system && col.uidt === UITypes.CreateTime, + ); + if (createdTimeSystemCol) return createdTimeSystemCol.column_name; + return column.column_name || 'created_at'; + } + case UITypes.LastModifiedTime: { + const lastModifiedTimeSystemCol = columns.find( + (col) => col.system && col.uidt === UITypes.LastModifiedTime, + ); + if (lastModifiedTimeSystemCol) + return lastModifiedTimeSystemCol.column_name; + return column.column_name || 'created_at'; + } default: return column.column_name; } @@ -2128,7 +2139,10 @@ class BaseModelSqlv2 { case UITypes.LastModifiedTime: case UITypes.DateTime: { - const columnName = getColumnName(column); + const columnName = getColumnName( + column, + await this.model.getColumns(), + ); if (this.isMySQL) { // MySQL stores timestamp in UTC but display in timezone // To verify the timezone, run `SELECT @@global.time_zone, @@session.time_zone;` @@ -5601,10 +5615,11 @@ class BaseModelSqlv2 { knex?: XKnex; }) { const columnName = await model.getColumns().then((columns) => { - return columns.find((c) => c.uidt === UITypes.LastModifiedTime)?.column_name; + return columns.find((c) => c.uidt === UITypes.LastModifiedTime) + ?.column_name; }); - if(!columnName) return; + if (!columnName) return; const qb = knex(model.table_name).update({ [columnName]: Noco.ncMeta.now(), diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index e1c45a84e3..4c735a1280 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1688,34 +1688,36 @@ export class ColumnsService { case UITypes.CreateTime: case UITypes.LastModifiedTime: { + const columns = await table.getColumns(); // check if column already exists, then just create a new column in meta // else create a new column in meta and db - const existingColumn = await table.getColumns().then((col) => { - return col.find( - (c) => c.uidt === colBody.uidt && c.system, - // || - // c.column_name === - // (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), - ); - }); + const existingColumn = columns.find( + (c) => c.uidt === colBody.uidt && c.system, + // || + // c.column_name === + // (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), + ); if (!existingColumn) { - const sqlClient = await reuseOrSave('sqlClient', reuse, async () => - NcConnectionMgrv2.getSqlClient(source), - ); - const dbColumns = ( - await sqlClient.columnList({ - tn: table.table_name, - schema: source.getConfig()?.schema, - }) - )?.data?.list; + let columnName = + colBody.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'; + // const sqlClient = await reuseOrSave('sqlClient', reuse, async () => + // NcConnectionMgrv2.getSqlClient(source), + // ); + // const dbColumns = ( + // await sqlClient.columnList({ + // tn: table.table_name, + // schema: source.getConfig()?.schema, + // }) + // )?.data?.list; // todo: check type as well - const dbColumn = dbColumns.find( - (c) => - c.column_name === - (c.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'), - ); + const dbColumn = columns.find((c) => c.column_name === columnName); + + if (dbColumn) { + columnName = getUniqueColumnName(columns, columnName); + } + if (!dbColumn) { // create column in db @@ -1752,11 +1754,11 @@ export class ColumnsService { system: 1, fk_model_id: table.id, }); - await Column.insert({ - ...colBody, - fk_model_id: table.id, - }); } + await Column.insert({ + ...colBody, + fk_model_id: table.id, + }); } break; default: @@ -2775,6 +2777,7 @@ export class ColumnsService { await Column.update(column.id, colBody); } } + async columnsHash(tableId: string) { const table = await Model.getWithInfo({ id: tableId, From d4c9cbf74ae0fe6710df0dd2076564537b0d4794 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 102/273] feat: populate created and updated time whils inserting / updating , programmatically --- packages/nocodb/src/db/BaseModelSqlv2.ts | 44 +++++++++++++------ .../nocodb/src/services/columns.service.ts | 18 ++++++-- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 04f9054947..3abdca693d 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -18,6 +18,7 @@ import Validator from 'validator'; import { customAlphabet } from 'nanoid'; import DOMPurify from 'isomorphic-dompurify'; import { v4 as uuidv4 } from 'uuid'; +import type { SortType } from 'nocodb-sdk'; import type { Knex } from 'knex'; import type LookupColumn from '~/models/LookupColumn'; import type { XKnex } from '~/db/CustomKnex'; @@ -35,15 +36,6 @@ import type { SelectOption, User, } from '~/models'; -import type { SortType } from 'nocodb-sdk'; -import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; -import genRollupSelectv2 from '~/db/genRollupSelectv2'; -import conditionV2 from '~/db/conditionV2'; -import sortV2 from '~/db/sortV2'; -import { customValidators } from '~/db/util/customValidators'; -import { extractLimitAndOffset } from '~/helpers'; -import { NcError } from '~/helpers/catchError'; -import getAst from '~/helpers/getAst'; import { Audit, BaseUser, @@ -55,6 +47,14 @@ import { Source, View, } from '~/models'; +import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; +import genRollupSelectv2 from '~/db/genRollupSelectv2'; +import conditionV2 from '~/db/conditionV2'; +import sortV2 from '~/db/sortV2'; +import { customValidators } from '~/db/util/customValidators'; +import { extractLimitAndOffset } from '~/helpers'; +import { NcError } from '~/helpers/catchError'; +import getAst from '~/helpers/getAst'; import { sanitize, unsanitize } from '~/helpers/sqlSanitize'; import Noco from '~/Noco'; import { HANDLE_WEBHOOK } from '~/services/hook-handler.service'; @@ -2358,7 +2358,7 @@ class BaseModelSqlv2 { await this.beforeInsert(insertObj, trx, cookie); } - await this.prepareNocoData(insertObj); + await this.prepareNocoData(insertObj, true); let response; // const driver = trx ? trx : this.dbDriver; @@ -2709,7 +2709,6 @@ class BaseModelSqlv2 { this.clientMeta, this.dbDriver, ); - let rowId = null; const nestedCols = (await this.model.getColumns()).filter((c) => @@ -2725,6 +2724,8 @@ class BaseModelSqlv2 { await this.beforeInsert(insertObj, this.dbDriver, cookie); + await this.prepareNocoData(insertObj, true); + let response; const query = this.dbDriver(this.tnPath).insert(insertObj); @@ -3068,7 +3069,7 @@ class BaseModelSqlv2 { } } - await this.prepareNocoData(insertObj); + await this.prepareNocoData(insertObj, true); // prepare nested link data for insert only if it is single record insertion if (isSingleRecordInsertion) { @@ -5632,13 +5633,28 @@ class BaseModelSqlv2 { await this.execAndParse(qb, null, { raw: true }); } - async prepareNocoData(data) { + async prepareNocoData(data, isInsertData = false) { if ( this.model.columns.some((c) => - [UITypes.Attachment, UITypes.User].includes(c.uidt), + [ + UITypes.Attachment, + UITypes.User, + UITypes.CreateTime, + UITypes.LastModifiedTime, + ].includes(c.uidt), ) ) { for (const column of this.model.columns) { + if ( + isInsertData && + column.uidt === UITypes.CreateTime && + column.system + ) { + data[column.column_name] = Noco.ncMeta.now(); + } + if (column.uidt === UITypes.LastModifiedTime && column.system) { + data[column.column_name] = Noco.ncMeta.now(); + } if (column.uidt === UITypes.Attachment) { if (data[column.column_name]) { if (Array.isArray(data[column.column_name])) { diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 4c735a1280..bd1ff10969 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1688,6 +1688,7 @@ export class ColumnsService { case UITypes.CreateTime: case UITypes.LastModifiedTime: { + let columnName: string; const columns = await table.getColumns(); // check if column already exists, then just create a new column in meta // else create a new column in meta and db @@ -1699,7 +1700,7 @@ export class ColumnsService { ); if (!existingColumn) { - let columnName = + columnName = colBody.uidt === UITypes.CreateTime ? 'created_at' : 'updated_at'; // const sqlClient = await reuseOrSave('sqlClient', reuse, async () => // NcConnectionMgrv2.getSqlClient(source), @@ -1718,9 +1719,14 @@ export class ColumnsService { columnName = getUniqueColumnName(columns, columnName); } - if (!dbColumn) { - // create column in db + { + // remove default value for SQLite since it doesn't support default value as function when adding column + // only support default value as constant value + if (source.type === 'sqlite3') { + colBody.cdf = null; + } + // create column in db const tableUpdateBody = { ...table, tn: table.table_name, @@ -1732,7 +1738,7 @@ export class ColumnsService { ...table.columns.map((c) => ({ ...c, cn: c.column_name })), { ...colBody, - cn: UITypes.CreateTime ? 'created_at' : 'updated_at', + cn: columnName, altered: Altered.NEW_COLUMN, }, ], @@ -1753,11 +1759,15 @@ export class ColumnsService { title, system: 1, fk_model_id: table.id, + column_name: columnName, }); + } else { + columnName = existingColumn.column_name; } await Column.insert({ ...colBody, fk_model_id: table.id, + column_name: columnName, }); } break; From 60610e1e0191594d839f1f014aed576328711512 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 103/273] feat: change the default created/updated column type and mark as system field --- packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts | 6 ++++-- packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts | 10 ++++++---- packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts | 6 ++++-- packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts | 6 ++++-- packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts | 10 ++++++---- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts index 02f4e96acf..fd623f67be 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts @@ -107,9 +107,10 @@ export class MssqlUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.CreateTime, uip: '', uicn: '', + system: true, }, { column_name: 'updated_at', @@ -131,9 +132,10 @@ export class MssqlUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.LastModifiedTime, uip: '', uicn: '', + system: true, }, ]; } diff --git a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts index 1b8cc0c8eb..9ab2fbe3b3 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts @@ -111,9 +111,10 @@ export class MysqlUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.CreateTime, uip: '', uicn: '', + system: true, }, { column_name: 'updated_at', @@ -134,9 +135,10 @@ export class MysqlUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.LastModifiedTime, uip: '', uicn: '', + system: true, }, ]; } @@ -1073,11 +1075,11 @@ export class MysqlUi { break; case 'CreateTime': colProp.dt = 'timestamp'; - colProp.cdf = 'CURRENT_TIMESTAMP' + colProp.cdf = 'CURRENT_TIMESTAMP'; break; case 'LastModifiedTime': colProp.dt = 'timestamp'; - colProp.cdf = 'CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP' + colProp.cdf = 'CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'; break; case 'AutoNumber': colProp.dt = 'int'; diff --git a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts index 64457eea79..20f026ff3c 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts @@ -170,9 +170,10 @@ export class PgUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.CreateTime, uip: '', uicn: '', + system: true, }, { column_name: 'updated_at', @@ -194,9 +195,10 @@ export class PgUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.LastModifiedTime, uip: '', uicn: '', + system: true, }, ]; } diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts index 9f915a5100..48737ee8ba 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts @@ -106,9 +106,10 @@ export class SnowflakeUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.CreateTime, uip: '', uicn: '', + system: true, }, { column_name: 'updated_at', @@ -130,9 +131,10 @@ export class SnowflakeUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.LastModifiedTime, uip: '', uicn: '', + system: true, }, ]; } diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts index 2225e672b6..4869dd2bb9 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts @@ -94,9 +94,10 @@ export class SqliteUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.CreateTime, uip: '', uicn: '', + system: true, }, { column_name: 'updated_at', @@ -117,9 +118,10 @@ export class SqliteUi { dtxp: '', dtxs: '', altered: 1, - uidt: UITypes.DateTime, + uidt: UITypes.LastModifiedTime, uip: '', uicn: '', + system: true, }, ]; } @@ -628,11 +630,11 @@ export class SqliteUi { break; case 'CreateTime': colProp.dt = 'datetime'; - colProp.cdf = 'CURRENT_TIMESTAMP'; + colProp.cdf = 'CURRENT_TIMESTAMP'; break; case 'LastModifiedTime': colProp.dt = 'datetime'; - colProp.cdf = 'CURRENT_TIMESTAMP'; + colProp.cdf = 'CURRENT_TIMESTAMP'; break; case 'AutoNumber': colProp.dt = 'integer'; From 64bdee82bf321ecaa83f4f8e1356c1141ed75c4a Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:20 +0000 Subject: [PATCH 104/273] refactor: keep column name only in system field --- packages/nocodb/src/services/columns.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index bd1ff10969..a2dbb446a6 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -1767,7 +1767,7 @@ export class ColumnsService { await Column.insert({ ...colBody, fk_model_id: table.id, - column_name: columnName, + column_name: null, }); } break; From 54f6748fd3bf2f3105c36414f42a3979580b1c6c Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:21 +0000 Subject: [PATCH 105/273] refactor: block system column deletion --- packages/nocodb/src/services/columns.service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index a2dbb446a6..0c7f203cdd 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -2212,6 +2212,11 @@ export class ColumnsService { case UITypes.CreateTime: case UITypes.LastModifiedTime: { + if (column.system) { + NcError.badRequest( + `The column '${column.column_name}' is a system column and cannot be deleted.`, + ); + } await Column.delete(param.columnId, ncMeta); } break; From 1b708da05b45fad12247f84a0892e8372cd11cb3 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 4 Jan 2024 13:19:21 +0000 Subject: [PATCH 106/273] feat: hide dlete option from menu for created/updated system column --- packages/nc-gui/components/smartsheet/header/Menu.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/smartsheet/header/Menu.vue b/packages/nc-gui/components/smartsheet/header/Menu.vue index c3460abe27..6a1a5e1299 100644 --- a/packages/nc-gui/components/smartsheet/header/Menu.vue +++ b/packages/nc-gui/components/smartsheet/header/Menu.vue @@ -1,6 +1,7 @@