diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f54fe1975a..8a7cb36d56 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -10,6 +10,7 @@ on: - "packages/nc-gui/**" - "packages/nocodb/**" - ".github/workflows/ci-cd.yml" + - "tests/playwright/**" pull_request: types: [opened, reopened, synchronize, ready_for_review, labeled] branches: [develop] diff --git a/.github/workflows/playwright-test-workflow.yml b/.github/workflows/playwright-test-workflow.yml index c081910a21..b3b0026d62 100644 --- a/.github/workflows/playwright-test-workflow.yml +++ b/.github/workflows/playwright-test-workflow.yml @@ -98,6 +98,14 @@ jobs: working-directory: ./tests/playwright run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test:shard:${{ inputs.shard }} + # Stress test added/modified tests + - name: Fetch develop branch + working-directory: ./tests/playwright + run: git fetch origin develop + - name: Stress test + working-directory: ./tests/playwright + run: E2E_DB_TYPE=${{ inputs.db }} node ./scripts/stressTestNewlyAddedTest.js + # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1) - name: Run quick server and tests (pg) if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }} @@ -132,13 +140,19 @@ jobs: name: playwright-report-quick-${{ inputs.shard }} path: ./tests/playwright/playwright-report-quick/ retention-days: 2 - + - uses: actions/upload-artifact@v3 if: always() with: name: playwright-report-${{ inputs.db }}-${{ inputs.shard }} path: ./tests/playwright/playwright-report/ retention-days: 2 + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report-stress-${{ inputs.db }}-${{ inputs.shard }} + path: ./tests/playwright/playwright-report-stress/ + retention-days: 2 - uses: actions/upload-artifact@v3 if: always() with: diff --git a/packages/nc-gui/components/smartsheet/Form.vue b/packages/nc-gui/components/smartsheet/Form.vue index f33b6200b3..30b7da12c7 100644 --- a/packages/nc-gui/components/smartsheet/Form.vue +++ b/packages/nc-gui/components/smartsheet/Form.vue @@ -33,6 +33,8 @@ provide(IsGalleryInj, ref(false)) // todo: generate hideCols based on default values const hiddenCols = ['created_at', 'updated_at'] +const hiddenColTypes = [UITypes.Rollup, UITypes.Lookup, UITypes.Formula, UITypes.QrCode, UITypes.SpecificDBType] + const state = useGlobal() const formRef = ref() @@ -227,7 +229,7 @@ async function addAllColumns() { } function shouldSkipColumn(col: Record) { - return isDbRequired(col) || !!col.required || (!!col.rqd && !col.cdf) + return isDbRequired(col) || !!col.required || (!!col.rqd && !col.cdf) || col.uidt === UITypes.QrCode } async function removeAllColumns() { @@ -256,7 +258,7 @@ async function checkSMTPStatus() { } function setFormData() { - const col = (formColumnData as Record)?.value + const col = formColumnData?.value || [] formViewData.value = { ...formViewData.value, @@ -273,27 +275,14 @@ function setFormData() { emailMe.value = data[state.user.value?.email as string] localColumns.value = col - .filter( - (f: Record) => - f.show && - f.uidt !== UITypes.Rollup && - f.uidt !== UITypes.Lookup && - f.uidt !== UITypes.Formula && - f.uidt !== UITypes.SpecificDBType, - ) - .sort((a: Record, b: Record) => a.order - b.order) - .map((c: Record) => ({ ...c, required: !!(c.required || 0) })) - - systemFieldsIds.value = getSystemColumns(col).map((c: Record) => c.fk_column_id) + .filter((f) => f.show && !hiddenColTypes.includes(f.uidt)) + .sort((a, b) => a.order - b.order) + .map((c) => ({ ...c, required: !!c.required })) + + systemFieldsIds.value = getSystemColumns(col).map((c) => c.fk_column_id) hiddenColumns.value = col.filter( - (f: Record) => - !f.show && - !systemFieldsIds.value.includes(f.fk_column_id) && - f.uidt !== UITypes.Rollup && - f.uidt !== UITypes.Lookup && - f.uidt !== UITypes.Formula && - f.uidt !== UITypes.SpecificDBType, + (f) => !f.show && !systemFieldsIds.value.includes(f.fk_column_id) && !hiddenColTypes.includes(f.uidt), ) } diff --git a/packages/nc-gui/components/smartsheet/VirtualCell.vue b/packages/nc-gui/components/smartsheet/VirtualCell.vue index 4ab569fbce..dcf44f2c06 100644 --- a/packages/nc-gui/components/smartsheet/VirtualCell.vue +++ b/packages/nc-gui/components/smartsheet/VirtualCell.vue @@ -13,6 +13,7 @@ import { isHm, isLookup, isMm, + isQrCode, isRollup, provide, toRef, @@ -39,7 +40,6 @@ provide(RowInj, row) provide(CellValueInj, toRef(props, 'modelValue')) const isForm = inject(IsFormInj, ref(false)) - function onNavigate(dir: NavigateDir, e: KeyboardEvent) { emit('navigate', dir) @@ -58,6 +58,7 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) { + diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue index 0eb697dc36..4640e8189f 100644 --- a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue +++ b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue @@ -165,6 +165,7 @@ useEventListener('keydown', (e: KeyboardEvent) => { + diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue index e5b5ae7000..1ccb069b43 100644 --- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue @@ -26,6 +26,8 @@ const props = defineProps<{ const emit = defineEmits(['update:value']) +const uiTypesNotSupportedInFormulas = [UITypes.QrCode] + const vModel = useVModel(props, 'value', emit) const { setAdditionalValidations, validateInfos, sqlUi, column } = useColumnCreateStoreOrThrow() @@ -44,7 +46,9 @@ enum JSEPNode { const meta = inject(MetaInj, ref()) -const columns = computed(() => meta?.value?.columns || []) +const supportedColumns = computed( + () => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [], +) const validators = { formula_raw: [ @@ -97,8 +101,8 @@ const suggestionsList = computed(() => { syntax: formulas[fn].syntax, examples: formulas[fn].examples, })), - ...columns.value - .filter((c: Record) => { + ...supportedColumns.value + .filter((c) => { // skip system LTAR columns if (c.uidt === UITypes.LinkToAnotherRecord && c.system) return false // v1 logic? skip the current column @@ -237,11 +241,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n errors = new Set([...errors, ...typeErrors]) } else if (parsedTree.type === JSEPNode.IDENTIFIER) { - if ( - columns.value - .filter((c: Record) => !column || column.value?.id !== c.id) - .every((c: Record) => c.title !== parsedTree.name) - ) { + if (supportedColumns.value.filter((c) => !column || column.value?.id !== c.id).every((c) => c.title !== parsedTree.name)) { errors.add(`Column '${parsedTree.name}' is not available`) } @@ -249,8 +249,8 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n // e.g. formula1 -> formula2 -> formula1 should return circular reference error // get all formula columns excluding itself - const formulaPaths = columns.value - .filter((c: Record) => c.id !== column.value?.id && c.uidt === UITypes.Formula) + const formulaPaths = supportedColumns.value + .filter((c) => c.id !== column.value?.id && c.uidt === UITypes.Formula) .reduce((res: Record[], c: Record) => { // in `formula`, get all the (unique) target neighbours // i.e. all column id (e.g. cl_xxxxxxxxxxxxxx) with formula type @@ -258,7 +258,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n ...new Set( (c.colOptions.formula.match(/cl_\w{14}/g) || []).filter( (colId: string) => - columns.value.filter((col: ColumnType) => col.id === colId && col.uidt === UITypes.Formula).length, + supportedColumns.value.filter((col: ColumnType) => col.id === colId && col.uidt === UITypes.Formula).length, ), ), ] @@ -269,7 +269,9 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n return res }, []) // include target formula column (i.e. the one to be saved if applicable) - const targetFormulaCol = columns.value.find((c: ColumnType) => c.title === parsedTree.name && c.uidt === UITypes.Formula) + const targetFormulaCol = supportedColumns.value.find( + (c: ColumnType) => c.title === parsedTree.name && c.uidt === UITypes.Formula, + ) if (targetFormulaCol && column.value?.id) { formulaPaths.push({ @@ -362,7 +364,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t } } } else if (parsedTree.type === JSEPNode.IDENTIFIER) { - const col = columns.value.find((c) => c.title === parsedTree.name) + const col = supportedColumns.value.find((c) => c.title === parsedTree.name) if (col === undefined) { return @@ -432,6 +434,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t case UITypes.Button: case UITypes.Checkbox: case UITypes.Collaborator: + case UITypes.QrCode: default: typeErrors.add(`Not supported to reference column '${parsedTree.name}'`) break @@ -455,7 +458,7 @@ function getRootDataType(parsedTree: any): any { if (parsedTree.type === JSEPNode.CALL_EXP) { return formulas[parsedTree.callee.name].type } else if (parsedTree.type === JSEPNode.IDENTIFIER) { - const col = columns.value.find((c) => c.title === parsedTree.name) as Record + const col = supportedColumns.value.find((c) => c.title === parsedTree.name) as Record if (col?.uidt === UITypes.Formula) { return getRootDataType(jsep(col?.formula_raw)) } else { @@ -500,6 +503,7 @@ function getRootDataType(parsedTree: any): any { case UITypes.Button: case UITypes.Checkbox: case UITypes.Collaborator: + case UITypes.QrCode: default: return 'N/A' } @@ -561,7 +565,7 @@ function handleInput() { .complete(wordToComplete.value) ?.sort((x: Record, y: Record) => sortOrder[x.type] - sortOrder[y.type]) if (!isCurlyBracketBalanced()) { - suggestion.value = suggestion.value.filter((v: Record) => v.type === 'column') + suggestion.value = suggestion.value.filter((v) => v.type === 'column') } autocomplete.value = !!suggestion.value.length } diff --git a/packages/nc-gui/components/smartsheet/column/LookupOptions.vue b/packages/nc-gui/components/smartsheet/column/LookupOptions.vue index 823bb9b895..1bb039bc7d 100644 --- a/packages/nc-gui/components/smartsheet/column/LookupOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/LookupOptions.vue @@ -54,7 +54,9 @@ const columns = $computed(() => { return [] } - return metas[selectedTable.id].columns.filter((c: any) => !isSystemColumn(c)) + return metas[selectedTable.id].columns.filter((c: any) => { + return !(isSystemColumn(c) || c.uidt === UITypes.QrCode) + }) }) diff --git a/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue b/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue new file mode 100644 index 0000000000..be08672d7a --- /dev/null +++ b/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue @@ -0,0 +1,68 @@ + + + diff --git a/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts b/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts index 029543fb48..5c0290dd88 100644 --- a/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts +++ b/packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts @@ -8,6 +8,7 @@ import HMIcon from '~icons/mdi/table-arrow-right' import BTIcon from '~icons/mdi/table-arrow-left' import MMIcon from '~icons/mdi/table-network' import FormulaIcon from '~icons/mdi/math-integral' +import QrCodeScan from '~icons/mdi/qrcode-scan' import RollupIcon from '~icons/mdi/movie-roll' import CountIcon from '~icons/mdi/counter' import SpecificDBTypeIcon from '~icons/mdi/database-settings' @@ -29,6 +30,8 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => { return { icon: SpecificDBTypeIcon, color: 'text-grey' } case UITypes.Formula: return { icon: FormulaIcon, color: 'text-grey' } + case UITypes.QrCode: + return { icon: QrCodeScan, color: 'text-grey' } case UITypes.Lookup: switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) { case RelationTypes.MANY_TO_MANY: diff --git a/packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue b/packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue index 230494c6d7..96937c4a71 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue @@ -21,14 +21,17 @@ const localValue = computed({ const options = computed(() => meta.value?.columns ?.filter((c: ColumnType) => { - /** ignore hasmany and manytomany relations if it's using within sort menu */ - if (isSort) { + if (c.uidt === UITypes.QrCode) { + return false + } else if (isSort) { + /** ignore hasmany and manytomany relations if it's using within sort menu */ return !( c.uidt === UITypes.LinkToAnotherRecord && (c.colOptions as LinkToAnotherRecordType).type !== RelationTypes.BELONGS_TO ) - /** ignore virtual fields which are system fields ( mm relation ) */ + /** ignore virtual fields which are system fields ( mm relation ) and qr code fields */ } else { - return !c.colOptions || !c.system + const isVirtualSystemField = c.colOptions && c.system + return !isVirtualSystemField } }) .map((c: ColumnType) => ({ diff --git a/packages/nc-gui/components/virtual-cell/Formula.vue b/packages/nc-gui/components/virtual-cell/Formula.vue index 952d74dbf6..f0afb25e59 100644 --- a/packages/nc-gui/components/virtual-cell/Formula.vue +++ b/packages/nc-gui/components/virtual-cell/Formula.vue @@ -1,7 +1,7 @@