From c88ed1acaa5634be05de125ce5391bcca66fba38 Mon Sep 17 00:00:00 2001 From: mertmit Date: Thu, 5 Oct 2023 10:00:08 +0000 Subject: [PATCH 01/43] fix: handle trailing spaces for select columns --- packages/nc-gui/components/cell/MultiSelect.vue | 4 ++-- packages/nc-gui/components/cell/SingleSelect.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue index a93eebd236..a6c783096a 100644 --- a/packages/nc-gui/components/cell/MultiSelect.vue +++ b/packages/nc-gui/components/cell/MultiSelect.vue @@ -142,8 +142,8 @@ const selectedTitles = computed(() => } return 0 }) - : modelValue.split(',') - : modelValue + : modelValue.split(',').map((el) => el.trim()) + : modelValue.map((el) => el.trim()) : [], ) diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index f671553f7c..7eb76bc658 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/packages/nc-gui/components/cell/SingleSelect.vue @@ -102,7 +102,7 @@ const hasEditRoles = computed(() => isUIAllowed('dataEdit')) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value) const vModel = computed({ - get: () => tempSelectedOptState.value ?? modelValue, + get: () => tempSelectedOptState.value ?? modelValue?.trim(), set: (val) => { if (val && isNewOptionCreateEnabled.value && (options.value ?? []).every((op) => op.title !== val)) { tempSelectedOptState.value = val From 2c3cd572cdd73957d13d8c1c6346c4130358813b Mon Sep 17 00:00:00 2001 From: mertmit Date: Thu, 5 Oct 2023 10:00:08 +0000 Subject: [PATCH 02/43] fix: supported types for conversion --- packages/nocodb/src/services/columns.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts index 9b78a3f2bb..e613f79709 100644 --- a/packages/nocodb/src/services/columns.service.ts +++ b/packages/nocodb/src/services/columns.service.ts @@ -303,9 +303,14 @@ export class ColumnsService { ); } } else if ( - [UITypes.SingleLineText, UITypes.LongText].includes(column.uidt) + [ + UITypes.SingleLineText, + UITypes.Email, + UITypes.PhoneNumber, + UITypes.URL, + ].includes(column.uidt) ) { - // SingleLineText/LongText to SingleSelect/MultiSelect + // Text to SingleSelect/MultiSelect const dbDriver = await reuseOrSave('dbDriver', reuse, async () => NcConnectionMgrv2.get(source), ); From 26de94bfc1f3a60ae23ae8a533e6c7c92692e573 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 5 Oct 2023 10:24:47 +0000 Subject: [PATCH 03/43] refactor: allow only one token per user --- packages/nocodb/src/helpers/catchError.ts | 8 ++++++++ packages/nocodb/src/services/api-tokens.service.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/helpers/catchError.ts b/packages/nocodb/src/helpers/catchError.ts index 0165fae6c7..a775950f91 100644 --- a/packages/nocodb/src/helpers/catchError.ts +++ b/packages/nocodb/src/helpers/catchError.ts @@ -415,6 +415,8 @@ export default function ( return res.status(400).json({ msg: e.message, errors: e.errors }); } else if (e instanceof UnprocessableEntity) { return res.status(422).json({ msg: e.message }); + } else if (e instanceof NotAllowed) { + return res.status(405).json({ msg: e.message }); } next(e); } @@ -423,6 +425,8 @@ export default function ( export class BadRequest extends Error {} +export class NotAllowed extends Error {} + export class Unauthorized extends Error {} export class Forbidden extends Error {} @@ -476,4 +480,8 @@ export class NcError { static unprocessableEntity(message = 'Unprocessable entity') { throw new UnprocessableEntity(message); } + + static notAllowed(message = 'Not allowed') { + throw new NotAllowed(message); + } } diff --git a/packages/nocodb/src/services/api-tokens.service.ts b/packages/nocodb/src/services/api-tokens.service.ts index fae3626bb7..8f644b0d78 100644 --- a/packages/nocodb/src/services/api-tokens.service.ts +++ b/packages/nocodb/src/services/api-tokens.service.ts @@ -9,7 +9,7 @@ import { ApiToken } from '~/models'; @Injectable() export class ApiTokensService { - constructor(private readonly appHooksService: AppHooksService) {} + constructor(protected readonly appHooksService: AppHooksService) {} async apiTokenList(param: { userId: string }) { return await ApiToken.list(param.userId); From dfb677edd3c3d0b6df938fda3f1b81a50e0121c9 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 5 Oct 2023 10:24:48 +0000 Subject: [PATCH 04/43] refactor: allow only one token per user --- packages/nocodb/src/services/org-tokens.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/services/org-tokens.service.ts b/packages/nocodb/src/services/org-tokens.service.ts index d7b3ccbdb1..bb53169be7 100644 --- a/packages/nocodb/src/services/org-tokens.service.ts +++ b/packages/nocodb/src/services/org-tokens.service.ts @@ -10,7 +10,7 @@ import { ApiToken } from '~/models'; @Injectable() export class OrgTokensService { - constructor(private readonly appHooksService: AppHooksService) {} + constructor(protected readonly appHooksService: AppHooksService) {} async apiTokenList(param: { user: User; query: any }) { const fk_user_id = param.user.id; From e0f30b678b888d2c4d16c2c0b56be660781bbf7a Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 5 Oct 2023 10:24:48 +0000 Subject: [PATCH 05/43] refactor: limit token creation in ui level --- packages/nc-gui/components/account/Token.vue | 36 +++++++++++--------- packages/nc-gui/lang/en.json | 1 + 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/nc-gui/components/account/Token.vue b/packages/nc-gui/components/account/Token.vue index 049495e54a..8b0b84c1fd 100644 --- a/packages/nc-gui/components/account/Token.vue +++ b/packages/nc-gui/components/account/Token.vue @@ -2,7 +2,7 @@ import type { VNodeRef } from '@vue/runtime-core' import { message } from 'ant-design-vue' import type { ApiTokenType, RequestParams } from 'nocodb-sdk' -import { extractSdkResponseErrorMsg, ref, useApi, useCopy, useNuxtApp } from '#imports' +import { extractSdkResponseErrorMsg, isEeUI, ref, useApi, useCopy, useNuxtApp } from '#imports' const { api, isLoading } = useApi() @@ -164,21 +164,25 @@ const handleCancel = () => {
{{ $t('title.apiTokens') }}
- - - - - - + + + + + + + + +
{{ $t('msg.apiTokenCreate') }}
diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index 5d66c9b49f..b140958c9a 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -388,6 +388,7 @@ } }, "labels": { + "tokenLimit": "Only one token per user is allowed", "duplicateAttachment": "File with name {filename} already attached", "toAddress": "To Address", "subject": "Subject", From f768b35bee27ac1e24b4a57b73fb5119608df421 Mon Sep 17 00:00:00 2001 From: mertmit Date: Thu, 5 Oct 2023 23:00:22 +0530 Subject: [PATCH 06/43] refactor: jobs service reference Signed-off-by: mertmit --- packages/nocodb/src/modules/jobs/jobs.module.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/modules/jobs/jobs.module.ts b/packages/nocodb/src/modules/jobs/jobs.module.ts index c88d26b102..c82639d7eb 100644 --- a/packages/nocodb/src/modules/jobs/jobs.module.ts +++ b/packages/nocodb/src/modules/jobs/jobs.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bull'; // Jobs @@ -34,7 +34,7 @@ import { GlobalModule } from '~/modules/global/global.module'; @Module({ imports: [ - GlobalModule, + forwardRef(() => GlobalModule), DatasModule, MetasModule, ...(process.env.NC_REDIS_JOB_URL @@ -80,5 +80,6 @@ import { GlobalModule } from '~/modules/global/global.module'; SourceCreateProcessor, SourceDeleteProcessor, ], + exports: ['JobsService'], }) export class JobsModule {} From a7a634fd40e20546d20c3e5f6c8336989a43277e Mon Sep 17 00:00:00 2001 From: mertmit Date: Thu, 5 Oct 2023 18:20:37 +0000 Subject: [PATCH 07/43] fix: long text to single text paste --- .../nc-gui/composables/useMultiSelect/convertCellData.ts | 8 ++++++++ packages/nc-gui/composables/useMultiSelect/index.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 047086bdd8..5044ef1798 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -17,6 +17,14 @@ export default function convertCellData( if (value === '') return null switch (to) { + case UITypes.SingleLineText: + case UITypes.LongText: + // This is to remove the quotes added from LongText + // TODO (refactor): remove this when we have a better way to handle this + if (value.match(/^".*"$/)) { + return value.slice(1, -1) + } + return value case UITypes.Number: { const parsedNumber = Number(value) if (isNaN(parsedNumber)) { diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index fd613f770b..5339f9cf47 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -184,7 +184,7 @@ export function useMultiSelect( } if (columnObj.uidt === UITypes.LongText) { - textToCopy = `"${textToCopy.replace(/\"/g, '""')}"` + textToCopy = `"${textToCopy.replace(/"/g, '\\"')}"` } return textToCopy @@ -202,7 +202,7 @@ export function useMultiSelect( const value = valueToCopy(row, col) copyRow += `${value}` text = `${text}${value}${cols.length - 1 !== i ? '\t' : ''}` - jsonRow.push(col.uidt === UITypes.LongText ? value.replace(/^"/, '').replace(/"$/, '').replace(/""/g, '"') : value) + jsonRow.push(value) }) html += `${copyRow}` if (rows.length - 1 !== i) { From d5c0599547261b8bc403b5ec5d65743fd02672c8 Mon Sep 17 00:00:00 2001 From: mertmit Date: Thu, 5 Oct 2023 19:20:27 +0000 Subject: [PATCH 08/43] fix: default value with single quote --- .../nc-gui/components/cell/MultiSelect.vue | 2 +- .../nc-gui/components/cell/SingleSelect.vue | 2 +- .../nc-gui/components/smartsheet/Cell.vue | 5 +- .../smartsheet/column/DefaultValue.vue | 53 +++++++++---------- .../smartsheet/column/SelectOptions.vue | 21 +------- .../nocodb/src/services/columns.service.ts | 12 ++++- 6 files changed, 40 insertions(+), 55 deletions(-) diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue index a93eebd236..31c00476dd 100644 --- a/packages/nc-gui/components/cell/MultiSelect.vue +++ b/packages/nc-gui/components/cell/MultiSelect.vue @@ -255,7 +255,7 @@ async function addIfMissingAndSave() { } // Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped - if (!isMysql(column.value.source_id)) { + if (!isMysql(column.value.source_id) && !isPg(column.value.source_id)) { updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'") } } diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index f671553f7c..26c7ab4d8d 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/packages/nc-gui/components/cell/SingleSelect.vue @@ -183,7 +183,7 @@ async function addIfMissingAndSave() { } // Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped - if (!isMysql(column.value.source_id)) { + if (!isMysql(column.value.source_id) && !isPg(column.value.source_id)) { updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'") } } diff --git a/packages/nc-gui/components/smartsheet/Cell.vue b/packages/nc-gui/components/smartsheet/Cell.vue index 94366f1c50..a7fbfe7b11 100644 --- a/packages/nc-gui/components/smartsheet/Cell.vue +++ b/packages/nc-gui/components/smartsheet/Cell.vue @@ -63,7 +63,7 @@ interface Props { const props = defineProps() -const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled', 'update:value']) +const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled', 'update:cdf']) const column = toRef(props, 'column') @@ -118,8 +118,7 @@ const vModel = computed({ }, set: (val) => { if (isEditColumnMenu.value) { - column.value.cdf = val - emit('update:value', val) + emit('update:cdf', val) } else if (val !== props.modelValue) { currentRow.value.rowMeta.changed = true emit('update:modelValue', val) diff --git a/packages/nc-gui/components/smartsheet/column/DefaultValue.vue b/packages/nc-gui/components/smartsheet/column/DefaultValue.vue index b78a3e1842..2c772fa5bd 100644 --- a/packages/nc-gui/components/smartsheet/column/DefaultValue.vue +++ b/packages/nc-gui/components/smartsheet/column/DefaultValue.vue @@ -5,54 +5,49 @@ import { iconMap } from '#imports' const props = defineProps<{ value: any }>() -const emit = defineEmits(['update:value']) +const emits = defineEmits(['update:value']) + +const meta = inject(MetaInj, ref()) provide(EditColumnInj, ref(true)) -const vModel = useVModel(props, 'value', emit) +const vModel = useVModel(props, 'value', emits) const rowRef = ref({ row: {}, oldRow: {}, rowMeta: { - isUpdatedFromCopyNPaste: [vModel?.value.title], + isUpdatedFromCopyNPaste: [vModel.value?.title], }, }) -const cdfValue = computed({ - get: () => { - if (vModel.value.uidt === UITypes.MultiSelect || vModel.value.uidt === UITypes.SingleSelect) { - return (vModel.value.cdf ?? '').replaceAll("'", '') - } else if ( - vModel.value.uidt === UITypes.SingleLineText || - vModel.value.uidt === UITypes.LongText || - vModel.value.uidt === UITypes.Email || - vModel.value.uidt === UITypes.URL || - vModel.value.uidt === UITypes.JSON || - vModel.value.uidt === UITypes.DateTime || - vModel.value.uidt === UITypes.Time || - vModel.value.uidt === UITypes.Year || - vModel.value.uidt === UITypes.Date - ) { - return (vModel.value.cdf ?? '').replace(/^'/, '').replace(/'$/, '') - } - return vModel.value.cdf - }, - set: (value) => { - vModel.value.cdf = value - }, -}) +useProvideSmartsheetRowStore(meta, rowRef) -useProvideSmartsheetRowStore(vModel, rowRef) +const cdfValue = ref(null) + +const updateCdfValue = (cdf: string | null) => { + vModel.value.cdf = cdf + cdfValue.value = vModel.value.cdf +} + +onMounted(() => { + updateCdfValue(vModel.value.cdf.replace(/^'/, '').replace(/'$/, '')) +})