diff --git a/build-local-docker-image.sh b/build-local-docker-image.sh index a4ccab4a89..3869d86a82 100755 --- a/build-local-docker-image.sh +++ b/build-local-docker-image.sh @@ -8,35 +8,70 @@ # 3. build nocodb SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +LOG_FILE=${SCRIPT_DIR}/build-local-docker-image.log +ERROR="" -#build nocodb-sdk -echo "Building nocodb-sdk" -cd ${SCRIPT_DIR}/packages/nocodb-sdk -npm ci -npm run build - -# build nc-gui -echo "Building nc-gui" -export NODE_OPTIONS="--max_old_space_size=16384" -# generate static build of nc-gui -cd ${SCRIPT_DIR}/packages/nc-gui -npm ci -npm run generate - -# copy nc-gui build to nocodb dir -rsync -rvzh --delete ./dist/ ${SCRIPT_DIR}/packages/nocodb/docker/nc-gui/ - -#build nocodb -# build nocodb ( pack nocodb-sdk and nc-gui ) -cd ${SCRIPT_DIR}/packages/nocodb -npm install -EE=true ./node_modules/.bin/webpack --config webpack.local.config.js -# remove nocodb-sdk since it's packed with the build -npm uninstall --save nocodb-sdk - -# build docker -docker build . -f Dockerfile.local -t nocodb-local - -echo 'docker image with tag "nocodb-local" built sussessfully. Use below sample command to run the container' -echo 'docker run -d -p 3333:8080 --name nocodb-local nocodb-local ' +function build_sdk(){ + #build nocodb-sdk + cd ${SCRIPT_DIR}/packages/nocodb-sdk + npm ci || ERROR="sdk build failed" + npm run build || ERROR="sdk build failed" +} +function build_gui(){ + # build nc-gui + export NODE_OPTIONS="--max_old_space_size=16384" + # generate static build of nc-gui + cd ${SCRIPT_DIR}/packages/nc-gui + npm ci || ERROR="gui build failed" + npm run generate || ERROR="gui build failed" +} + +function copy_gui_artifacts(){ + # copy nc-gui build to nocodb dir + rsync -rvzh --delete ./dist/ ${SCRIPT_DIR}/packages/nocodb/docker/nc-gui/ || ERROR="copy_gui_artifacts failed" +} + +function package_nocodb(){ + #build nocodb + # build nocodb ( pack nocodb-sdk and nc-gui ) + cd ${SCRIPT_DIR}/packages/nocodb + npm install || ERROR="package_nocodb failed" + EE=true ./node_modules/.bin/webpack --config webpack.local.config.js || ERROR="package_nocodb failed" +} + +function build_image(){ + # build docker + docker build . -f Dockerfile.local -t nocodb-local || ERROR="build_image failed" +} + +function log_message(){ + if [[ ${ERROR} != "" ]]; + then + >&2 echo "build failed, Please check build-local-docker-image.log for more details" + >&2 echo "ERROR: ${ERROR}" + exit 1 + else + echo 'docker image with tag "nocodb-local" built sussessfully. Use below sample command to run the container' + echo 'docker run -d -p 3333:8080 --name nocodb-local nocodb-local ' + fi +} + +echo "Info: Building nocodb-sdk" | tee ${LOG_FILE} +build_sdk 1>> ${LOG_FILE} 2>> ${LOG_FILE} + +echo "Info: Building nc-gui" | tee -a ${LOG_FILE} +build_gui 1>> ${LOG_FILE} 2>> ${LOG_FILE} + +echo "Info: copy nc-gui build to nocodb dir" | tee -a ${LOG_FILE} +copy_gui_artifacts 1>> ${LOG_FILE} 2>> ${LOG_FILE} + +echo "Info: build nocodb, package nocodb-sdk and nc-gui" | tee -a ${LOG_FILE} +package_nocodb 1>> ${LOG_FILE} 2>> ${LOG_FILE} + +if [[ ${ERROR} == "" ]]; then + echo "Info: building docker image" | tee -a ${LOG_FILE} + build_image 1>> ${LOG_FILE} 2>> ${LOG_FILE} +fi + +log_message | tee -a ${LOG_FILE} diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue index beb16b58be..6889cfae2e 100644 --- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue @@ -149,28 +149,29 @@ function parseAndValidateFormula(formula: string) { function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = new Set()) { if (parsedTree.type === JSEPNode.CALL_EXP) { + const calleeName = parsedTree.callee.name.toUpperCase() // validate function name - if (!availableFunctions.includes(parsedTree.callee.name)) { - errors.add(`'${parsedTree.callee.name}' function is not available`) + if (!availableFunctions.includes(calleeName)) { + errors.add(`'${calleeName}' function is not available`) } // validate arguments - const validation = formulas[parsedTree.callee.name] && formulas[parsedTree.callee.name].validation + const validation = formulas[calleeName] && formulas[calleeName].validation if (validation && validation.args) { if (validation.args.rqd !== undefined && validation.args.rqd !== parsedTree.arguments.length) { - errors.add(`'${parsedTree.callee.name}' required ${validation.args.rqd} arguments`) + errors.add(`'${calleeName}' required ${validation.args.rqd} arguments`) } else if (validation.args.min !== undefined && validation.args.min > parsedTree.arguments.length) { - errors.add(`'${parsedTree.callee.name}' required minimum ${validation.args.min} arguments`) + errors.add(`'${calleeName}' required minimum ${validation.args.min} arguments`) } else if (validation.args.max !== undefined && validation.args.max < parsedTree.arguments.length) { - errors.add(`'${parsedTree.callee.name}' required maximum ${validation.args.max} arguments`) + errors.add(`'${calleeName}' required maximum ${validation.args.max} arguments`) } } parsedTree.arguments.map((arg: Record) => validateAgainstMeta(arg, errors)) // validate data type if (parsedTree.callee.type === JSEPNode.IDENTIFIER) { - const expectedType = formulas[parsedTree.callee.name].type + const expectedType = formulas[calleeName.toUpperCase()].type if (expectedType === formulaTypes.NUMERIC) { - if (parsedTree.callee.name === 'WEEKDAY') { + if (calleeName === 'WEEKDAY') { // parsedTree.arguments[0] = date validateAgainstType( parsedTree.arguments[0], @@ -202,7 +203,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n parsedTree.arguments.map((arg: Record) => validateAgainstType(arg, expectedType, null, typeErrors)) } } else if (expectedType === formulaTypes.DATE) { - if (parsedTree.callee.name === 'DATEADD') { + if (calleeName === 'DATEADD') { // parsedTree.arguments[0] = date validateAgainstType( parsedTree.arguments[0], @@ -236,7 +237,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n }, typeErrors, ) - } else if (parsedTree.callee.name === 'DATETIME_DIFF') { + } else if (calleeName === 'DATETIME_DIFF') { // parsedTree.arguments[0] = date validateAgainstType( parsedTree.arguments[0], @@ -504,8 +505,9 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t typeErrors.add(`${formulaTypes.NUMERIC} type is found but ${expectedType} type is expected`) } } else if (parsedTree.type === JSEPNode.CALL_EXP) { - if (formulas[parsedTree.callee.name]?.type && expectedType !== formulas[parsedTree.callee.name].type) { - typeErrors.add(`${expectedType} not matched with ${formulas[parsedTree.callee.name].type}`) + const calleeName = parsedTree.callee.name.toUpperCase() + if (formulas[calleeName]?.type && expectedType !== formulas[calleeName].type) { + typeErrors.add(`${expectedType} not matched with ${formulas[calleeName].type}`) } } return typeErrors @@ -514,7 +516,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t function getRootDataType(parsedTree: any): any { // given a parse tree, return the data type of it if (parsedTree.type === JSEPNode.CALL_EXP) { - return formulas[parsedTree.callee.name].type + return formulas[parsedTree.callee.name.toUpperCase()].type } else if (parsedTree.type === JSEPNode.IDENTIFIER) { const col = supportedColumns.value.find((c) => c.title === parsedTree.name) as Record if (col?.uidt === UITypes.Formula) { diff --git a/packages/nc-gui/composables/useSharedFormViewStore.ts b/packages/nc-gui/composables/useSharedFormViewStore.ts index fd8151c437..db93cf4b39 100644 --- a/packages/nc-gui/composables/useSharedFormViewStore.ts +++ b/packages/nc-gui/composables/useSharedFormViewStore.ts @@ -101,18 +101,10 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share {} as Record, ) - let order = 1 - - columns.value = meta?.value?.columns - ?.map((c: Record) => ({ - ...c, - fk_column_id: c.id, - fk_view_id: viewMeta.id, - ...(fieldById[c.id] ? fieldById[c.id] : {}), - order: (fieldById[c.id] && fieldById[c.id].order) || order++, - id: fieldById[c.id] && fieldById[c.id].id, - })) - .sort((a: Record, b: Record) => a.order - b.order) as Record[] + columns.value = viewMeta.model?.columns?.map((c) => ({ + ...c, + description: fieldById[c.id].description, + })) const _sharedViewMeta = (viewMeta as any).meta sharedViewMeta.value = isString(_sharedViewMeta) ? JSON.parse(_sharedViewMeta) : _sharedViewMeta diff --git a/packages/nc-gui/package-lock.json b/packages/nc-gui/package-lock.json index 67bc79e68c..39b3f597d9 100644 --- a/packages/nc-gui/package-lock.json +++ b/packages/nc-gui/package-lock.json @@ -30,7 +30,7 @@ "leaflet.markercluster": "^1.5.3", "locale-codes": "^1.3.1", "monaco-editor": "^0.33.0", - "nocodb-sdk": "0.107.2", + "nocodb-sdk": "file:../nocodb-sdk", "papaparse": "^5.3.2", "pinia": "^2.0.33", "qrcode": "^1.5.1", @@ -111,7 +111,6 @@ }, "../nocodb-sdk": { "version": "0.107.2", - "extraneous": true, "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", @@ -8776,6 +8775,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "devOptional": true, "funding": [ { "type": "individual", @@ -12294,21 +12294,8 @@ } }, "node_modules/nocodb-sdk": { - "version": "0.107.2", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.107.2.tgz", - "integrity": "sha512-anuWvUrtWvu7U5hKeW7jWZgLpAWZK6OYHrUXy+DAWmrEDVjnNv5voRVyv8QT1krKKw48KqSYr8jswz6vQ2cTBQ==", - "dependencies": { - "axios": "^0.21.1", - "jsep": "^1.3.6" - } - }, - "node_modules/nocodb-sdk/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } + "resolved": "../nocodb-sdk", + "link": true }, "node_modules/node-abi": { "version": "3.23.0", @@ -24810,7 +24797,8 @@ "follow-redirects": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "devOptional": true }, "form-data": { "version": "4.0.0", @@ -27360,22 +27348,22 @@ } }, "nocodb-sdk": { - "version": "0.107.2", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.107.2.tgz", - "integrity": "sha512-anuWvUrtWvu7U5hKeW7jWZgLpAWZK6OYHrUXy+DAWmrEDVjnNv5voRVyv8QT1krKKw48KqSYr8jswz6vQ2cTBQ==", + "version": "file:../nocodb-sdk", "requires": { + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.1", "axios": "^0.21.1", - "jsep": "^1.3.6" - }, - "dependencies": { - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - } + "cspell": "^4.1.0", + "eslint": "^7.8.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-functional": "^3.0.2", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-prettier": "^4.0.0", + "jsep": "^1.3.6", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "typescript": "^4.0.2" } }, "node-abi": { diff --git a/packages/nc-gui/package.json b/packages/nc-gui/package.json index 648604bbdb..847017b499 100644 --- a/packages/nc-gui/package.json +++ b/packages/nc-gui/package.json @@ -54,7 +54,7 @@ "leaflet.markercluster": "^1.5.3", "locale-codes": "^1.3.1", "monaco-editor": "^0.33.0", - "nocodb-sdk": "0.107.2", + "nocodb-sdk": "file:../nocodb-sdk", "papaparse": "^5.3.2", "pinia": "^2.0.33", "qrcode": "^1.5.1", diff --git a/packages/nocodb-sdk/package-lock.json b/packages/nocodb-sdk/package-lock.json index 0af2305204..d8c17ad5c8 100644 --- a/packages/nocodb-sdk/package-lock.json +++ b/packages/nocodb-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "nocodb-sdk", - "version": "0.107.1", + "version": "0.107.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nocodb-sdk", - "version": "0.107.1", + "version": "0.107.2", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index 0ddb2f4376..d002977eba 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -173,7 +173,7 @@ export function jsepTreeToFormula(node) { 'SWITCH', 'URL', ]; - if (!formulas.includes(node.name)) return '{' + node.name + '}'; + if (!formulas.includes(node.name.toUpperCase())) return '{' + node.name + '}'; return node.name; } diff --git a/packages/nocodb/Dockerfile.local b/packages/nocodb/Dockerfile.local index dff9c15b7d..07b3c07c00 100644 --- a/packages/nocodb/Dockerfile.local +++ b/packages/nocodb/Dockerfile.local @@ -21,6 +21,7 @@ COPY ./public/favicon.ico ./docker/public/ # install production dependencies, # reduce node_module size with modclean & removing sqlite deps, # package built code into app.tar.gz & add execute permission to start.sh +RUN npm uninstall --save nocodb-sdk RUN npm ci --omit=dev --quiet \ && npx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**" --run \ && rm -rf ./node_modules/sqlite3/deps \ diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json index 0b21f52d4f..ca74337df6 100644 --- a/packages/nocodb/package-lock.json +++ b/packages/nocodb/package-lock.json @@ -83,7 +83,7 @@ "nc-lib-gui": "0.107.2", "nc-plugin": "^0.1.3", "ncp": "^2.0.0", - "nocodb-sdk": "0.107.2", + "nocodb-sdk": "file:../nocodb-sdk", "nodemailer": "^6.4.10", "object-hash": "^3.0.0", "os-locale": "^6.0.2", @@ -191,7 +191,6 @@ }, "../nocodb-sdk": { "version": "0.107.2", - "extraneous": true, "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", @@ -13207,13 +13206,8 @@ } }, "node_modules/nocodb-sdk": { - "version": "0.107.2", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.107.2.tgz", - "integrity": "sha512-anuWvUrtWvu7U5hKeW7jWZgLpAWZK6OYHrUXy+DAWmrEDVjnNv5voRVyv8QT1krKKw48KqSYr8jswz6vQ2cTBQ==", - "dependencies": { - "axios": "^0.21.1", - "jsep": "^1.3.6" - } + "resolved": "../nocodb-sdk", + "link": true }, "node_modules/node-abort-controller": { "version": "3.1.1", @@ -28485,12 +28479,22 @@ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, "nocodb-sdk": { - "version": "0.107.2", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.107.2.tgz", - "integrity": "sha512-anuWvUrtWvu7U5hKeW7jWZgLpAWZK6OYHrUXy+DAWmrEDVjnNv5voRVyv8QT1krKKw48KqSYr8jswz6vQ2cTBQ==", + "version": "file:../nocodb-sdk", "requires": { + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.1", "axios": "^0.21.1", - "jsep": "^1.3.6" + "cspell": "^4.1.0", + "eslint": "^7.8.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-functional": "^3.0.2", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-prettier": "^4.0.0", + "jsep": "^1.3.6", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "typescript": "^4.0.2" } }, "node-abort-controller": { diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index 56be11bf5b..3821fac092 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -114,7 +114,7 @@ "nc-lib-gui": "0.107.2", "nc-plugin": "^0.1.3", "ncp": "^2.0.0", - "nocodb-sdk": "0.107.2", + "nocodb-sdk": "file:../nocodb-sdk", "nodemailer": "^6.4.10", "object-hash": "^3.0.0", "os-locale": "^6.0.2", @@ -201,4 +201,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts index f01061eb47..0c63753aa7 100644 --- a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts @@ -548,11 +548,11 @@ async function _formulaQueryBuilder( const colAlias = a ? ` as ${a}` : ''; pt.arguments?.forEach?.((arg) => { if (arg.fnName) return; - arg.fnName = pt.callee.name; + arg.fnName = pt.callee.name.toUpperCase(); arg.argsCount = pt.arguments?.length; }); if (pt.type === 'CallExpression') { - switch (pt.callee.name) { + switch (pt.callee.name.toUpperCase()) { case 'ADD': case 'SUM': if (pt.arguments.length > 1) { @@ -631,13 +631,14 @@ async function _formulaQueryBuilder( break; } + const calleeName = pt.callee.name.toUpperCase(); return { builder: knex.raw( - `${pt.callee.name}(${( + `${calleeName}(${( await Promise.all( pt.arguments.map(async (arg) => { let query = (await fn(arg)).builder.toQuery(); - if (pt.callee.name === 'CONCAT') { + if (calleeName === 'CONCAT') { if (knex.clientType() !== 'sqlite3') { query = await convertDateFormatForConcat( arg, diff --git a/packages/nocodb/src/db/mapFunctionName.ts b/packages/nocodb/src/db/mapFunctionName.ts index 669442e157..c351cabbbc 100644 --- a/packages/nocodb/src/db/mapFunctionName.ts +++ b/packages/nocodb/src/db/mapFunctionName.ts @@ -20,7 +20,7 @@ export interface MapFnArgs { } const mapFunctionName = async (args: MapFnArgs): Promise => { - const name = args.pt.callee.name; + const name = args.pt.callee.name.toUpperCase(); let val; switch (args.knex.clientType()) { diff --git a/packages/nocodb/webpack.local.config.js b/packages/nocodb/webpack.local.config.js index 60193094e9..606beb2fe2 100644 --- a/packages/nocodb/webpack.local.config.js +++ b/packages/nocodb/webpack.local.config.js @@ -42,7 +42,6 @@ module.exports = { globalObject: "typeof self !== 'undefined' ? self : this", }, node: { - fs: 'empty', __dirname: false, }, plugins: [new webpack.EnvironmentPlugin(['EE'])], diff --git a/tests/playwright/pages/SharedForm/index.ts b/tests/playwright/pages/SharedForm/index.ts index 2e27d08c94..0d6a32c2c5 100644 --- a/tests/playwright/pages/SharedForm/index.ts +++ b/tests/playwright/pages/SharedForm/index.ts @@ -29,4 +29,39 @@ export class SharedFormPage extends BasePage { }) ).toBeVisible(); } + + async clickLinkToChildList() { + await this.get().locator('button[data-testid="nc-child-list-button-link-to"]').click(); + } + + async verifyChildList(cardTitle?: string[]) { + await this.get().locator('.nc-modal-link-record').waitFor(); + const linkRecord = await this.get(); + + // DOM element validation + // title: Link Record + // button: Add new record + // icon: reload + await expect(this.get().locator(`.ant-modal-title`)).toHaveText(`Link record`); + + // add new record option is not available for shared form + await expect(await linkRecord.locator(`button:has-text("Add new record")`).isVisible()).toBeFalsy(); + + await expect(await linkRecord.locator(`.nc-reload`).isVisible()).toBeTruthy(); + // placeholder: Filter query + await expect(await linkRecord.locator(`[placeholder="Filter query"]`).isVisible()).toBeTruthy(); + + { + const childList = linkRecord.locator(`.ant-card`); + const childCards = await childList.count(); + await expect(childCards).toEqual(cardTitle.length); + for (let i = 0; i < cardTitle.length; i++) { + await expect(await childList.nth(i).textContent()).toContain(cardTitle[i]); + } + } + } + + async selectChildList(cardTitle: string) { + await this.get().locator(`.ant-card:has-text("${cardTitle}"):visible`).click(); + } } diff --git a/tests/playwright/tests/db/columnFormula.spec.ts b/tests/playwright/tests/db/columnFormula.spec.ts index 4aabefe37e..18ddb182af 100644 --- a/tests/playwright/tests/db/columnFormula.spec.ts +++ b/tests/playwright/tests/db/columnFormula.spec.ts @@ -134,6 +134,20 @@ const formulaDataByDbType = (context: NcContext) => [ formula: `IF((SEARCH({Address List}, "Parkway") != 0), "2.0","WRONG")`, result: ['WRONG', 'WRONG', 'WRONG', '2.0', '2.0'], }, + + // additional tests for formula case-insensitivity + { + formula: `weekday("2022-07-19")`, + result: ['1', '1', '1', '1', '1'], + }, + { + formula: `Weekday("2022-07-19")`, + result: ['1', '1', '1', '1', '1'], + }, + { + formula: `WeekDay("2022-07-19")`, + result: ['1', '1', '1', '1', '1'], + }, ]; test.describe('Virtual Columns', () => { diff --git a/tests/playwright/tests/db/viewForm.spec.ts b/tests/playwright/tests/db/viewForm.spec.ts index 558905dde0..d927ee8dc8 100644 --- a/tests/playwright/tests/db/viewForm.spec.ts +++ b/tests/playwright/tests/db/viewForm.spec.ts @@ -5,6 +5,8 @@ import { FormPage } from '../../pages/Dashboard/Form'; import { SharedFormPage } from '../../pages/SharedForm'; import { AccountPage } from '../../pages/Account'; import { AccountAppStorePage } from '../../pages/Account/AppStore'; +import { Api, UITypes } from 'nocodb-sdk'; +let api: Api; // todo: Move most of the ui actions to page object and await on the api response test.describe('Form view', () => { @@ -238,3 +240,127 @@ test.describe('Form view', () => { await sharedForm.verifySuccessMessage(); }); }); + +test.describe('Form view with LTAR', () => { + let dashboard: DashboardPage; + let form: FormPage; + let context: any; + + let cityTable: any, countryTable: any; + + test.beforeEach(async ({ page }) => { + context = await setup({ page, isEmptyProject: true }); + dashboard = new DashboardPage(page, context.project); + form = dashboard.form; + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + const cityColumns = [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'City', + title: 'City', + uidt: UITypes.SingleLineText, + pv: true, + }, + ]; + const countryColumns = [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'Country', + title: 'Country', + uidt: UITypes.SingleLineText, + pv: true, + }, + ]; + + try { + const project = await api.project.read(context.project.id); + cityTable = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'City', + title: 'City', + columns: cityColumns, + }); + countryTable = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'Country', + title: 'Country', + columns: countryColumns, + }); + + const cityRowAttributes = [{ City: 'Atlanta' }, { City: 'Pune' }, { City: 'London' }, { City: 'Sydney' }]; + await api.dbTableRow.bulkCreate('noco', context.project.id, cityTable.id, cityRowAttributes); + + const countryRowAttributes = [{ Country: 'India' }, { Country: 'UK' }, { Country: 'Australia' }]; + await api.dbTableRow.bulkCreate('noco', context.project.id, countryTable.id, countryRowAttributes); + + // create LTAR Country has-many City + await api.dbTableColumn.create(countryTable.id, { + column_name: 'CityList', + title: 'CityList', + uidt: UITypes.LinkToAnotherRecord, + parentId: countryTable.id, + childId: cityTable.id, + type: 'hm', + }); + + // await api.dbTableRow.nestedAdd('noco', context.project.id, countryTable.id, '1', 'hm', 'CityList', '1'); + } catch (e) { + console.log(e); + } + + // reload page after api calls + await page.reload(); + }); + + test('Form view with LTAR', async () => { + await dashboard.treeView.openTable({ title: 'Country' }); + + const url = dashboard.rootPage.url(); + + await dashboard.viewSidebar.createFormView({ title: 'NewForm' }); + await dashboard.form.toolbar.clickShareView(); + const formLink = await dashboard.form.toolbar.shareView.getShareLink(); + + await dashboard.rootPage.goto(formLink); + + const sharedForm = new SharedFormPage(dashboard.rootPage); + await sharedForm.cell.fillText({ + columnHeader: 'Country', + text: 'USA', + }); + await sharedForm.clickLinkToChildList(); + await sharedForm.verifyChildList(['Atlanta', 'Pune', 'London', 'Sydney']); + await sharedForm.selectChildList('Atlanta'); + + await sharedForm.submit(); + await sharedForm.verifySuccessMessage(); + + await dashboard.rootPage.goto(url); + await dashboard.viewSidebar.openView({ title: 'Country' }); + + await dashboard.grid.cell.verify({ + index: 3, + columnHeader: 'Country', + value: 'USA', + }); + await dashboard.grid.cell.verifyVirtualCell({ + index: 3, + columnHeader: 'CityList', + count: 1, + value: ['Atlanta'], + }); + }); +});