From 6e9a3fae27005a75cf1ce49148f51d08ffbe5310 Mon Sep 17 00:00:00 2001 From: Yannick Armspach Date: Thu, 12 Jan 2023 07:15:35 +0100 Subject: [PATCH 01/56] Remove the "t" in postgres path (#5306) Change postgrest:// to postgres:// --- docs/versioned_docs/version-2.0.0-beta/tooljet_database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/versioned_docs/version-2.0.0-beta/tooljet_database.md b/docs/versioned_docs/version-2.0.0-beta/tooljet_database.md index da3fa670f2..ede8e33b25 100644 --- a/docs/versioned_docs/version-2.0.0-beta/tooljet_database.md +++ b/docs/versioned_docs/version-2.0.0-beta/tooljet_database.md @@ -36,7 +36,7 @@ If this parameter is not specified then PostgREST refuses authentication request | PGRST_LOG_LEVEL | `info` | :::info -Please make sure that DB_URI is given in the format `postgrest://[USERNAME]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]` +Please make sure that DB_URI is given in the format `postgres://[USERNAME]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]` ::: #### Additional ToolJet server configuration From e0fe9a0cf9e4964aa44f40b982b3ae1dc10c1b0f Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:44:59 +0530 Subject: [PATCH 02/56] AMI fix for v2 (#5311) --- deploy/ec2/setup_app | 1 - deploy/ec2/tooljet_ubuntu_bionic.pkr.hcl | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/deploy/ec2/setup_app b/deploy/ec2/setup_app index 526d2bb909..33ecb4b033 100755 --- a/deploy/ec2/setup_app +++ b/deploy/ec2/setup_app @@ -36,7 +36,6 @@ then fi sudo npm --prefix server run db:setup:prod -sudo npm --prefix server run db:seed:prod if sudo systemctl start nest then diff --git a/deploy/ec2/tooljet_ubuntu_bionic.pkr.hcl b/deploy/ec2/tooljet_ubuntu_bionic.pkr.hcl index 8da1d7857e..b60a3ded77 100644 --- a/deploy/ec2/tooljet_ubuntu_bionic.pkr.hcl +++ b/deploy/ec2/tooljet_ubuntu_bionic.pkr.hcl @@ -51,6 +51,11 @@ build { destination = "/tmp/setup_app" } + provisioner "file" { + source = "postgrest.service" + destination = "/tmp/postgrest.service" + } + provisioner "shell" { script = "setup_machine.sh" } From d5775e20e9dc3243f9aa59136d853d77a7509d4c Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Thu, 12 Jan 2023 14:12:45 +0530 Subject: [PATCH 03/56] Added workflow dispatch (#5312) * Added workflow dispatch * adding Tooljet_db_* vars --- .github/workflows/packer-build.yml | 3 +++ deploy/ec2/.env | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index 9aa43f886b..e14404ced3 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -4,6 +4,9 @@ on: release: types: [published] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + jobs: packer: runs-on: ubuntu-latest diff --git a/deploy/ec2/.env b/deploy/ec2/.env index 5d50899e03..2e7f98451a 100644 --- a/deploy/ec2/.env +++ b/deploy/ec2/.env @@ -12,7 +12,12 @@ DEPLOYMENT_PLATFORM=ec2 # ToolJet Database ENABLE_TOOLJET_DB=false TOOLJET_DB=tooljet_db +TOOLJET_DB_USER= +TOOLJET_DB_HOST= +TOOLJET_DB_PASS= PGRST_HOST=localhost:3001 PGRST_SERVER_PORT=3001 PGRST_JWT_SECRET= PGRST_DB_URI= + + From 17abaf048e7780455b163d5e2bff3360e0bfd4d9 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Thu, 12 Jan 2023 15:23:18 +0530 Subject: [PATCH 04/56] Add and modify data-cy (#5314) --- frontend/src/Editor/Header/HeaderActions.jsx | 2 ++ frontend/src/Editor/Inspector/Inspector.jsx | 4 +++- frontend/src/Editor/LeftSidebar/SidebarInspector.jsx | 1 + frontend/src/Editor/LeftSidebar/SidebarItem.jsx | 12 +++++------- frontend/src/HomePage/AppMenu.jsx | 5 ++++- frontend/src/_ui/Layout/index.jsx | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/src/Editor/Header/HeaderActions.jsx b/frontend/src/Editor/Header/HeaderActions.jsx index 5e5fdbc7d6..57b320587e 100644 --- a/frontend/src/Editor/Header/HeaderActions.jsx +++ b/frontend/src/Editor/Header/HeaderActions.jsx @@ -25,6 +25,7 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout aria-selected="true" tabIndex="0" onClick={() => toggleCurrentLayout('desktop')} + data-cy={`button-change-layout-to-desktop`} > toggleCurrentLayout('mobile')} + data-cy={`button-change-layout-to-mobile`} >
switchSidebarTab(2)}> -
+
setSelectedTab('properties')} + data-cy={`sidebar-option-properties`} > {t('widget.common.properties', 'Properties')} @@ -390,6 +391,7 @@ export const Inspector = ({ aria-selected="false" tabIndex="-1" onClick={() => setSelectedTab('styles')} + data-cy={`sidebar-option-styles`} > {t('widget.common.styles', 'Styles')} diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx index 70ff345562..6d1adddef7 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx @@ -169,6 +169,7 @@ export const LeftSidebarInspector = ({ darkMode={darkMode} size="sm" styles={{ width: '28px', padding: 0 }} + data-cy={`left-sidebar-inspector`} > +
{icon && ( -
+
{commentBadge && }
diff --git a/frontend/src/HomePage/AppMenu.jsx b/frontend/src/HomePage/AppMenu.jsx index a7c6a8498e..23cb0b42cd 100644 --- a/frontend/src/HomePage/AppMenu.jsx +++ b/frontend/src/HomePage/AppMenu.jsx @@ -82,7 +82,10 @@ export const AppMenu = function AppMenu({ } > -
+
-
+
From 22b6e4a5edb7c6386010a8ab92c1f0e281a7a2fb Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Thu, 12 Jan 2023 19:38:45 +0530 Subject: [PATCH 05/56] Added TOOLJET_DB_* vars to render-preview-deploy (#5309) * Added TOOLJET_DB_* vars to render-preview-deploy Added the TOOLJET_DB_* vars * Update render-preview-deploy.yml --- .github/workflows/render-preview-deploy.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index 3f1e0ddb4b..14e450a2ee 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -65,6 +65,22 @@ jobs: "key": "TOOLJET_DB", "value": "${{ env.PR_NUMBER }}" }, + { + "key": "TOOLJET_DB_HOST", + "value": "${{ secrets.RENDER_PG_HOST }}" + }, + { + "key": "TOOLJET_DB_USER", + "value": "${{ secrets.RENDER_PG_USER }}" + }, + { + "key": "TOOLJET_DB_PASS", + "value": "${{ secrets.RENDER_PG_PASS }}" + }, + { + "key": "TOOLJET_DB_PORT", + "value": "5432" + }, { "key": "PGRST_DB_URI", "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}" From 22b349c8e8369c4474c63533d14531d87247df17 Mon Sep 17 00:00:00 2001 From: vjaris42 Date: Fri, 13 Jan 2023 12:13:13 +0530 Subject: [PATCH 06/56] fix: password reset on save for encrypted fields (#5323) --- frontend/src/Editor/DataSourceManager/DataSourceManager.jsx | 1 + server/src/services/data_sources.service.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx index c3ca2b97d2..7122751ad6 100644 --- a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx @@ -162,6 +162,7 @@ class DataSourceManagerComponent extends React.Component { key: key, value: options[key].value, encrypted: keyMeta ? keyMeta.encrypted : false, + ...(!options[key]?.value && { credential_id: options[key]?.credential_id }), }; }); if (name.trim() !== '') { diff --git a/server/src/services/data_sources.service.ts b/server/src/services/data_sources.service.ts index 9f037234ed..1e2fe73491 100644 --- a/server/src/services/data_sources.service.ts +++ b/server/src/services/data_sources.service.ts @@ -342,7 +342,8 @@ export class DataSourcesService { dataSource.options[option['key']] && dataSource.options[option['key']]['credential_id']; if (existingCredentialId) { - await this.credentialsService.update(existingCredentialId, option['value'] || ''); + (option['value'] || option['value'] === '') && + (await this.credentialsService.update(existingCredentialId, option['value'] || '')); parsedOptions[option['key']] = { credential_id: existingCredentialId, From c11a10152d0d24bc7ddad6057a8199ba7e06042f Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Fri, 13 Jan 2023 12:14:51 +0530 Subject: [PATCH 07/56] fix for packer-build yaml file (#5321) * fix for packer-action file * removed the reqiured field * corrections * corrections --- .github/workflows/packer-build.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index e14404ced3..092dce36b6 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -6,6 +6,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: + inputs: + tags: + description: "RELEASE_VERSION" jobs: packer: @@ -16,7 +19,12 @@ jobs: - name: Checkout Repository uses: actions/checkout@v2 - - name: Set env + - name: Setting tag + if: "${{ github.event.inputs.version != '' }}" + run: echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV + + - name: Set evn + if: "${{ github.event.release }}" run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Configure AWS Credentials From d42c77527634fff82492a7cf17c51f4511927f7b Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Fri, 13 Jan 2023 13:07:23 +0530 Subject: [PATCH 08/56] This commit has the corrections for input value (#5328) --- .github/workflows/packer-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index 092dce36b6..9f0609ce20 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -7,7 +7,7 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: inputs: - tags: + version: description: "RELEASE_VERSION" jobs: From 7ab793fe4988c6fa07b883c19ab2a5d0ecc54380 Mon Sep 17 00:00:00 2001 From: Manish Kushare Date: Mon, 16 Jan 2023 13:26:08 +0530 Subject: [PATCH 09/56] [ Bug fixed ] : datepicker UI in table widget column is broken (#5335) * bug fixed : datepicker UI in table widget column * made thead position static instead of sticky --- frontend/src/_styles/custom.scss | 8 +++++++- frontend/src/_styles/theme.scss | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/_styles/custom.scss b/frontend/src/_styles/custom.scss index f670d6841d..a3b3ebfaf2 100644 --- a/frontend/src/_styles/custom.scss +++ b/frontend/src/_styles/custom.scss @@ -7,11 +7,17 @@ position: fixed; thead { - display: table-header-group; + display: table-header-group !important; vertical-align: middle; + position: static !important; } tbody { display: table-row-group; + tr{ + td{ + width: 14.2857% !important; + } + } } tr { display: table-row; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 3586bb94ef..a34ad2c1eb 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -1647,7 +1647,7 @@ button { z-index: 2; } - .table thead th { + .table thead th:not(.rdtPrev):not(.rdtSwitch):not(.rdtNext):not(.dow){ display: flex !important; } From 5448e6d14827d37129194f219c1f5a049659e097 Mon Sep 17 00:00:00 2001 From: Yuku Takahashi Date: Mon, 16 Jan 2023 18:34:12 +0900 Subject: [PATCH 10/56] Enhance GraphQL plugin to be able to send custom headers (#5127) --- frontend/src/_components/DynamicForm.jsx | 10 ++- frontend/src/_ui/HttpHeaders/QueryEditor.jsx | 43 ++++++++++ frontend/src/_ui/HttpHeaders/SourceEditor.jsx | 64 ++++++++++++++ frontend/src/_ui/HttpHeaders/index.js | 83 ++++--------------- .../graphql/__tests__/graphql.test.js | 41 +++++++++ plugins/packages/graphql/lib/index.ts | 14 +++- plugins/packages/graphql/lib/manifest.json | 4 +- plugins/packages/graphql/lib/operations.json | 7 ++ plugins/packages/graphql/lib/types.ts | 1 + 9 files changed, 193 insertions(+), 74 deletions(-) create mode 100644 frontend/src/_ui/HttpHeaders/QueryEditor.jsx create mode 100644 frontend/src/_ui/HttpHeaders/SourceEditor.jsx diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index 21bdccbf57..595a92fbb7 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -155,12 +155,18 @@ const DynamicForm = ({ styles: computeSelectStyles ? computeSelectStyles('100%') : {}, useCustomStyles: computeSelectStyles ? true : false, }; - case 'react-component-headers': + case 'react-component-headers': { + const isRenderedAsQueryEditor = currentState != null; return { getter: key, - options: options?.[key]?.value ?? schema?.defaults?.[key]?.value, + options: isRenderedAsQueryEditor + ? options?.[key] ?? schema?.defaults?.[key] + : options?.[key]?.value ?? schema?.defaults?.[key]?.value, optionchanged, + currentState, + isRenderedAsQueryEditor, }; + } case 'react-component-oauth-authentication': return { grant_type: options.grant_type?.value, diff --git a/frontend/src/_ui/HttpHeaders/QueryEditor.jsx b/frontend/src/_ui/HttpHeaders/QueryEditor.jsx new file mode 100644 index 0000000000..e775ae0868 --- /dev/null +++ b/frontend/src/_ui/HttpHeaders/QueryEditor.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter'; + +export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairValueChanged, currentState }) => { + return ( +
+ {options.map((option, index) => { + return ( +
+
+
+ keyValuePairValueChanged(value, 0, index)} + componentName={`HttpHeaders::key::${index}`} + /> +
+
+ keyValuePairValueChanged(value, 1, index)} + componentName={`HttpHeaders::value::${index}`} + /> +
+
+ +
+ ); + })} + +
+ ); +}; diff --git a/frontend/src/_ui/HttpHeaders/SourceEditor.jsx b/frontend/src/_ui/HttpHeaders/SourceEditor.jsx new file mode 100644 index 0000000000..d6bc25c821 --- /dev/null +++ b/frontend/src/_ui/HttpHeaders/SourceEditor.jsx @@ -0,0 +1,64 @@ +import React from 'react'; + +export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairValueChanged }) => { + return ( +
+ + + + + + + + + + {options.map((option, index) => { + return ( + + + + {index > 0 && ( + + )} + {index === 0 && ( + + )} + + ); + })} + +
KeyValue
+ keyValuePairValueChanged(e.target.value, 0, index)} + /> + + keyValuePairValueChanged(e.target.value, 1, index)} + /> + + { + removeKeyValuePair(index); + }} + > + x + + + +
+
+ ); +}; diff --git a/frontend/src/_ui/HttpHeaders/index.js b/frontend/src/_ui/HttpHeaders/index.js index 498d056a79..d50ffafc37 100644 --- a/frontend/src/_ui/HttpHeaders/index.js +++ b/frontend/src/_ui/HttpHeaders/index.js @@ -1,6 +1,8 @@ import React from 'react'; +import QueryEditor from './QueryEditor'; +import SourceEditor from './SourceEditor'; -export default ({ getter, options = [['', '']], optionchanged }) => { +export default ({ getter, options = [['', '']], optionchanged, currentState, isRenderedAsQueryEditor }) => { function addNewKeyValuePair() { const newPairs = [...options, ['', '']]; optionchanged(getter, newPairs); @@ -11,79 +13,26 @@ export default ({ getter, options = [['', '']], optionchanged }) => { optionchanged(getter, options); } - function keyValuePairValueChanged(e, keyIndex, index) { - if (options.length - 1 === index) { + function keyValuePairValueChanged(value, keyIndex, index) { + if (!isRenderedAsQueryEditor && options.length - 1 === index) { setTimeout(() => { addNewKeyValuePair(); }, 100); } - const value = e.target.value; options[index][keyIndex] = value; optionchanged(getter, options); } - return ( -
- - - - - - - - - - {options.map((option, index) => { - return ( - - - - {index > 0 && ( - - )} - {index === 0 && ( - - )} - - ); - })} - -
KeyValue
- keyValuePairValueChanged(e, 0, index)} - /> - - keyValuePairValueChanged(e, 1, index)} - /> - - { - removeKeyValuePair(index); - }} - > - x - - - -
-
+ const commonProps = { + options, + addNewKeyValuePair, + removeKeyValuePair, + keyValuePairValueChanged, + }; + + return isRenderedAsQueryEditor ? ( + + ) : ( + ); }; diff --git a/plugins/packages/graphql/__tests__/graphql.test.js b/plugins/packages/graphql/__tests__/graphql.test.js index 5215350e5e..31f55aa6a8 100644 --- a/plugins/packages/graphql/__tests__/graphql.test.js +++ b/plugins/packages/graphql/__tests__/graphql.test.js @@ -18,4 +18,45 @@ describe('graphql', () => { expect(result.data['data']['launchesPast'].length).toBe(10); }); + + it('should merge headers query and source', async () => { + const sourceOptions = { + url: 'https://example.com/graphql', + headers: [ + ['source-only-header', 'source value'], + ['source-query-header', 'should be overriden'], + ], + url_params: [], + } + const queryOptions = { + query: '{ greeting }', + headers: [ + ['source-query-header', 'query takes precedence over source'], + ['query-only-header', 'query value'], + ], + } + + const _graphql = new graphql.default(); + + const spy = jest.spyOn(_graphql, 'sendRequest').mockImplementation(() => ({ + body: JSON.stringify({ data: { greeting: 'hello' }}) + })) + + const result = await _graphql.run(sourceOptions, queryOptions, 'no-datasource-id') + + expect(result.data).toEqual({ data: { greeting: 'hello' } }); + expect(spy).toHaveBeenCalledWith('https://example.com/graphql', { + method: 'post', + headers: { + 'source-only-header': 'source value', + 'source-query-header': 'query takes precedence over source', + 'query-only-header': 'query value', + }, + searchParams: {}, + json: { + query: '{ greeting }', + variables: {}, + } + }); + }); }); diff --git a/plugins/packages/graphql/lib/index.ts b/plugins/packages/graphql/lib/index.ts index 731cef01cc..aa00a583d9 100644 --- a/plugins/packages/graphql/lib/index.ts +++ b/plugins/packages/graphql/lib/index.ts @@ -4,16 +4,24 @@ import got from 'got'; import { SourceOptions, QueryOptions } from './types'; export default class GraphqlQueryService implements QueryService { + constructor(private sendRequest = got) {} + async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise { let result = {}; const url = sourceOptions.url; const { query, variables } = queryOptions; - const headers = Object.fromEntries(sourceOptions['headers']); + // Query takes precedence over source. + const headers = { + ...Object.fromEntries(sourceOptions['headers']), + ...Object.fromEntries(queryOptions['headers'] ?? []), + }; + const searchParams = Object.fromEntries(sourceOptions['url_params']); - // Remove invalid headers from the headers object + // Remove invalid entries from the headers and searchParams objects Object.keys(headers).forEach((key) => (headers[key] === '' ? delete headers[key] : {})); + Object.keys(searchParams).forEach((key) => (searchParams[key] === '' ? delete searchParams[key] : {})); const json = { query, @@ -21,7 +29,7 @@ export default class GraphqlQueryService implements QueryService { }; try { - const response = await got(url, { + const response = await this.sendRequest(url, { method: 'post', headers, searchParams, diff --git a/plugins/packages/graphql/lib/manifest.json b/plugins/packages/graphql/lib/manifest.json index acbae7f0ab..a9eb4c3469 100644 --- a/plugins/packages/graphql/lib/manifest.json +++ b/plugins/packages/graphql/lib/manifest.json @@ -59,13 +59,13 @@ "label": "Headers", "key": "headers", "type": "react-component-headers", - "description": "key-value pair headers for rest api" + "description": "key-value pair headers for graphql api" }, "url_params": { "label": "URL parameters", "key": "url_params", "type": "react-component-headers", - "description": "key-value pair url parameters for rest api" + "description": "key-value pair url parameters for graphql api" } }, "required": [ diff --git a/plugins/packages/graphql/lib/operations.json b/plugins/packages/graphql/lib/operations.json index 34148e4e52..be0a7ca382 100644 --- a/plugins/packages/graphql/lib/operations.json +++ b/plugins/packages/graphql/lib/operations.json @@ -18,6 +18,13 @@ "type": "codehinter", "description": "Enter Variables", "height": "150px" + }, + "headers": { + "label": "Headers", + "key": "headers", + "type": "react-component-headers", + "description": "key-value pair headers for graphql api", + "height": "150px" } }, "required": [ diff --git a/plugins/packages/graphql/lib/types.ts b/plugins/packages/graphql/lib/types.ts index e8ef35fa04..5abd517ba5 100644 --- a/plugins/packages/graphql/lib/types.ts +++ b/plugins/packages/graphql/lib/types.ts @@ -2,5 +2,6 @@ export type SourceOptions = { url: string; headers: any; url_params: any }; export type QueryOptions = { operation: string; query: string; + headers?: [string, string][]; variables?: object; }; From 75df2eb078da21aecc7aea3ac34e61470aa98fb3 Mon Sep 17 00:00:00 2001 From: RrobertRr <25011725+RrobertRr@users.noreply.github.com> Date: Tue, 17 Jan 2023 07:16:47 +0100 Subject: [PATCH 11/56] [docs] Update run-py.md (#5333) * Update run-py.md Changed 'Run JavaScript code' to 'Run PythonScript code' * Update docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md Co-authored-by: Shubhendra Singh Chauhan --- docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md b/docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md index 6dbfce18b1..4a1b749cb9 100644 --- a/docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md +++ b/docs/versioned_docs/version-2.0.0-beta/data-sources/run-py.md @@ -14,7 +14,7 @@ You can write custom Python code to interact with components and queries. To do #### Example: Using Python code to trigger component specific actions - Let's drag a **button** and a **text** widget onto the canvas. We will set a text on the text component and trigger button click event from the Python query. -- Click on the `+` on the query panel to create a query and select **Run JavaScript code** from the available datasources +- Click on the `+` on the query panel to create a query and select **Run Python code** from the available datasources - Let's write the code in **Python Editor** and save the query: ```python @@ -52,4 +52,4 @@ You can also write custom Python code to get the data from **External APIs** and :::info Issues with writing custom Python code? Ask in our [Slack community](https://www.tooljet.com/slack). -::: \ No newline at end of file +::: From abe4e7b0993f7a5b9c6db6441ba36063e2bcf796 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 17 Jan 2023 14:35:21 +0530 Subject: [PATCH 12/56] Added automation for AWS S3 and MySQL connection. (#5071) * Add happypath for aws s3 * Add happypath for MySQL * Add common DS methods to utils * Add creds to env * Add minor changes on clear and type --- cypress-tests/cypress.config.js | 14 +- .../cypress/constants/selectors/awss3.js | 9 + .../cypress/constants/texts/awss3.js | 18 ++ .../cypress/constants/texts/mysql.js | 9 + .../editor/data-source/mysqlHappyPath.cy.js | 181 ++++++++++++++++++ .../e2e/editor/data-source/s3HappyPath.cy.js | 176 +++++++++++++++++ .../cypress/support/utils/dataSource.js | 14 ++ .../cypress/support/utils/postgreSql.js | 14 +- 8 files changed, 428 insertions(+), 7 deletions(-) create mode 100644 cypress-tests/cypress/constants/selectors/awss3.js create mode 100644 cypress-tests/cypress/constants/texts/awss3.js create mode 100644 cypress-tests/cypress/constants/texts/mysql.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/mysqlHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/s3HappyPath.cy.js create mode 100644 cypress-tests/cypress/support/utils/dataSource.js diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 44723c77f9..d046d98487 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -1,6 +1,6 @@ const { defineConfig } = require("cypress"); const { rmdir } = require("fs"); -const pg = require('pg'); +const pg = require("pg"); module.exports = defineConfig({ execTimeout: 1800000, @@ -19,6 +19,11 @@ module.exports = defineConfig({ sso_password: "", git_user: "", google_user: "", + mysql_host: "", + mysql_user: "", + mysql_password: "", + aws_access: "", + aws_secret: "", }, db: { user: "postgres", @@ -44,15 +49,16 @@ module.exports = defineConfig({ }); on("task", { - UpdateId({dbconfig,sql}){ + UpdateId({ dbconfig, sql }) { const client = new pg.Pool(dbconfig); return client.query(sql); - } - }) + }, + }); return require("./cypress/plugins/index.js")(on, config); }, experimentalModfyObstructiveThirdPartyCode: true, + experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", specPattern: "cypress/e2e/**/*.cy.js", }, diff --git a/cypress-tests/cypress/constants/selectors/awss3.js b/cypress-tests/cypress/constants/selectors/awss3.js new file mode 100644 index 0000000000..59bdebacff --- /dev/null +++ b/cypress-tests/cypress/constants/selectors/awss3.js @@ -0,0 +1,9 @@ +export const s3Selector = { + awsDatasource: '[data-cy="data-source-aws s3"]', + accessKeyLabel: '[data-cy="label-access-key"]', + secretKeyLabel: '[data-cy="label-secret-key"]', + regionLabel: '[data-cy="label-region"]', + customEndpointLabel: '[data-cy="label-custom-endpoint"]', + customEndpointInput: '[data-cy="undefined-text-field"]', + dataSourceNameInput: '[data-cy="data-source-name-input-filed"]', +}; diff --git a/cypress-tests/cypress/constants/texts/awss3.js b/cypress-tests/cypress/constants/texts/awss3.js new file mode 100644 index 0000000000..534aefc325 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/awss3.js @@ -0,0 +1,18 @@ +export const s3Text = { + awsS3: "AWS S3", + accessKey: "Access key", + secretKey: "Secret keyEncrypted", + region: "Region", + customEndpoint: "Custom Endpoint", + alertRegionIsMissing: "Region is missing", + cypressAwsS3: "cypress-aws-s3", + placeholderEnterAccessKey: "Enter access key", + placeholderEnterSecretKey: "Enter secret key", + labelRegion: "Region", + region: "N. california", + alertInvalidUrl: "Invalid URL:", + accessKeyError: + "The AWS Access Key Id you provided does not exist in our records.", + sinatureError: + "The request signature we calculated does not match the signature you provided. Check your key and signing method.", +}; diff --git a/cypress-tests/cypress/constants/texts/mysql.js b/cypress-tests/cypress/constants/texts/mysql.js new file mode 100644 index 0000000000..8c1ac7dfb7 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/mysql.js @@ -0,0 +1,9 @@ +export const mySqlText = { + errorConnectionRefused: "connect ECONNREFUSED", + errorUnknownDb: "ER_BAD_DB_ERROR: Unknown database 'test_db1'", + errorAccessDeniedAdmin1: + "ER_ACCESS_DENIED_ERROR: Access denied for user 'admin1'", + errorAccessDeniedAdmin: + "ER_ACCESS_DENIED_ERROR: Access denied for user 'admin'", + cypressMySql: "cypress-mysql", +}; diff --git a/cypress-tests/cypress/e2e/editor/data-source/mysqlHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/mysqlHappyPath.cy.js new file mode 100644 index 0000000000..9456989da3 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/mysqlHappyPath.cy.js @@ -0,0 +1,181 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { mySqlText } from "Texts/mysql"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; +describe("Data sources MySql", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on MySQL connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type("MySQL"); + cy.get("[data-cy*='data-source-']").eq(0).should("contain", "MySQL"); + cy.get('[data-cy="data-source-mysql"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + "MySQL" + ); + cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + "have.text", + postgreSqlText.labelHost + ); + cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + "have.text", + postgreSqlText.labelPort + ); + cy.get(postgreSqlSelector.labelSsl).verifyVisibleElement( + "have.text", + postgreSqlText.labelSSL + ); + cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement( + "have.text", + postgreSqlText.labelDbName + ); + cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( + "have.text", + postgreSqlText.labelUserName + ); + cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( + "have.text", + postgreSqlText.labelPassword + ); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + verifyCouldnotConnectWithAlert(mySqlText.errorConnectionRefused); + }); + + it("Should verify the functionality of MySQL connection form.", () => { + selectDataSource("MySQL"); + + cy.clearAndType( + '[data-cy="data-source-name-input-filed"]', + mySqlText.cypressMySql + ); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + Cypress.env("mysql_host") + ); + fillDataSourceTextField( + postgreSqlText.labelPort, + postgreSqlText.placeholderEnterPort, + "3306" + ); + fillDataSourceTextField( + postgreSqlText.labelDbName, + postgreSqlText.placeholderNameOfDB, + "test_db1" + ); + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "admin" + ); + + cy.get(postgreSqlSelector.passwordTextField).type( + Cypress.env("mysql_password") + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(mySqlText.errorUnknownDb); + fillDataSourceTextField( + postgreSqlText.labelDbName, + postgreSqlText.placeholderNameOfDB, + "test_db" + ); + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "admin1" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(mySqlText.errorAccessDeniedAdmin1); + + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "admin" + ); + cy.get(postgreSqlSelector.passwordTextField).type("testpassword"); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(mySqlText.errorAccessDeniedAdmin); + cy.get(postgreSqlSelector.passwordTextField).type( + `{selectAll}{backspace}${Cypress.env("mysql_password")}` + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", mySqlText.cypressMySql) + .find("button") + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/s3HappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/s3HappyPath.cy.js new file mode 100644 index 0000000000..16b7cf1455 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/s3HappyPath.cy.js @@ -0,0 +1,176 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { s3Selector } from "Selectors/awss3"; +import { postgreSqlText } from "Texts/postgreSql"; +import { s3Text } from "Texts/awss3"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; +describe("Data sources AWS S3", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on AWS S3 connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type(s3Text.awsS3); + cy.get("[data-cy*='data-source-']").eq(0).should("contain", s3Text.awsS3); + cy.get(s3Selector.awsDatasource).click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + s3Text.awsS3 + ); + cy.get(s3Selector.accessKeyLabel).verifyVisibleElement( + "have.text", + s3Text.accessKey + ); + cy.get(s3Selector.secretKeyLabel).verifyVisibleElement( + "have.text", + s3Text.secretKey + ); + cy.get(s3Selector.regionLabel).verifyVisibleElement( + "have.text", + s3Text.labelRegion + ); + cy.get(s3Selector.customEndpointLabel) + .verifyVisibleElement("have.text", s3Text.customEndpoint) + .next() + .find("input") + .click(); + + cy.get(s3Selector.customEndpointInput).should("be.visible"); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + verifyCouldnotConnectWithAlert(s3Text.alertRegionIsMissing); + }); + + it("Should verify the functionality of AWS S3 connection form.", () => { + selectDataSource(s3Text.awsS3); + + cy.clearAndType(s3Selector.dataSourceNameInput, s3Text.cypressAwsS3); + + fillDataSourceTextField( + s3Text.accessKey, + s3Text.placeholderEnterAccessKey, + Cypress.env("aws_access") + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(s3Text.alertRegionIsMissing); + + fillDataSourceTextField( + "Secret key", + s3Text.placeholderEnterSecretKey, + Cypress.env("aws_secret"), + "contain" + ); + + cy.get(s3Selector.regionLabel) + .next() + .find("input") + .type(`${s3Text.region}{enter}`); + + cy.get(s3Selector.customEndpointLabel) + .verifyVisibleElement("have.text", s3Text.customEndpoint) + .next() + .find("input") + .click(); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(s3Text.alertInvalidUrl); + cy.get(s3Selector.customEndpointLabel) + .verifyVisibleElement("have.text", s3Text.customEndpoint) + .next() + .find("input") + .click(); + + fillDataSourceTextField( + s3Text.accessKey, + s3Text.placeholderEnterAccessKey, + "aws_access" + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(s3Text.accessKeyError); + + fillDataSourceTextField( + s3Text.accessKey, + s3Text.placeholderEnterAccessKey, + Cypress.env("aws_access") + ); + fillDataSourceTextField( + "Secret key", + s3Text.placeholderEnterSecretKey, + "aws_secret", + "contain" + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + + verifyCouldnotConnectWithAlert(s3Text.sinatureError); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", s3Text.cypressAwsS3) + .find("button") + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js new file mode 100644 index 0000000000..deb9b8ce2c --- /dev/null +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -0,0 +1,14 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; + +export const verifyCouldnotConnectWithAlert = (dangerText) => { + cy.get(postgreSqlSelector.connectionFailedText, { + timeout: 10000, + }).verifyVisibleElement("have.text", postgreSqlText.couldNotConnect, { + timeout: 5000, + }); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "contain.text", + dangerText + ); +}; diff --git a/cypress-tests/cypress/support/utils/postgreSql.js b/cypress-tests/cypress/support/utils/postgreSql.js index a314c2524b..d0bfe852dc 100644 --- a/cypress-tests/cypress/support/utils/postgreSql.js +++ b/cypress-tests/cypress/support/utils/postgreSql.js @@ -60,15 +60,23 @@ export const fillConnectionForm = (data) => { cy.get(postgreSqlSelector.buttonSave).click(); }; -export const fillDataSourceTextField = (fieldName, placeholder, input) => { +export const fillDataSourceTextField = ( + fieldName, + placeholder, + input, + assertionType = "have", + args +) => { cy.get(`[data-cy="label-${cyParamName(fieldName)}"]`).should( - "have.text", + `${assertionType}.text`, fieldName ); cy.get(`[data-cy="${cyParamName(fieldName)}-text-field"]`) .invoke("attr", "placeholder") .should("eq", placeholder.replace(/\u00a0/g, " ")); - cy.clearAndType(`[data-cy="${cyParamName(fieldName)}-text-field"]`, input); + cy.get(`[data-cy="${cyParamName(fieldName)}-text-field"]`) + .clear() + .type(input, args); }; export const openQueryEditor = (dataSourceName) => { From 80e22acb4a96e4097b2fa11120fd4e6697811c77 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 17 Jan 2023 14:37:08 +0530 Subject: [PATCH 13/56] Fix failing components verification specs (#5317) * Add constants for components (labels and selectors) * Modify common methods according to v2 changes * Modify components specs according to v2 changes * Modify cypress config to fix cypress runner crashing. * Remove hardcoded data-cy from specs --- cypress-tests/cypress.config.js | 2 + .../cypress/constants/selectors/common.js | 10 +- .../cypress/constants/texts/common.js | 4 +- .../e2e/editor/widget/buttonHappyPath.cy.js | 92 ++++++++++++------- .../editor/widget/datePickerHappyPath.cy.js | 20 ++-- .../e2e/editor/widget/listViewHappyPath.cy.js | 32 ++++--- .../editor/widget/multiselectHappyPath.cy.js | 35 +++---- .../editor/widget/numberInputHappyPath.cy.js | 22 +++-- .../widget/passwordInputHappyPath.cy.js | 23 +++-- .../editor/widget/textInputHappyPath.cy.js | 8 +- cypress-tests/cypress/support/commands.js | 4 +- .../cypress/support/utils/commonWidget.js | 67 ++++++++------ 12 files changed, 185 insertions(+), 134 deletions(-) diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index d046d98487..24e625e622 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -61,5 +61,7 @@ module.exports = defineConfig({ experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", specPattern: "cypress/e2e/**/*.cy.js", + numTestsKeptInMemory: 0, + redirectionLimit: 10, }, }); diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index 603289261e..8e7a55cf30 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -162,8 +162,8 @@ export const commonWidgetSelector = { return `[data-cy="${widgetName.toLowerCase()}-invalid-feedback"]`; }, - buttonCloseEditorSideBar: "[data-rb-event-key='close-inpector-light']", - buttonStylesEditorSideBar: "[data-rb-event-key='styles']", + buttonCloseEditorSideBar: "[data-cy='inspector-close-icon']", + buttonStylesEditorSideBar: "[data-cy='sidebar-option-styles']", WidgetNameInputField: "[data-cy=edit-widget-name]", tooltipInputField: "[data-cy='tooltip-input-field']", @@ -182,8 +182,10 @@ export const commonWidgetSelector = { '[data-cy="action-options-action-selection-field"]', componentTextInput: '[data-cy="action-options-text-input-field"]', changeLayoutButton: "[data-cy= 'change-layout-button']", + changeLayoutToMobileButton: '[data-cy="button-change-layout-to-desktop"]', + changeLayoutToDesktopButton: '[data-cy="button-change-layout-to-mobile"]', - sidebarinspector: "[data-cy='left-sidebar-inspector-button']", + sidebarinspector: "[data-cy='left-sidebar-inspect-button']", inspectorNodeComponents: "[data-cy='inspector-node-components']> .node-key", nodeComponentValue: "[data-cy='inspector-node-value']> .mx-2", nodeComponentValues: "[data-cy='inspector-node-values']> .node-key", @@ -196,4 +198,4 @@ export const commonWidgetSelector = { boxShadowColorPicker: "[data-cy='box-shadow-picker']", textInputWidget: '[data-cy="draggable-widget-textinput1"]', previewButton: `[data-cy="preview-link-button"]`, -}; \ No newline at end of file +}; diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index 35a84915c9..d52099ccf4 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -12,7 +12,7 @@ export const path = { }; export const commonText = { - autoSave: "All changes are saved", + autoSave: "Saved changes", email: "dev@tooljet.io", password: "password", loginErrorToast: "Invalid email or password", @@ -139,4 +139,4 @@ export const widgetValue = (widgetName) => { export const customValidation = (name, message) => { return ["{{", `components.${name}.value ? true : '${message}'}}`]; -}; \ No newline at end of file +}; diff --git a/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js index 6d0dbc348c..75362db4f0 100644 --- a/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js @@ -3,9 +3,7 @@ import { buttonText } from "Texts/button"; import { fake } from "Fixtures/fake"; import { commonWidgetText } from "Texts/common"; -import { - verifyControlComponentAction, -} from "Support/utils/button"; +import { verifyControlComponentAction } from "Support/utils/button"; import { openAccordion, @@ -25,12 +23,10 @@ import { verifyTooltip, editAndVerifyWidgetName, verifyPropertiesGeneralAccordion, - verifyStylesGeneralAccordion - + verifyStylesGeneralAccordion, } from "Support/utils/commonWidget"; describe("Editor- Test Button widget", () => { - beforeEach(() => { cy.appUILogin(); cy.createApp(); @@ -48,7 +44,7 @@ describe("Editor- Test Button widget", () => { cy.renameApp(data.appName); openEditorSidebar(buttonText.defaultWidgetName); - editAndVerifyWidgetName(data.widgetName) + editAndVerifyWidgetName(data.widgetName); openAccordion(commonWidgetText.accordionProperties); verifyAndModifyParameter(buttonText.buttonTextLabel, data.widgetName); @@ -81,7 +77,7 @@ describe("Editor- Test Button widget", () => { verifyLayout(data.widgetName); - cy.get(commonWidgetSelector.changeLayoutButton).click(); + cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click(); cy.get( commonWidgetSelector.parameterTogglebutton( commonWidgetText.parameterShowOnDesktop @@ -124,7 +120,10 @@ describe("Editor- Test Button widget", () => { commonWidgetSelector.parameterFxButton(buttonText.backgroundColor) ).click(); - selectColourFromColourPicker(buttonText.backgroundColor, data.backgroundColor); + selectColourFromColourPicker( + buttonText.backgroundColor, + data.backgroundColor + ); verifyWidgetColorCss( buttonText.defaultWidgetName, @@ -146,7 +145,7 @@ describe("Editor- Test Button widget", () => { commonWidgetSelector.parameterFxButton(buttonText.textColor) ).click(); - selectColourFromColourPicker(buttonText.textColor, data.textColor); + selectColourFromColourPicker(buttonText.textColor, data.textColor, 1); verifyWidgetColorCss(buttonText.defaultWidgetName, "color", data.textColor); @@ -164,7 +163,7 @@ describe("Editor- Test Button widget", () => { commonWidgetSelector.parameterFxButton(buttonText.loaderColor) ).click(); - selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor); + selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor, 2); verifyLoaderColor(buttonText.defaultWidgetName, data.loaderColor); @@ -213,7 +212,13 @@ describe("Editor- Test Button widget", () => { cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); data.colourHex = fake.randomRgbaHex; - verifyStylesGeneralAccordion(buttonText.defaultWidgetName, data.boxShadowParam, data.colourHex, data.boxShadowColor); + verifyStylesGeneralAccordion( + buttonText.defaultWidgetName, + data.boxShadowParam, + data.colourHex, + data.boxShadowColor, + 4 + ); cy.get(commonSelectors.editorPageLogo).click(); cy.deleteApp(data.appName); @@ -237,26 +242,32 @@ describe("Editor- Test Button widget", () => { openEditorSidebar(buttonText.defaultWidgetName); verifyAndModifyParameter(buttonText.buttonTextLabel, data.widgetName); - openAccordion(commonWidgetText.accordionEvents); + openAccordion(commonWidgetText.accordionEvents); addDefaultEventHandler(data.alertMessage); - verifyPropertiesGeneralAccordion(buttonText.defaultWidgetName, data.tooltipText); + verifyPropertiesGeneralAccordion( + buttonText.defaultWidgetName, + data.tooltipText + ); openEditorSidebar(buttonText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - selectColourFromColourPicker(buttonText.backgroundColor, data.backgroundColor); - - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); + selectColourFromColourPicker( + buttonText.backgroundColor, + data.backgroundColor + ); + + cy.forceClickOnCanvas(); openEditorSidebar(buttonText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - selectColourFromColourPicker(buttonText.textColor, data.textColor); + selectColourFromColourPicker(buttonText.textColor, data.textColor, 1); - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); + cy.forceClickOnCanvas(); openEditorSidebar(buttonText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor); + selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor, 2); - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); + cy.forceClickOnCanvas(); openEditorSidebar(buttonText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); @@ -269,9 +280,18 @@ describe("Editor- Test Button widget", () => { .clear() .type(buttonText.borderRadiusInput); - verifyStylesGeneralAccordion(buttonText.defaultWidgetName, data.boxShadowParam, data.colourHex, data.boxShadowColor); + verifyStylesGeneralAccordion( + buttonText.defaultWidgetName, + data.boxShadowParam, + data.colourHex, + data.boxShadowColor, + 4 + ); - verifyControlComponentAction(buttonText.defaultWidgetName, data.customMessage); + verifyControlComponentAction( + buttonText.defaultWidgetName, + data.customMessage + ); cy.waitForAutoSave(); cy.openInCurrentTab(commonWidgetSelector.previewButton); @@ -280,9 +300,11 @@ describe("Editor- Test Button widget", () => { commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName) ).verifyVisibleElement("have.text", data.widgetName); - cy.get(commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)).click(); + cy.get( + commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName) + ).click(); cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage); - cy.get(commonWidgetSelector.draggableWidget('textinput1')).should( + cy.get(commonWidgetSelector.draggableWidget("textinput1")).should( "have.value", data.customMessage ); @@ -292,17 +314,23 @@ describe("Editor- Test Button widget", () => { data.tooltipText ); - verifyWidgetColorCss(buttonText.defaultWidgetName, "background-color", data.backgroundColor); + verifyWidgetColorCss( + buttonText.defaultWidgetName, + "background-color", + data.backgroundColor + ); verifyWidgetColorCss(buttonText.defaultWidgetName, "color", data.textColor); verifyLoaderColor(buttonText.defaultWidgetName, data.loaderColor); - cy.get(commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)).should( - "have.css", - "border-radius", - "20px" - ); + cy.get( + commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName) + ).should("have.css", "border-radius", "20px"); - verifyBoxShadowCss(buttonText.defaultWidgetName, data.boxShadowColor, data.boxShadowParam); + verifyBoxShadowCss( + buttonText.defaultWidgetName, + data.boxShadowColor, + data.boxShadowParam + ); cy.get(commonSelectors.viewerPageLogo).click(); cy.deleteApp(data.appName); diff --git a/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js index 13baca6185..9a144180d2 100644 --- a/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js @@ -66,7 +66,7 @@ describe("Date Picker widget", () => { verifyAndModifyParameter(datePickerText.labelformat, "DD/MM/YY"); cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); verifyDate(data.widgetName, data.date, "DD/MM/YY"); - verifyComponentValueFromInspector(data.widgetName, data.date, "opened"); + verifyComponentValueFromInspector(data.widgetName, data.date); cy.get(commonSelectors.canvas).click({ force: true }); openEditorSidebar(data.widgetName); @@ -156,14 +156,14 @@ describe("Date Picker widget", () => { "not.exist" ); - verifyAndModifyToggleFx( - commonWidgetText.parameterShowOnMobile, - commonWidgetText.codeMirrorLabelFalse - ); - cy.get(commonWidgetSelector.changeLayoutButton).click(); - cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( - "exist" - ); + // verifyAndModifyToggleFx( + // commonWidgetText.parameterShowOnMobile, + // commonWidgetText.codeMirrorLabelFalse + // ); + // cy.get(commonWidgetSelector.changeLayoutButton).click(); + // cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( + // "exist" + // ); }); it("should verify the styles of the date picker widget", () => { @@ -278,7 +278,7 @@ describe("Date Picker widget", () => { commonWidgetText.borderRadiusInput ); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); cy.get( commonWidgetSelector.stylePicker(commonWidgetText.parameterBoxShadow) diff --git a/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js index 635f3e72e4..fb99e3a586 100644 --- a/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js @@ -175,14 +175,14 @@ describe("List view widget", () => { "not.exist" ); - verifyAndModifyToggleFx( - commonWidgetText.parameterShowOnMobile, - commonWidgetText.codeMirrorLabelFalse - ); - cy.get(commonWidgetSelector.changeLayoutButton).click(); - cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( - "exist" - ); + // verifyAndModifyToggleFx( + // commonWidgetText.parameterShowOnMobile, + // commonWidgetText.codeMirrorLabelFalse + // ); + // cy.get(commonWidgetSelector.changeLayoutButton).click(); + // cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( + // "exist" + // ); }); it("should verify the styles of the list view widget", () => { @@ -230,7 +230,7 @@ describe("List view widget", () => { openEditorSidebar(listviewText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); verifyAndModifyToggleFx( commonWidgetText.parameterBoxShadow, @@ -246,7 +246,11 @@ describe("List view widget", () => { data.boxShadowParam ); - selectColourFromColourPicker(commonWidgetText.boxShadowColor, data.colour); + selectColourFromColourPicker( + commonWidgetText.boxShadowColor, + data.colour, + 2 + ); verifyBoxShadowCss( listviewText.defaultWidgetName, data.colour, @@ -368,7 +372,7 @@ describe("List view widget", () => { openEditorSidebar(listviewText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); verifyAndModifyToggleFx( commonWidgetText.parameterBoxShadow, @@ -381,7 +385,11 @@ describe("List view widget", () => { commonWidgetSelector.boxShadowDefaultParam, data.boxShadowParam ); - selectColourFromColourPicker(commonWidgetText.boxShadowColor, data.colour); + selectColourFromColourPicker( + commonWidgetText.boxShadowColor, + data.colour, + 2 + ); cy.openInCurrentTab(commonWidgetSelector.previewButton); diff --git a/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js index ac4d0f33f8..a718bfa249 100644 --- a/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js @@ -79,11 +79,7 @@ describe("Multiselect widget", () => { commonWidgetText.labelDefaultValue, codeMirrorInputLabel("[1,2]") ); - verifyMultipleComponentValuesFromInspector( - data.widgetName, - [1, 2], - "opened" - ); + verifyMultipleComponentValuesFromInspector(data.widgetName, [1, 2]); verifyMultiselectHeader(data.widgetName, "one, two"); verifyMultiselectStatus(data.widgetName); @@ -96,11 +92,7 @@ describe("Multiselect widget", () => { multiselectText.labelAllItemsSelected ); - verifyMultipleComponentValuesFromInspector( - data.widgetName, - [1, 2, 3], - "opened" - ); + verifyMultipleComponentValuesFromInspector(data.widgetName, [1, 2, 3]); openEditorSidebar(data.widgetName); verifyAndModifyParameter( @@ -111,8 +103,7 @@ describe("Multiselect widget", () => { verifyMultipleComponentValuesFromInspector( data.widgetName, - data.randomLabels, - "opened" + data.randomLabels ); openEditorSidebar(data.widgetName); @@ -169,14 +160,14 @@ describe("Multiselect widget", () => { "not.exist" ); - verifyAndModifyToggleFx( - commonWidgetText.parameterShowOnMobile, - commonWidgetText.codeMirrorLabelFalse - ); - cy.get(commonWidgetSelector.changeLayoutButton).click(); - cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( - "exist" - ); + // verifyAndModifyToggleFx( + // commonWidgetText.parameterShowOnMobile, + // commonWidgetText.codeMirrorLabelFalse + // ); + // cy.get(commonWidgetSelector.changeLayoutButton).click(); + // cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should( + // "exist" + // ); }); it("should verify the styles of the widget", () => { @@ -223,7 +214,7 @@ describe("Multiselect widget", () => { openEditorSidebar(multiselectText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); verifyAndModifyStylePickerFx( commonWidgetText.parameterBoxShadow, @@ -288,7 +279,7 @@ describe("Multiselect widget", () => { openEditorSidebar(data.widgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); cy.get( commonWidgetSelector.stylePicker(commonWidgetText.parameterBoxShadow) diff --git a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js index 7f5f066a5b..1d7867394b 100644 --- a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js @@ -117,14 +117,14 @@ describe("Number Input", () => { verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText); - verifyLayout(data.widgetName); + // verifyLayout(data.widgetName); - cy.get(commonWidgetSelector.changeLayoutButton).click(); - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterShowOnDesktop - ) - ).click(); + // cy.get(commonWidgetSelector.changeLayoutButton).click(); + // cy.get( + // commonWidgetSelector.parameterTogglebutton( + // commonWidgetText.parameterShowOnDesktop + // ) + // ).click(); cy.get(commonWidgetSelector.widgetDocumentationLink).should( "have.text", @@ -189,7 +189,8 @@ describe("Number Input", () => { numberInputText.defaultWidgetName, data.boxShadowParam, data.colourHex, - data.boxShadowColor + data.boxShadowColor, + 3 ); cy.get(commonSelectors.editorPageLogo).click(); @@ -245,7 +246,7 @@ describe("Number Input", () => { openEditorSidebar(numberInputText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); cy.get(commonWidgetSelector.boxShadowColorPicker).click(); fillBoxShadowParams( @@ -254,7 +255,8 @@ describe("Number Input", () => { ); selectColourFromColourPicker( commonWidgetText.boxShadowColor, - data.boxShadowColor + data.boxShadowColor, + 3 ); addTextWidgetToVerifyValue("components.numberinput1.value"); diff --git a/cypress-tests/cypress/e2e/editor/widget/passwordInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/passwordInputHappyPath.cy.js index ebc47277c3..8d56667053 100644 --- a/cypress-tests/cypress/e2e/editor/widget/passwordInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/passwordInputHappyPath.cy.js @@ -154,14 +154,14 @@ describe("Password Input", () => { cy.get( commonWidgetSelector.accordion(commonWidgetText.accordionValidation) ).click(); - verifyLayout(data.widgetName); + // verifyLayout(data.widgetName); - cy.get(commonWidgetSelector.changeLayoutButton).click(); - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterShowOnDesktop - ) - ).click(); + // cy.get(commonWidgetSelector.changeLayoutButton).click(); + // cy.get( + // commonWidgetSelector.parameterTogglebutton( + // commonWidgetText.parameterShowOnDesktop + // ) + // ).click(); cy.get(commonWidgetSelector.widgetDocumentationLink).should( "have.text", @@ -226,7 +226,8 @@ describe("Password Input", () => { passwordInputText.defaultWidgetName, data.boxShadowParam, data.colourHex, - data.boxShadowColor + data.boxShadowColor, + 1 ); cy.get(commonSelectors.editorPageLogo).click(); @@ -299,7 +300,7 @@ describe("Password Input", () => { openEditorSidebar(passwordInputText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); cy.get(commonWidgetSelector.boxShadowColorPicker).click(); fillBoxShadowParams( @@ -308,7 +309,8 @@ describe("Password Input", () => { ); selectColourFromColourPicker( commonWidgetText.boxShadowColor, - data.boxShadowColor + data.boxShadowColor, + 1 ); addTextWidgetToVerifyValue("components.passwordinput1.value"); cy.waitForAutoSave(); @@ -335,6 +337,7 @@ describe("Password Input", () => { cy.get( commonWidgetSelector.draggableWidget(commonWidgetText.text1) ).verifyVisibleElement("have.text", "t"); + cy.forceClickOnCanvas(); cy.get( commonWidgetSelector.validationFeedbackMessage( passwordInputText.defaultWidgetName diff --git a/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js index d41671fcb3..5116e1056d 100644 --- a/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js @@ -173,7 +173,7 @@ describe("Text Input", () => { ).click(); verifyLayout(data.widgetName); - cy.get(commonWidgetSelector.changeLayoutButton).click(); + cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click(); cy.get( commonWidgetSelector.parameterTogglebutton( commonWidgetText.parameterShowOnDesktop @@ -245,7 +245,8 @@ describe("Text Input", () => { textInputText.defaultWidgetName, data.boxShadowParam, data.colourHex, - data.boxShadowColor + data.boxShadowColor, + 4 ); cy.get(commonSelectors.editorPageLogo).click(); @@ -321,7 +322,8 @@ describe("Text Input", () => { textInputText.defaultWidgetName, data.boxShadowParam, data.colourHex, - data.boxShadowColor + data.boxShadowColor, + 4 ); cy.waitForAutoSave(); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 03f2729eaa..134fa0c58b 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -24,7 +24,7 @@ Cypress.Commands.add("forceClickOnCanvas", () => { }); Cypress.Commands.add("verifyToastMessage", (selector, message) => { - cy.get(selector).should("be.visible").and("have.text", message); + cy.get(selector).eq(0).should("be.visible").and("have.text", message); cy.get("body").then(($body) => { if ($body.find(commonSelectors.toastCloseButton).length > 0) { cy.closeToastMessage(); @@ -131,7 +131,7 @@ Cypress.Commands.add("appUILogin", () => { cy.clearAndType(commonSelectors.passwordInputField, "password"); cy.get(loginSelectors.signInButton).click(); cy.get(commonSelectors.homePageLogo).should("be.visible"); - cy.wait(2000) + cy.wait(2000); cy.get("body").then(($el) => { if ($el.text().includes("Skip")) { cy.get(commonSelectors.skipInstallationModal).click(); diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index 32d0c88dfe..1814bc88f2 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -125,25 +125,27 @@ export const verifyMultipleComponentValuesFromInspector = ( cy.forceClickOnCanvas(); }; -export const selectColourFromColourPicker = (paramName, colour) => { +export const selectColourFromColourPicker = (paramName, colour, index = 0) => { cy.get(commonWidgetSelector.stylePicker(paramName)).click(); - cy.get(commonWidgetSelector.colourPickerParent).within(() => { - colour.forEach((value, i) => - cy - .get(commonWidgetSelector.colourPickerInput(i + 1)) - .click() - .clear() - .type(value) - .then(($input) => { - if (!$input.val(value)) { - cy.get(commonWidgetSelector.colourPickerInput(i + 1)) - .click() - .clear() - .type(value); - } - }) - ); - }); + cy.get(commonWidgetSelector.colourPickerParent) + .eq(index) + .within(() => { + colour.forEach((value, i) => + cy + .get(commonWidgetSelector.colourPickerInput(i + 1)) + .click() + .clear() + .type(value) + .then(($input) => { + if (!$input.val(value)) { + cy.get(commonWidgetSelector.colourPickerInput(i + 1)) + .click() + .clear() + .type(value); + } + }) + ); + }); cy.waitForAutoSave(); }; @@ -194,7 +196,8 @@ export const verifyComponentFromInspector = ( export const verifyAndModifyStylePickerFx = ( paramName, defaultValue, - value + value, + index = 0 ) => { cy.get(commonWidgetSelector.parameterLabel(paramName)).should( "have.text", @@ -227,9 +230,11 @@ export const verifyAndModifyStylePickerFx = ( commonWidgetSelector.stylePickerFxInput(paramName) ).clearAndTypeOnCodeMirror(value); - cy.get(commonWidgetSelector.stylePickerFxInput(paramName)).within(() => { - cy.get(".CodeMirror-line").should("be.visible").and("have.text", value); - }); + cy.get(commonWidgetSelector.stylePickerFxInput(paramName)) + .eq(index) + .within(() => { + cy.get(".CodeMirror-line").should("be.visible").and("have.text", value); + }); }; export const verifyWidgetColorCss = (widgetName, cssProperty, color) => { @@ -266,7 +271,7 @@ export const verifyLayout = (widgetName) => { commonWidgetText.parameterShowOnMobile, commonWidgetText.codeMirrorLabelFalse ); - cy.get(commonWidgetSelector.changeLayoutButton).click(); + cy.get(commonWidgetSelector.changeLayoutToMobileButton).click(); cy.get(commonWidgetSelector.draggableWidget(widgetName)).should("exist"); }; @@ -285,11 +290,12 @@ export const verifyStylesGeneralAccordion = ( widgetName, boxShadowParameter, hexColor, - boxShadowColor + boxShadowColor, + index = 0 ) => { openEditorSidebar(widgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, [], "1"); + openAccordion(commonWidgetText.accordionGenaral, []); verifyAndModifyStylePickerFx( commonWidgetText.parameterBoxShadow, commonWidgetText.boxShadowDefaultValue, @@ -307,7 +313,11 @@ export const verifyStylesGeneralAccordion = ( commonWidgetSelector.boxShadowDefaultParam, boxShadowParameter ); - selectColourFromColourPicker(commonWidgetText.boxShadowColor, boxShadowColor); + selectColourFromColourPicker( + commonWidgetText.boxShadowColor, + boxShadowColor, + index + ); verifyBoxShadowCss(widgetName, boxShadowColor, boxShadowParameter); }; @@ -322,11 +332,14 @@ export const addTextWidgetToVerifyValue = (customfunction) => { export const verifyTooltip = (widgetSelector, message) => { cy.forceClickOnCanvas(); + cy.get(widgetSelector).click(); cy.get(widgetSelector) .trigger("mouseover", { timeout: 2000 }) .trigger("mouseover") .then(() => { - cy.get(commonWidgetSelector.tooltipLabel).should("have.text", message); + cy.get(commonWidgetSelector.tooltipLabel) + .last() + .should("have.text", message); }); }; From d9e9637b7738cb92ae36e438fefaa5abead7dccc Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 17 Jan 2023 14:39:38 +0530 Subject: [PATCH 14/56] Added automation for datasource connection. (#5091) * Add automation for BigQuery * Add automation for Firestore * Add automation for MongoDB * Add automation for Redis * Add fields on env --- cypress-tests/cypress.config.js | 10 + .../cypress/constants/texts/bigquery.js | 7 + .../cypress/constants/texts/firestore.js | 10 + .../cypress/constants/texts/mongoDb.js | 13 ++ .../cypress/constants/texts/redis.js | 9 + .../data-source/bigqueryHappyPath.cy.js | 128 ++++++++++++ .../data-source/fireStoreHappyPath.cy.js | 127 ++++++++++++ .../editor/data-source/mongoDbHappyPath.cy.js | 183 +++++++++++++++++ .../editor/data-source/redisHappyPath.cy.js | 188 ++++++++++++++++++ 9 files changed, 675 insertions(+) create mode 100644 cypress-tests/cypress/constants/texts/bigquery.js create mode 100644 cypress-tests/cypress/constants/texts/firestore.js create mode 100644 cypress-tests/cypress/constants/texts/mongoDb.js create mode 100644 cypress-tests/cypress/constants/texts/redis.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/bigqueryHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/fireStoreHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/mongoDbHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/redisHappyPath.cy.js diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 24e625e622..c2d24d9f1a 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -19,6 +19,15 @@ module.exports = defineConfig({ sso_password: "", git_user: "", google_user: "", + redis_host: "", + redis_port: "", + redis_password: "", + mongodb_connString: "", + mongodb_host: "", + mongodb_user: "", + mongo_password: "", + bigquery_pvt_key: {}, + firestore_pvt_key: {}, mysql_host: "", mysql_user: "", mysql_password: "", @@ -57,6 +66,7 @@ module.exports = defineConfig({ return require("./cypress/plugins/index.js")(on, config); }, + experimentalRunAllSpecs: true, experimentalModfyObstructiveThirdPartyCode: true, experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", diff --git a/cypress-tests/cypress/constants/texts/bigquery.js b/cypress-tests/cypress/constants/texts/bigquery.js new file mode 100644 index 0000000000..d01f2c7695 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/bigquery.js @@ -0,0 +1,7 @@ +export const bigqueryText = { + bigQuery: "BigQuery", + cypressBigQuery: "cypress-bigquery", + errorInvalidEmailId: + "The incoming JSON object does not contain a client_email field", + placehlderPrivateKey: "Enter JSON private key for service account", +}; diff --git a/cypress-tests/cypress/constants/texts/firestore.js b/cypress-tests/cypress/constants/texts/firestore.js new file mode 100644 index 0000000000..1fc4ae530c --- /dev/null +++ b/cypress-tests/cypress/constants/texts/firestore.js @@ -0,0 +1,10 @@ +export const firestoreText = { + firestore: "Firestore", + cypressFirestore: "cypress-firestore", + labelPrivateKey: "Private keyEncrypted", + privateKey: "Private key", + placeholderPrivateKey: "Enter private key", + + errorGcpKeyCouldNotBeParsed: + "GCP key could not be parsed as a valid JSON object", +}; diff --git a/cypress-tests/cypress/constants/texts/mongoDb.js b/cypress-tests/cypress/constants/texts/mongoDb.js new file mode 100644 index 0000000000..3b06dc0735 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/mongoDb.js @@ -0,0 +1,13 @@ +export const mongoDbText = { + mongoDb: "MongoDB", + cypressMongoDb: "cypress-mongodb", + errorConnectionRefused: "connect ECONNREFUSED 127.0.0.1:27017", + + optionConnectUsingConnectionString: "Connect using connection string{enter}", + labelConnectionString: "Connection string", + + errorInvalisScheme: + 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"', + connectionStringPlaceholder: + "mongodb+srv://tooljet:@cluster0.i1vq4.mongodb.net/mydb?retryWrites=true&w=majority", +}; diff --git a/cypress-tests/cypress/constants/texts/redis.js b/cypress-tests/cypress/constants/texts/redis.js new file mode 100644 index 0000000000..89489b7d62 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/redis.js @@ -0,0 +1,9 @@ +export const redisText = { + redis: "Redis", + cypressRedis: "cypress-redis", + + errorMaxRetries: + 'Reached the max retries per request limit (which is 1). Refer to "maxRetriesPerRequest" option for details.', + errorPort: "Port should be >= 0 and < 65536. Received 108299.", + errorInvalidUserOrPassword: "WRONGPASS invalid username-password pair", +}; diff --git a/cypress-tests/cypress/e2e/editor/data-source/bigqueryHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/bigqueryHappyPath.cy.js new file mode 100644 index 0000000000..00cb8e79ee --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/bigqueryHappyPath.cy.js @@ -0,0 +1,128 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { bigqueryText } from "Texts/bigquery"; +import { firestoreText } from "Texts/firestore"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; + +describe("Data source BigQuery", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on BigQuery connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type( + bigqueryText.bigQuery + ); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", bigqueryText.bigQuery); + cy.get('[data-cy="data-source-bigquery"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + bigqueryText.bigQuery + ); + + cy.get('[data-cy="label-private-key"]').verifyVisibleElement( + "have.text", + firestoreText.labelPrivateKey + ); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + bigqueryText.errorInvalidEmailId + ); + }); + + it("Should verify the functionality of BigQuery connection form.", () => { + selectDataSource(bigqueryText.bigQuery); + + cy.clearAndType( + '[data-cy="data-source-name-input-filed"]', + bigqueryText.cypressBigQuery + ); + + fillDataSourceTextField( + firestoreText.privateKey, + bigqueryText.placehlderPrivateKey, + JSON.stringify(Cypress.env("bigquery_pvt_key")), + "contain", + { parseSpecialCharSequences: false, delay: 0 } + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", bigqueryText.cypressBigQuery) + .find("button") + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/fireStoreHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/fireStoreHappyPath.cy.js new file mode 100644 index 0000000000..a109847010 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/fireStoreHappyPath.cy.js @@ -0,0 +1,127 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { firestoreText } from "Texts/firestore"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; + +describe("Data source Firestore", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on Firestore connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type( + firestoreText.firestore + ); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", firestoreText.firestore); + cy.get('[data-cy="data-source-firestore"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + firestoreText.firestore + ); + + cy.get('[data-cy="label-private-key"]').verifyVisibleElement( + "have.text", + firestoreText.labelPrivateKey + ); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + firestoreText.errorGcpKeyCouldNotBeParsed + ); + }); + + it("Should verify the functionality of Firestore connection form.", () => { + selectDataSource(firestoreText.firestore); + + cy.clearAndType( + '[data-cy="data-source-name-input-filed"]', + firestoreText.cypressFirestore + ); + + fillDataSourceTextField( + firestoreText.privateKey, + firestoreText.placeholderPrivateKey, + JSON.stringify(Cypress.env("firestore_pvt_key")), + "contain", + { parseSpecialCharSequences: false, delay: 0 } + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("contain.text", firestoreText.cypressFirestore) + .find("button") + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/mongoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/mongoDbHappyPath.cy.js new file mode 100644 index 0000000000..df6c7de1a3 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/mongoDbHappyPath.cy.js @@ -0,0 +1,183 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { mongoDbText } from "Texts/mongoDb"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; + +describe("Data source MongoDB", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on MongoDB connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type( + mongoDbText.mongoDb + ); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", mongoDbText.mongoDb); + cy.get('[data-cy="data-source-mongodb"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + mongoDbText.mongoDb + ); + cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + "have.text", + postgreSqlText.labelHost + ); + cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + "have.text", + postgreSqlText.labelPort + ); + cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement( + "have.text", + postgreSqlText.labelDbName + ); + cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( + "have.text", + postgreSqlText.labelUserName + ); + cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( + "have.text", + postgreSqlText.labelPassword + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect, + { timeout: 65000 } + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + mongoDbText.errorConnectionRefused + ); + cy.get('[data-cy="query-select-dropdown"]').type( + mongoDbText.optionConnectUsingConnectionString + ); + cy.get('[data-cy="label-connection-string"]').verifyVisibleElement( + "have.text", + mongoDbText.labelConnectionString + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect, + { timeout: 60000 } + ); + verifyCouldnotConnectWithAlert(mongoDbText.errorInvalisScheme); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + }); + + it("Should verify the functionality of MongoDB connection form.", () => { + selectDataSource(mongoDbText.mongoDb); + + cy.clearAndType( + '[data-cy="data-source-name-input-filed"]', + mongoDbText.cypressMongoDb + ); + + cy.get('[data-cy="query-select-dropdown"]').type( + mongoDbText.optionConnectUsingConnectionString + ); + + fillDataSourceTextField( + mongoDbText.labelConnectionString, + mongoDbText.connectionStringPlaceholder, + Cypress.env("mongodb_connString"), + "contain", + { parseSpecialCharSequences: false, delay: 0 } + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", mongoDbText.cypressMongoDb) + .find("button") + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/redisHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/redisHappyPath.cy.js new file mode 100644 index 0000000000..bd7de11bcc --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/redisHappyPath.cy.js @@ -0,0 +1,188 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { redisText } from "Texts/redis"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; + +describe("Data source Redis", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on connecti Redison form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type(redisText.redis); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", redisText.redis); + cy.get('[data-cy="data-source-redis"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + redisText.redis + ); + cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + "have.text", + postgreSqlText.labelHost + ); + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + "redis_host" + ); + cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + "have.text", + postgreSqlText.labelPort + ); + cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( + "have.text", + postgreSqlText.labelUserName + ); + cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( + "have.text", + postgreSqlText.labelPassword + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + verifyCouldnotConnectWithAlert(redisText.errorMaxRetries); + }); + it("Should verify the functionality of Redis connection form.", () => { + selectDataSource(redisText.redis); + + cy.clearAndType( + '[data-cy="data-source-name-input-filed"]', + redisText.cypressRedis + ); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + "redis_host" + ); + fillDataSourceTextField( + postgreSqlText.labelPort, + postgreSqlText.placeholderEnterPort, + Cypress.env("redis_port") + ); + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "dev@tooljet.io" + ); + cy.get(postgreSqlSelector.passwordTextField).type( + Cypress.env("redis_password") + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(redisText.errorMaxRetries); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + Cypress.env("redis_host") + ); + fillDataSourceTextField( + postgreSqlText.labelPort, + postgreSqlText.placeholderEnterPort, + "108299" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(redisText.errorPort); + + fillDataSourceTextField( + postgreSqlText.labelPort, + postgreSqlText.placeholderEnterPort, + Cypress.env("redis_port") + ); + cy.get(postgreSqlSelector.passwordTextField).type( + `{selectAll}{backspace}"redis_password"` + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(redisText.errorInvalidUserOrPassword); + + cy.get(postgreSqlSelector.passwordTextField).type( + `{selectAll}{backspace}${Cypress.env("redis_password")}` + ); + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "dev@tooljet.com" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(redisText.errorInvalidUserOrPassword); + + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "dev@tooljet.io" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", redisText.cypressRedis) + .find("button") + .should("be.visible"); + }); +}); From 05f499c9daacca5325e7616b26bc40eff6fefd97 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 17 Jan 2023 15:52:31 +0530 Subject: [PATCH 15/56] Add automation for smtp, dynamoDB, Elastic-search connections. (#5349) * cypress-test * Add and modify selectors and texts * Add spec for dynamoDB * Add spec for Elasticsearch * Add spec for SMTP * Add creds on env --- cypress-tests/cypress.config.js | 23 ++- .../cypress/constants/selectors/postgreSql.js | 2 +- .../cypress/constants/texts/dynamodb.js | 14 ++ .../cypress/constants/texts/elasticsearch.js | 7 + .../cypress/constants/texts/postgreSql.js | 2 +- .../data-source/dynamoDbHappyPath.cy.js | 166 ++++++++++++++++ .../data-source/elasticsearchHappyPath.cy.js | 188 ++++++++++++++++++ .../editor/data-source/smtpHappyPath.cy.js | 145 ++++++++++++++ 8 files changed, 544 insertions(+), 3 deletions(-) create mode 100644 cypress-tests/cypress/constants/texts/dynamodb.js create mode 100644 cypress-tests/cypress/constants/texts/elasticsearch.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/dynamoDbHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/elasticsearchHappyPath.cy.js create mode 100644 cypress-tests/cypress/e2e/editor/data-source/smtpHappyPath.cy.js diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index c2d24d9f1a..d958bc3db7 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -16,23 +16,43 @@ module.exports = defineConfig({ pg_host: "", pg_user: "", pg_password: "", + + elasticsearch_host: "", + elasticsearch_user: "", + elasticsearch_password: "", + sso_password: "", git_user: "", google_user: "", + + dynamodb_access_key: "", + dynamodb_secret_key: "", + + smtp_host: "", + smtp_port: "587", + smtp_user: "", + smtp_password: "", + redis_host: "", redis_port: "", redis_password: "", + mongodb_connString: "", mongodb_host: "", mongodb_user: "", mongo_password: "", + bigquery_pvt_key: {}, + firestore_pvt_key: {}, + mysql_host: "", mysql_user: "", mysql_password: "", + aws_access: "", aws_secret: "", + }, db: { user: "postgres", @@ -71,7 +91,8 @@ module.exports = defineConfig({ experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", specPattern: "cypress/e2e/**/*.cy.js", - numTestsKeptInMemory: 0, + numTestsKeptInMemory: 25, redirectionLimit: 10, + experimentalRunAllSpecs: true, }, }); diff --git a/cypress-tests/cypress/constants/selectors/postgreSql.js b/cypress-tests/cypress/constants/selectors/postgreSql.js index 3d00622b7c..216cb4accd 100644 --- a/cypress-tests/cypress/constants/selectors/postgreSql.js +++ b/cypress-tests/cypress/constants/selectors/postgreSql.js @@ -1,5 +1,5 @@ export const postgreSqlSelector = { - leftSidebarDatasourceButton: "[data-cy='left-sidebar-sources-button']", + leftSidebarDatasourceButton: "[data-cy='left-sidebar-database-button']", labelDataSources: "[data-cy='label-datasources']", addDatasourceLink: "[data-cy='add-datasource-link']", diff --git a/cypress-tests/cypress/constants/texts/dynamodb.js b/cypress-tests/cypress/constants/texts/dynamodb.js new file mode 100644 index 0000000000..5b1b520431 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/dynamodb.js @@ -0,0 +1,14 @@ +export const dynamoDbText = { + dynamoDb: "DynamoDB", + cypressDynamoDb: "cypress-dynamodb", + region: "Region", + accessKey: "Access key", + placeHolderAccessKey: "Enter access key", + secretKey: "Secret key", + placeholderSecretKey: "Enter secret key", + + errorMissingRegion: "Missing region in config", + errorInvalidToken: "The security token included in the request is invalid.", + errorSignatureMissmatch: + "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.", +}; diff --git a/cypress-tests/cypress/constants/texts/elasticsearch.js b/cypress-tests/cypress/constants/texts/elasticsearch.js new file mode 100644 index 0000000000..d14a347a14 --- /dev/null +++ b/cypress-tests/cypress/constants/texts/elasticsearch.js @@ -0,0 +1,7 @@ +export const elasticsearchText = { + elasticSearch: "Elasticsearch", + cypressElasticsearch: "cypress-elasticsearch", + + errorConnectionRefused: "connect ECONNREFUSED 127.0.0.1:9200", + errorGetAddrInfoNotFound: "getaddrinfo ENOTFOUND elasticsearch_host", +}; diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js index 2224484f69..eabd1853da 100644 --- a/cypress-tests/cypress/constants/texts/postgreSql.js +++ b/cypress-tests/cypress/constants/texts/postgreSql.js @@ -1,5 +1,5 @@ export const postgreSqlText = { - labelDataSources: "Data sources", + labelDataSources: "Datasources", labelAddDataSource: "+ add data source", allDataSources: "All Datasources (39)", diff --git a/cypress-tests/cypress/e2e/editor/data-source/dynamoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/dynamoDbHappyPath.cy.js new file mode 100644 index 0000000000..b35af014d6 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/dynamoDbHappyPath.cy.js @@ -0,0 +1,166 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { dynamoDbText } from "Texts/dynamodb"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; + +describe("Data source DynamoDB", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on DynamoDB connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type( + dynamoDbText.dynamoDb + ); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", dynamoDbText.dynamoDb); + cy.get('[data-cy="data-source-dynamodb"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + dynamoDbText.dynamoDb + ); + cy.get('[data-cy="label-region"]').verifyVisibleElement( + "have.text", + dynamoDbText.region + ); + cy.get('[data-cy="label-access-key"]').verifyVisibleElement( + "have.text", + dynamoDbText.accessKey + ); + cy.get('[data-cy="label-secret-key"]').verifyVisibleElement( + "have.text", + dynamoDbText.secretKey + ); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + dynamoDbText.errorMissingRegion + ); + }); + + it("Should verify the functionality of DynamoDB connection form.", () => { + selectDataSource(dynamoDbText.dynamoDb); + + cy.clearAndType( + postgreSqlSelector.dataSourceNameInputField, + dynamoDbText.cypressDynamoDb + ); + + cy.get('[data-cy="label-region"]') + .parent() + .next() + .find("input") + .type(`N. california{enter}`); + fillDataSourceTextField( + dynamoDbText.accessKey, + dynamoDbText.placeHolderAccessKey, + "dynamodb_access_key" + ); + fillDataSourceTextField( + dynamoDbText.secretKey, + dynamoDbText.placeholderSecretKey, + Cypress.env("dynamodb_secret_key") + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(dynamoDbText.errorInvalidToken); + + fillDataSourceTextField( + dynamoDbText.accessKey, + dynamoDbText.placeHolderAccessKey, + Cypress.env("dynamodb_access_key") + ); + fillDataSourceTextField( + dynamoDbText.secretKey, + dynamoDbText.placeholderSecretKey, + "dynamodb_secret_key" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(dynamoDbText.errorSignatureMissmatch); + + fillDataSourceTextField( + dynamoDbText.secretKey, + dynamoDbText.placeholderSecretKey, + Cypress.env("dynamodb_secret_key") + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified, { + timeout: 10000, + }); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("contains.text", dynamoDbText.cypressDynamoDb) + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/elasticsearchHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/elasticsearchHappyPath.cy.js new file mode 100644 index 0000000000..f42b3f36f5 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/elasticsearchHappyPath.cy.js @@ -0,0 +1,188 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { elasticsearchText } from "Texts/elasticsearch"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; +import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource"; + +describe("Data source Elasticsearch", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on Elasticsearch connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type( + elasticsearchText.elasticSearch + ); + cy.get("[data-cy*='data-source-']") + .eq(0) + .should("contain", elasticsearchText.elasticSearch); + cy.get('[data-cy="data-source-elasticsearch"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + elasticsearchText.elasticSearch + ); + cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + "have.text", + postgreSqlText.labelHost + ); + cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + "have.text", + postgreSqlText.labelPort + ); + cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( + "have.text", + postgreSqlText.labelUserName + ); + cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( + "have.text", + "Password" + ); + cy.get(postgreSqlSelector.labelSsl).verifyVisibleElement( + "have.text", + postgreSqlText.labelSSL + ); + cy.get(postgreSqlSelector.labelSSLCertificate).verifyVisibleElement( + "have.text", + postgreSqlText.sslCertificate + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + elasticsearchText.errorConnectionRefused + ); + }); + + it("Should verify the functionality of Elasticsearch connection form.", () => { + selectDataSource(elasticsearchText.elasticSearch); + + cy.clearAndType( + postgreSqlSelector.dataSourceNameInputField, + elasticsearchText.cypressElasticsearch + ); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + "elasticsearch_host" + ); + fillDataSourceTextField( + postgreSqlText.labelPort, + postgreSqlText.placeholderEnterPort, + "443" + ); + + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + Cypress.env("elasticsearch_user") + ); + + cy.get(postgreSqlSelector.passwordTextField).type( + Cypress.env("elasticsearch_password") + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert(elasticsearchText.errorGetAddrInfoNotFound); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + Cypress.env("elasticsearch_host") + ); + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + "elasticsearch_user" + ); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert("Response Error"); + + fillDataSourceTextField( + postgreSqlText.labelUserName, + postgreSqlText.placeholderEnterUserName, + Cypress.env("elasticsearch_user") + ); + cy.get(postgreSqlSelector.passwordTextField) + .clear() + .type("elasticsearch_password"); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + verifyCouldnotConnectWithAlert("Response Error"); + cy.get(postgreSqlSelector.passwordTextField) + .clear() + .type(Cypress.env("elasticsearch_password")); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", elasticsearchText.cypressElasticsearch) + .should("be.visible"); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/data-source/smtpHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/data-source/smtpHappyPath.cy.js new file mode 100644 index 0000000000..f944cbed15 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/data-source/smtpHappyPath.cy.js @@ -0,0 +1,145 @@ +import { postgreSqlSelector } from "Selectors/postgreSql"; +import { postgreSqlText } from "Texts/postgreSql"; +import { commonSelectors } from "Selectors/common"; +import { + fillDataSourceTextField, + selectDataSource, +} from "Support/utils/postgreSql"; + +describe("Data source SMTP", () => { + beforeEach(() => { + cy.appUILogin(); + cy.createApp(); + }); + + it("Should verify elements on SMTP connection form", () => { + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.labelDataSources).should( + "have.text", + postgreSqlText.labelDataSources + ); + + cy.get(postgreSqlSelector.addDatasourceLink) + .should("have.text", postgreSqlText.labelAddDataSource) + .click(); + + cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should( + "have.text", + postgreSqlText.allDataSources + ); + cy.get(postgreSqlSelector.databaseLabelAndCount).should( + "have.text", + postgreSqlText.allDatabase + ); + cy.get(postgreSqlSelector.apiLabelAndCount).should( + "have.text", + postgreSqlText.allApis + ); + cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should( + "have.text", + postgreSqlText.allCloudStorage + ); + + cy.get(postgreSqlSelector.dataSourceSearchInputField).type("SMTP"); + cy.get("[data-cy*='data-source-']").eq(0).should("contain", "SMTP"); + cy.get('[data-cy="data-source-smtp"]').click(); + + cy.get(postgreSqlSelector.dataSourceNameInputField).should( + "have.value", + "SMTP" + ); + cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + "have.text", + postgreSqlText.labelHost + ); + cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + "have.text", + postgreSqlText.labelPort + ); + cy.get('[data-cy="label-user"]').verifyVisibleElement("have.text", "User"); + cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( + "have.text", + "Password" + ); + + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextSave + ); + cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement( + "have.text", + "Invalid credentials" + ); + }); + + it("Should verify the functionality of SMTP connection form.", () => { + selectDataSource("SMTP"); + + cy.clearAndType( + postgreSqlSelector.dataSourceNameInputField, + "cypress-smtp" + ); + + fillDataSourceTextField( + postgreSqlText.labelHost, + postgreSqlText.placeholderEnterHost, + Cypress.env("smtp_host") + ); + fillDataSourceTextField( + postgreSqlText.labelPort, + "Recommended port 465 (Secured)", + Cypress.env("smtp_port") + ); + + fillDataSourceTextField( + "User", + postgreSqlText.placeholderEnterUserName, + Cypress.env("smtp_user") + ); + + cy.get(postgreSqlSelector.passwordTextField).type( + Cypress.env("smtp_password") + ); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 20000, + }).should("have.text", postgreSqlText.labelConnectionVerified, { + timeout: 10000, + }); + cy.get(postgreSqlSelector.buttonSave).click(); + + cy.verifyToastMessage( + commonSelectors.toastMessage, + postgreSqlText.toastDSAdded + ); + + cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click(); + cy.get(postgreSqlSelector.datasourceLabelOnList) + .should("have.text", "cypress-smtp") + .should("be.visible"); + }); +}); From 219a42ab1c94c97369bc25102be3b3b969ea587c Mon Sep 17 00:00:00 2001 From: Lola Date: Tue, 17 Jan 2023 05:44:01 -0600 Subject: [PATCH 16/56] [docs] Update dead docs link (#5302) The current documentation link in the readme appears to be out of date, and gives a 404. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd616d6ce6..b37580d832 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ You can use ToolJet cloud for a fully managed solution. If you want to self-host ## Community support -For general help using ToolJet, please refer to the official [documentation](https://docs.tooljet.com/docs/intro/). For additional help, you can use one of these channels to ask a question: +For general help using ToolJet, please refer to the official [documentation](https://docs.tooljet.com/docs/). For additional help, you can use one of these channels to ask a question: - [Slack](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg) - Discussions with the community and the team. - [GitHub](https://github.com/ToolJet/ToolJet/issues) - For bug reports and feature requests. From 9c8cddebe93de291f458f049bbab72ff0126af59 Mon Sep 17 00:00:00 2001 From: Ajith KV Date: Tue, 17 Jan 2023 17:47:53 +0530 Subject: [PATCH 17/56] Fix and modify login spec (#5345) --- .../cypress/constants/selectors/common.js | 3 + .../cypress/constants/selectors/login.js | 15 ---- .../cypress/constants/texts/common.js | 2 + .../cypress/constants/texts/login.js | 11 --- .../cypress/e2e/authentication/login.cy.js | 79 ++++++++++++------- cypress-tests/cypress/support/commands.js | 14 ++-- .../cypress/support/utils/dashboard.js | 7 +- cypress-tests/cypress/support/utils/login.js | 43 ---------- .../cypress/support/utils/manageSSO.js | 3 +- 9 files changed, 66 insertions(+), 111 deletions(-) delete mode 100644 cypress-tests/cypress/constants/selectors/login.js delete mode 100644 cypress-tests/cypress/constants/texts/login.js delete mode 100644 cypress-tests/cypress/support/utils/login.js diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index 8e7a55cf30..99df0ac6d4 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -15,8 +15,10 @@ export const commonSelectors = { skipButton: ".driver-close-btn", skipInstallationModal: "[data-cy=skip-button]", homePageLogo: "[data-cy=home-page-logo]", + pageLogo: "[data-cy=page-logo]", workEmailLabel: '[data-cy="work-email-label"]', workEmailInputField: "[data-cy=work-email-input]", + emailInputError: '[data-cy="email-error-message"]', passwordLabel: '[data-cy="password-label"]', forgotPasswordLink: '[data-cy="forgot-password-link"]', passwordInputField: "[data-cy=password-input-field]", @@ -64,6 +66,7 @@ export const commonSelectors = { createWorkspaceButton: '[data-cy="create-workspace-button"]', workspaceLoginUrl: "[data-cy=workspace-login-url]", workspaceName: '[data-cy="workspace-name"]', + signInHeader: '[data-cy="sign-in-header"]', signInSubHeader: '[data-cy="sign-in-sub-header"]', createAnAccountLink: '[data-cy="create-an-account-link"]', SignUpSectionHeader: '[data-cy="signup-section-header"]', diff --git a/cypress-tests/cypress/constants/selectors/login.js b/cypress-tests/cypress/constants/selectors/login.js deleted file mode 100644 index d0a1b8df29..0000000000 --- a/cypress-tests/cypress/constants/selectors/login.js +++ /dev/null @@ -1,15 +0,0 @@ -export const loginSelectors = { - logo: "[data-cy=login-page-logo]", - cardTitle: "[data-cy=login-page-header]", - emailLabel: "[data-cy=email-label]", - emailField: "[data-cy=email-text-field]", - passwordLabel: "[data-cy=password-label]", - forgotPassword: "[data-cy=forgot-password-link]", - passwordField: "[data-cy=password-text-field]", - checkBox: "[data-cy=checkbox-input]", - showPassword: "[data-cy=show-password-label]", - signInButton: "[data-cy=login-button]", - signUpText: "[data-cy=sign-up-message]", - signUpLink: "[data-cy=sign-up-link]", - homePage: "[data-cy=home-page-logo]", -}; diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index d52099ccf4..ea82c9162d 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -51,9 +51,11 @@ export const commonText = { "Are you sure you want to delete the folder? Apps within the folder will not be deleted.", closeButton: "modal close", workEmailLabel: "Email", + emailInputError: "Invalid Email", passwordLabel: "Password", forgotPasswordLink: "Forgot?", loginButton: " Login", + signInHeader: "Sign in", signInSubHeader: "New to ToolJet?Create an account", SignUpSectionHeader: "Join ToolJet", signInRedirectText: "Already have an account?", diff --git a/cypress-tests/cypress/constants/texts/login.js b/cypress-tests/cypress/constants/texts/login.js deleted file mode 100644 index 4ca40d53b0..0000000000 --- a/cypress-tests/cypress/constants/texts/login.js +++ /dev/null @@ -1,11 +0,0 @@ -export const loginTexts = { - cardTitle: "Login to your account", - emailLabel: "Email address", - passwordLabel: "Password", - forgotPassword: "Forgot Password", - showPassword: "show password", - signIn: "Sign in", - signUpText: "Don't have account yet?", - signUpLink: "Sign up", - toastMessage: "Invalid email or password", -}; diff --git a/cypress-tests/cypress/e2e/authentication/login.cy.js b/cypress-tests/cypress/e2e/authentication/login.cy.js index a65c15584c..62fb7106fc 100644 --- a/cypress-tests/cypress/e2e/authentication/login.cy.js +++ b/cypress-tests/cypress/e2e/authentication/login.cy.js @@ -1,59 +1,82 @@ -import { loginSelectors } from "Selectors/login"; import { commonSelectors } from "Selectors/common"; -import { loginTexts } from "Texts/login"; import { fake } from "Fixtures/fake"; -import * as login from "Support/utils/login"; +import { commonText, path } from "Texts/common"; describe("Login functionality", () => { let user; const invalidEmail = fake.email; const invalidPassword = fake.password; - before(() => { + beforeEach(() => { cy.fixture("credentials/login.json").then((login) => { user = login; }); cy.visit("/"); }); it("Should verify elements on the login page", () => { - login.loginPageElements(); + cy.url().should("include", path.loginPath); + cy.get(commonSelectors.pageLogo).should("be.visible"); + cy.get(commonSelectors.signInHeader).verifyVisibleElement( + "have.text", + commonText.signInHeader + ); + cy.get(commonSelectors.workEmailLabel).verifyVisibleElement( + "have.text", + commonText.workEmailLabel + ); + cy.get(commonSelectors.passwordLabel).should(($el) => { + expect($el.contents().first().text().trim()).to.eq( + commonText.passwordLabel + ); + }); + cy.get(commonSelectors.forgotPasswordLink).verifyVisibleElement( + "have.text", + commonText.forgotPasswordLink + ); + cy.get(commonSelectors.loginButton).verifyVisibleElement( + "have.text", + commonText.loginButton + ); + + cy.get(commonSelectors.workEmailInputField).should("be.visible"); + cy.get(commonSelectors.passwordInputField).should("be.visible"); }); it("Should not be able to login with invalid credentials", () => { - cy.get(loginSelectors.signInButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - loginTexts.toastMessage + cy.get(commonSelectors.loginButton).click(); + cy.get(commonSelectors.emailInputError).verifyVisibleElement( + "have.text", + commonText.emailInputError ); - cy.clearAndType(loginSelectors.emailField, invalidEmail); - cy.get(loginSelectors.signInButton).click(); + cy.clearAndType(commonSelectors.workEmailInputField, invalidEmail); + cy.get(commonSelectors.loginButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - loginTexts.toastMessage + commonText.loginErrorToast ); - cy.get(loginSelectors.emailField).clear(); - cy.clearAndType(loginSelectors.passwordField, invalidPassword); - cy.get(loginSelectors.signInButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - loginTexts.toastMessage + cy.get(commonSelectors.workEmailInputField).clear(); + cy.clearAndType(commonSelectors.passwordInputField, invalidPassword); + cy.get(commonSelectors.loginButton).click(); + cy.get(commonSelectors.emailInputError).verifyVisibleElement( + "have.text", + commonText.emailInputError ); - cy.clearAndType(loginSelectors.emailField, user.email); - cy.get(loginSelectors.passwordField).clear(); - cy.get(loginSelectors.signInButton).click(); + cy.clearAndType(commonSelectors.workEmailInputField, user.email); + cy.get(commonSelectors.passwordInputField).clear(); + cy.get(commonSelectors.loginButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - loginTexts.toastMessage + commonText.loginErrorToast ); - cy.get(loginSelectors.emailField).clear(); - cy.clearAndType(loginSelectors.passwordField, user.password); - cy.get(loginSelectors.signInButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - loginTexts.toastMessage + cy.get(commonSelectors.workEmailInputField).clear(); + cy.clearAndType(commonSelectors.passwordInputField, user.password); + cy.get(commonSelectors.loginButton).click(); + cy.get(commonSelectors.emailInputError).verifyVisibleElement( + "have.text", + commonText.emailInputError ); }); it("Should be able to login with valid credentials", () => { diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 134fa0c58b..595c4f6d05 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1,18 +1,16 @@ import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { dashboardSelector } from "Selectors/dashboard"; -import { loginSelectors } from "Selectors/login"; import { ssoSelector } from "Selectors/manageSSO"; import { commonText, createBackspaceText } from "Texts/common"; import { passwordInputText } from "Texts/passwordInput"; Cypress.Commands.add("login", (email, password) => { cy.visit("/"); - cy.clearAndType(loginSelectors.emailField, email); - cy.clearAndType(loginSelectors.passwordField, password); - cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=").as("homePage"); - cy.get(loginSelectors.signInButton).click(); - cy.get(loginSelectors.homePage).should("be.visible"); - cy.wait("@homePage"); + cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); + cy.clearAndType(commonSelectors.passwordInputField, "password"); + cy.get(commonSelectors.signInButton).click(); + cy.get(commonSelectors.homePageLogo).should("be.visible"); + cy.wait(2000) }); Cypress.Commands.add("clearAndType", (selector, text) => { @@ -129,7 +127,7 @@ Cypress.Commands.add("appUILogin", () => { cy.visit("/"); cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); cy.clearAndType(commonSelectors.passwordInputField, "password"); - cy.get(loginSelectors.signInButton).click(); + cy.get(commonSelectors.signInButton).click(); cy.get(commonSelectors.homePageLogo).should("be.visible"); cy.wait(2000); cy.get("body").then(($el) => { diff --git a/cypress-tests/cypress/support/utils/dashboard.js b/cypress-tests/cypress/support/utils/dashboard.js index a9654e8ae4..61f5475e9e 100644 --- a/cypress-tests/cypress/support/utils/dashboard.js +++ b/cypress-tests/cypress/support/utils/dashboard.js @@ -1,7 +1,6 @@ import { commonSelectors } from "Selectors/common"; import { dashboardSelector } from "Selectors/dashboard"; import { dashboardText } from "Texts/dashboard"; -import { loginSelectors } from "Selectors/login"; import { commonText } from "Texts/common"; import { viewAppCardOptions, @@ -12,9 +11,9 @@ import { export const login = () => { cy.visit("/"); - cy.clearAndType(loginSelectors.emailField, "dev@tooljet.io"); - cy.clearAndType(loginSelectors.passwordField, "password"); - cy.get(loginSelectors.signInButton).click(); + cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); + cy.clearAndType(commonSelectors.passwordInputField, "password"); + cy.get(commonSelectors.loginButton).click(); }; export const modifyAndVerifyAppCardIcon = (appName) => { diff --git a/cypress-tests/cypress/support/utils/login.js b/cypress-tests/cypress/support/utils/login.js deleted file mode 100644 index bc3b9b3b4c..0000000000 --- a/cypress-tests/cypress/support/utils/login.js +++ /dev/null @@ -1,43 +0,0 @@ -import { loginSelectors } from "Selectors/login"; -import { loginTexts } from "Texts/login"; -import { path } from "Texts/common"; - -export const loginPageElements = () => { - cy.url().should("include", path.loginPath); - cy.get(loginSelectors.logo).should("be.visible"); - cy.get(loginSelectors.cardTitle).verifyVisibleElement( - "have.text", - loginTexts.cardTitle - ); - cy.get(loginSelectors.emailLabel).verifyVisibleElement( - "have.text", - loginTexts.emailLabel - ); - cy.get(loginSelectors.passwordLabel) - .should(($el) => { - expect($el.contents().first().text().trim()).to.eq( - loginTexts.passwordLabel - ); - }) - .should("be.visible"); - cy.get(loginSelectors.forgotPassword).verifyVisibleElement( - "have.text", - loginTexts.forgotPassword - ); - cy.get(loginSelectors.showPassword).should( - "have.text", - loginTexts.showPassword - ); - cy.get(loginSelectors.signUpText).should("be.visible"); - cy.get(loginSelectors.checkBox).check(); - cy.get(loginSelectors.checkBox).uncheck(); - cy.get(loginSelectors.signUpText) - .should(($el) => { - expect($el.contents().first().text().trim()).to.eq(loginTexts.signUpText); - }) - .should("be.visible"); - cy.get(loginSelectors.signUpLink).verifyVisibleElement( - "have.text", - loginTexts.signUpLink - ); -}; diff --git a/cypress-tests/cypress/support/utils/manageSSO.js b/cypress-tests/cypress/support/utils/manageSSO.js index ae0c792873..36db2f0edd 100644 --- a/cypress-tests/cypress/support/utils/manageSSO.js +++ b/cypress-tests/cypress/support/utils/manageSSO.js @@ -3,7 +3,6 @@ import { ssoSelector } from "Selectors/manageSSO"; import { ssoText } from "Texts/manageSSO"; import * as common from "Support/utils/common"; import { commonText } from "Texts/common"; -import { loginSelectors } from "Selectors/login"; import { dashboardSelector } from "Selectors/dashboard"; export const generalSettings = () => { @@ -286,7 +285,7 @@ export const passwordLoginVisible = () => { export const workspaceLogin = (workspaceName) => { cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); cy.clearAndType(commonSelectors.passwordInputField, "password"); - cy.get(loginSelectors.signInButton).click(); + cy.get(commonSelectors.loginButton).click(); cy.get(commonSelectors.homePageLogo).should("be.visible"); cy.get(dashboardSelector.modeToggle, { timeout: 10000 }).should("be.visible"); cy.get(commonSelectors.workspaceName).verifyVisibleElement( From 82b8ade0509cc6405d0f2d5a8844e0f7b50cc01d Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 18 Jan 2023 14:19:20 +0530 Subject: [PATCH 18/56] add internal table count for telemetry (#5285) --- server/src/services/metadata.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 1c26759802..72998ca360 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -6,6 +6,7 @@ import { gt } from 'semver'; import got from 'got'; import { User } from 'src/entities/user.entity'; import { ConfigService } from '@nestjs/config'; +import { InternalTable } from 'src/entities/internal_table.entity'; @Injectable() export class MetadataService { @@ -72,6 +73,7 @@ export class MetadataService { async sendTelemetryData(metadata: Metadata) { const manager = getManager(); const totalUserCount = await manager.count(User); + const totalInternalTableCount = await manager.count(InternalTable); const totalEditorCount = await this.fetchTotalEditorCount(manager); const totalViewerCount = await this.fetchTotalViewerCount(manager); @@ -83,6 +85,7 @@ export class MetadataService { total_users: totalUserCount, total_editors: totalEditorCount, total_viewers: totalViewerCount, + tooljet_db_table_count: totalInternalTableCount, tooljet_version: globalThis.TOOLJET_VERSION, deployment_platform: this.configService.get('DEPLOYMENT_PLATFORM'), }, From c22059498a3e61fa8b107403bb4a1b17000a780c Mon Sep 17 00:00:00 2001 From: vjaris42 Date: Thu, 19 Jan 2023 14:45:39 +0530 Subject: [PATCH 19/56] add: received props check to update state on initial render (#5362) --- frontend/src/_ui/JSONTreeViewer/JSONTreeViewer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/_ui/JSONTreeViewer/JSONTreeViewer.jsx b/frontend/src/_ui/JSONTreeViewer/JSONTreeViewer.jsx index 884373641a..dbf84d64ed 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONTreeViewer.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONTreeViewer.jsx @@ -14,7 +14,7 @@ export class JSONTreeViewer extends React.Component { hoveredNode: null, darkTheme: false, showHideActions: false, - enableCopyToClipboard: false, + enableCopyToClipboard: props.enableCopyToClipboard ?? false, actionsList: props.actionsList || [], useActions: props.useActions ?? false, }; From 6e057e89b9a42f353b5b2a4e381c7d855f661efb Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 19 Jan 2023 15:32:21 +0530 Subject: [PATCH 20/56] [Bugfix] The sequence of primary columns in the table changes after updating rows using the query builder (#5374) * fixes: The sequence of primary columns in the table changes after updating rows using the query builder * Revert "fixes: The sequence of primary columns in the table changes after updating rows using the query builder" This reverts commit b0f262332e27986f5ccfa6498a956a464b8a3373. * update query with order as parameter --- .../QueryManager/QueryEditors/TooljetDatabase/operations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js index e59e2accf5..1acecf40c7 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js @@ -92,7 +92,7 @@ async function updateRows(queryOptions, organizationId, currentState) { !isEmpty(whereQuery) && query.push(whereQuery); - return await tooljetDatabaseService.updateRows(organizationId, tableName, body, query.join('&')); + return await tooljetDatabaseService.updateRows(organizationId, tableName, body, query.join('&') + '&order=id'); } async function deleteRows(queryOptions, organizationId, currentState) { From 15bd45c2d2dcbb4b9b305d9f2de81898aae6b24e Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Thu, 19 Jan 2023 16:09:05 +0530 Subject: [PATCH 21/56] Add cypress.env.example (#5366) * Add cypress.env.example file * Remove js config(unused) * Remove all env vars from config file --- cypress-tests/.gitignore | 3 +- cypress-tests/cypress.config.js | 48 ---------------------------- cypress-tests/cypress.env.example | 49 +++++++++++++++++++++++++++++ cypress-tests/cypress/jsconfig.json | 12 ------- 4 files changed, 51 insertions(+), 61 deletions(-) create mode 100644 cypress-tests/cypress.env.example delete mode 100644 cypress-tests/cypress/jsconfig.json diff --git a/cypress-tests/.gitignore b/cypress-tests/.gitignore index 30bc162798..ca68be770b 100644 --- a/cypress-tests/.gitignore +++ b/cypress-tests/.gitignore @@ -1 +1,2 @@ -/node_modules \ No newline at end of file +/node_modules +/cypress.env.json \ No newline at end of file diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index d958bc3db7..ad26145508 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -12,55 +12,7 @@ module.exports = defineConfig({ viewportHeight: 960, chromeWebSecurity: false, trashAssetsBeforeRuns: true, - env: { - pg_host: "", - pg_user: "", - pg_password: "", - elasticsearch_host: "", - elasticsearch_user: "", - elasticsearch_password: "", - - sso_password: "", - git_user: "", - google_user: "", - - dynamodb_access_key: "", - dynamodb_secret_key: "", - - smtp_host: "", - smtp_port: "587", - smtp_user: "", - smtp_password: "", - - redis_host: "", - redis_port: "", - redis_password: "", - - mongodb_connString: "", - mongodb_host: "", - mongodb_user: "", - mongo_password: "", - - bigquery_pvt_key: {}, - - firestore_pvt_key: {}, - - mysql_host: "", - mysql_user: "", - mysql_password: "", - - aws_access: "", - aws_secret: "", - - }, - db: { - user: "postgres", - host: "localhost", - database: "tooljet_development", - password: "postgres", - port: "5432", - }, e2e: { setupNodeEvents(on, config) { on("task", { diff --git a/cypress-tests/cypress.env.example b/cypress-tests/cypress.env.example new file mode 100644 index 0000000000..35a68e3669 --- /dev/null +++ b/cypress-tests/cypress.env.example @@ -0,0 +1,49 @@ +{ + "sso_password": "", + "git_user": "", + "google_user": "", + + "pg_host": "", + "pg_user": "", + "pg_password": "", + + "elasticsearch_host": "", + "elasticsearch_user": "", + "elasticsearch_password": "", + + "dynamodb_access_key": "", + "dynamodb_secret_key": "", + + "smtp_host": "", + "smtp_port": "", + "smtp_user": "", + "smtp_password": "", + + "redis_host": "", + "redis_port": "", + "redis_password": "", + + "mongodb_connString": "", + "mongodb_host": "", + "mongodb_user": "", + "mongo_password": "", + + "bigquery_pvt_key": {}, + + "firestore_pvt_key": {}, + + "mysql_host": "", + "mysql_user": "", + "mysql_password": "", + + "aws_access": "", + "aws_secret": "", + + "app_db": { + "user": "postgres", + "host": "localhost", + "database": "tooljet_development", + "password": "postgres", + "port": "5432" + } +} diff --git a/cypress-tests/cypress/jsconfig.json b/cypress-tests/cypress/jsconfig.json deleted file mode 100644 index 77fcc67601..0000000000 --- a/cypress-tests/cypress/jsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "Texts/*": [ - "./constants/texts/*" - ], - "Selectors/*": [ - "./constants/selectors/*" - ] - } - } -} \ No newline at end of file From be3fc4b9bb84756801b06bdabbf8e8b668cbfa5f Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 19 Jan 2023 21:47:20 +0530 Subject: [PATCH 22/56] [Bugfix] app crashes when delete op is ran for tj db query (#5342) * fixes: app crashes when delete op is ran for tj db query * fixe: error toast for preview query: tj-db * fixes:Validation of Limit input field --- .../TooljetDatabase/operations.js | 21 +++++++++++++++++++ frontend/src/_helpers/appUtils.js | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js index 1acecf40c7..dcfe3dc449 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/operations.js @@ -59,6 +59,17 @@ async function listRows(queryOptions, organizationId, currentState) { if (!isEmpty(listRows)) { const { limit, where_filters: whereFilters, order_filters: orderFilters } = listRows; + + if (limit && isNaN(limit)) { + return { + status: 'failed', + statusText: 'failed', + message: 'Please provide a valid limit', + description: 'Limit should be a number', + data: {}, + }; + } + const whereQuery = buildPostgrestQuery(whereFilters); const orderQuery = buildPostgrestQuery(orderFilters); @@ -113,6 +124,16 @@ async function deleteRows(queryOptions, organizationId, currentState) { }; } + if (limit && isNaN(limit)) { + return { + status: 'failed', + statusText: 'failed', + message: 'Please provide a valid limit', + description: 'Limit should be a number', + data: {}, + }; + } + !isEmpty(whereQuery) && query.push(whereQuery); limit && limit !== '' && query.push(`limit=${limit}&order=id`); diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 9a628de9f6..0a3ad3176a 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -817,7 +817,7 @@ export function previewQuery(_ref, query, editorState, calledFromQuery = false) switch (queryStatus) { case 'Bad Request': case 'failed': { - const err = query.kind == 'tooljetdb' ? data.error : _.isEmpty(data.data) ? data : data.data; + const err = query.kind == 'tooljetdb' ? data?.error || data : _.isEmpty(data.data) ? data : data.data; toast.error(`${err.message}`); break; } @@ -962,8 +962,8 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = definition: { events: dataQuery.options.events }, }); if (mode !== 'view') { - const err = query.kind == 'tooljetdb' ? data.error : _.isEmpty(data.data) ? data : data.data; - toast.error(err.message); + const err = query.kind == 'tooljetdb' ? data?.error || data : _.isEmpty(data.data) ? data : data.data; + toast.error(err?.message); } } ); From 555bd2447a44179a5228e7cb330c03bf962d73d4 Mon Sep 17 00:00:00 2001 From: Sajda Kabir Date: Mon, 23 Jan 2023 10:14:49 +0530 Subject: [PATCH 23/56] [docs] minor correction (#5376) fixed the Widgets description --- docs/versioned_docs/version-1.x.x/tutorial/creating-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/versioned_docs/version-1.x.x/tutorial/creating-app.md b/docs/versioned_docs/version-1.x.x/tutorial/creating-app.md index 08d97e2496..9610d69bbb 100644 --- a/docs/versioned_docs/version-1.x.x/tutorial/creating-app.md +++ b/docs/versioned_docs/version-1.x.x/tutorial/creating-app.md @@ -26,6 +26,6 @@ You will be redirected to the visual app editor once the app has been created. C The main components of an app: -- **[Widgets](https://docs.tooljet.com/docs/tutorial/adding-widget)** - UI components such as tables, buttons, dropdowns. +- **[Widgets](https://docs.tooljet.com/docs/tutorial/adding-widget)** - UI components such as tables, buttons, and dropdowns etc. - **[Data sources](https://docs.tooljet.com/docs/tutorial/adding-a-datasource)** - ToolJet can connect to databases, APIs and external services to fetch and modify data. -- **[Queries](https://docs.tooljet.com/docs/tutorial/building-queries)** - Queries are used to access the connected data sources. \ No newline at end of file +- **[Queries](https://docs.tooljet.com/docs/tutorial/building-queries)** - Queries are used to access the connected data sources. From 540913306652fd34e855caadf15fe8d011fe21c0 Mon Sep 17 00:00:00 2001 From: Mike <89523154+mikebarr24@users.noreply.github.com> Date: Mon, 23 Jan 2023 20:19:29 +0000 Subject: [PATCH 24/56] chore: add backend version name length validation (#4938) * chore: add backend length va lidation to version name * update: use MaxLength Minlenght for version name dto validation * fix: dto version name validation Co-authored-by: Midhun G S --- server/src/dto/version-create.dto.ts | 4 +++- server/src/services/apps.service.ts | 7 ------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/server/src/dto/version-create.dto.ts b/server/src/dto/version-create.dto.ts index bd5eac8cd2..44f7d96f5d 100644 --- a/server/src/dto/version-create.dto.ts +++ b/server/src/dto/version-create.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { sanitizeInput } from '../helpers/utils.helper'; @@ -6,6 +6,8 @@ export class VersionCreateDto { @IsString() @Transform(({ value }) => sanitizeInput(value)) @IsNotEmpty() + @MinLength(0, { message: 'Version name cannot be empty.' }) + @MaxLength(25, { message: 'Version name cannot be longer than 25 characters' }) versionName: string; @IsUUID() diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index 8b489ef76f..4dbada84cc 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -250,13 +250,6 @@ export class AppsService { versionFromId: string, manager?: EntityManager ): Promise { - if (!versionName) { - throw new BadRequestException('Version name cannot be empty.'); - } - if (versionName.length > 25) { - throw new BadRequestException('Version name cannot be longer than 25 characters.'); - } - return await dbTransactionWrap(async (manager: EntityManager) => { let versionFrom: AppVersion; if (versionFromId) { From d5e7bbc53d488b016d0b872558681d837a17516a Mon Sep 17 00:00:00 2001 From: tanishk-23 <72482878+tanishk-23@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:53:28 +0530 Subject: [PATCH 25/56] Bug: Added workspace title length validation while editing (#4671) --- frontend/src/_components/Organization.jsx | 6 ++++++ .../src/controllers/organizations.controller.ts | 16 +++++++++++++++- .../test/controllers/organizations.e2e-spec.ts | 10 ++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/frontend/src/_components/Organization.jsx b/frontend/src/_components/Organization.jsx index 22c53df4d8..96f57d8415 100644 --- a/frontend/src/_components/Organization.jsx +++ b/frontend/src/_components/Organization.jsx @@ -108,6 +108,12 @@ export const Organization = function Organization({ darkMode }) { }); return; } + if (newOrgName.length > 25) { + toast.error('Workspace name cannot be longer than 25 characters.', { + position: 'top-center', + }); + return; + } if (organizationNameExists) { toast.error(`The workspace ${newOrgName} already exists.`, { position: 'top-center', diff --git a/server/src/controllers/organizations.controller.ts b/server/src/controllers/organizations.controller.ts index eb3c910e9b..e71ddd2528 100644 --- a/server/src/controllers/organizations.controller.ts +++ b/server/src/controllers/organizations.controller.ts @@ -1,4 +1,15 @@ -import { Body, Controller, Get, NotFoundException, Param, Patch, Post, UseGuards, Query } from '@nestjs/common'; +import { + BadRequestException, + Body, + Controller, + Get, + NotFoundException, + Param, + Patch, + Post, + UseGuards, + Query, +} from '@nestjs/common'; import { OrganizationsService } from '@services/organizations.service'; import { AppConfigService } from '@services/app_config.service'; import { decamelizeKeys } from 'humps'; @@ -113,6 +124,9 @@ export class OrganizationsController { @CheckPolicies((ability: AppAbility) => ability.can('updateOrganizations', UserEntity)) @Patch() async update(@Body() body, @User() user) { + if (body.name.length > 25) { + throw new BadRequestException('name cannot be longer than 25 characters'); + } await this.organizationsService.updateOrganization(user.organizationId, body); return {}; } diff --git a/server/test/controllers/organizations.e2e-spec.ts b/server/test/controllers/organizations.e2e-spec.ts index cd98d2c77e..a0adee1936 100644 --- a/server/test/controllers/organizations.e2e-spec.ts +++ b/server/test/controllers/organizations.e2e-spec.ts @@ -148,6 +148,16 @@ describe('organizations controller', () => { expect(organization.enableSignUp).toBeTruthy(); }); + it('should throw error if name is longer than 25 characters', async () => { + const { user } = await createUser(app, { email: 'admin@tooljet.io' }); + const response = await request(app.getHttpServer()) + .post('/api/organizations') + .send({ name: 'xxxxxxxxxxxxxxxxxxxxxxxxxx' }) + .set('Authorization', authHeaderForUser(user)); + + expect(response.statusCode).toBe(400); + }); + it('should not change organization params if changes are not done by admin', async () => { const { organization } = await createUser(app, { email: 'admin@tooljet.io' }); const developerUserData = await createUser(app, { From 82cad72fc396f14229d228d1af0f0f9f183e2d5d Mon Sep 17 00:00:00 2001 From: vjaris42 Date: Tue, 24 Jan 2023 11:45:57 +0530 Subject: [PATCH 26/56] File picker issues on CSV parsing (#5025) * fix: csv parsing for multiline values * fix: remove escape characters while copying inspector values * add: defalut value for null/undefined cells --- frontend/src/Editor/Components/FilePicker.jsx | 19 ++++++------------- .../Editor/LeftSidebar/SidebarInspector.jsx | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/frontend/src/Editor/Components/FilePicker.jsx b/frontend/src/Editor/Components/FilePicker.jsx index 5706e2f62b..c99807c038 100644 --- a/frontend/src/Editor/Components/FilePicker.jsx +++ b/frontend/src/Editor/Components/FilePicker.jsx @@ -386,25 +386,18 @@ FilePicker.AcceptedFiles = ({ children, width, height }) => { }; const processCSV = (str, delimiter = ',') => { - const headers = str.slice(0, str.indexOf('\n')).split(delimiter); - const rows = str.slice(str.indexOf('\n') + 1).split('\n'); - try { - const newArray = rows.map((row) => { - const values = row.split(delimiter); - const eachObject = headers.reduce((obj, header, i) => { - obj[header] = values[i]; - return obj; - }, {}); - return eachObject; - }); - - return newArray; + const wb = XLSX.read(str, { type: 'string' }); + const wsname = wb.SheetNames[0]; + const ws = wb.Sheets[wsname]; + const data = XLSX.utils.sheet_to_json(ws, { delimiter, defval: '' }); + return data; } catch (error) { console.log(error); handleErrors(error); } }; + const processXls = (str) => { try { const wb = XLSX.read(str, { type: 'base64' }); diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx index 6d1adddef7..3c079e69d7 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx @@ -122,7 +122,7 @@ export const LeftSidebarInspector = ({ }; const copyToClipboard = (data) => { - const stringified = JSON.stringify(data, null, 2); + const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); navigator.clipboard.writeText(stringified); return toast.success('Copied to the clipboard', { position: 'top-center' }); }; From 25d7f70d5a8622bb28683e088b8dfc9f7415854b Mon Sep 17 00:00:00 2001 From: Muhsin Shah C P Date: Tue, 24 Jan 2023 11:56:06 +0530 Subject: [PATCH 27/56] fixed last name null issue (#5327) --- frontend/src/Editor/CommentNotifications/Content.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Editor/CommentNotifications/Content.jsx b/frontend/src/Editor/CommentNotifications/Content.jsx index 6ebb97156d..7be99d700e 100644 --- a/frontend/src/Editor/CommentNotifications/Content.jsx +++ b/frontend/src/Editor/CommentNotifications/Content.jsx @@ -43,7 +43,7 @@ const Content = ({ notifications, loading, darkMode }) => { >
- {`${comment.user?.firstName} ${comment.user?.lastName}`}{' '} + {`${comment.user?.firstName} ${comment.user?.lastName ?? ''}`}{' '}
{moment(comment.createdAt).fromNow()} From 686de2b224a414c365cb70e7ee396a28abf825d0 Mon Sep 17 00:00:00 2001 From: vjaris42 Date: Tue, 24 Jan 2023 12:07:12 +0530 Subject: [PATCH 28/56] fix: route name for manage users (#5364) --- frontend/src/Editor/ManageAppUsers.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Editor/ManageAppUsers.jsx b/frontend/src/Editor/ManageAppUsers.jsx index e41004f371..e146ab5266 100644 --- a/frontend/src/Editor/ManageAppUsers.jsx +++ b/frontend/src/Editor/ManageAppUsers.jsx @@ -260,7 +260,7 @@ class ManageAppUsersComponent extends React.Component { {this.currentUser?.admin && ( - + Manage users )} From 29d37798301544a0a2512e4b41f7fabd4c4ff9cd Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 24 Jan 2023 13:43:01 +0530 Subject: [PATCH 29/56] Added spec to verify common components operations (#5409) * Modify method to drag and drop * Modify method editAndVerifWidgetName * Add basic funtions for the spec * Add spec --- .../widget/componentsBasicHappypath.cy.js | 604 ++++++++++++++++++ cypress-tests/cypress/support/commands.js | 4 +- .../cypress/support/utils/basicComponents.js | 45 ++ .../cypress/support/utils/commonWidget.js | 7 +- 4 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js create mode 100644 cypress-tests/cypress/support/utils/basicComponents.js diff --git a/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js new file mode 100644 index 0000000000..4ae5c44a53 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js @@ -0,0 +1,604 @@ +import { commonWidgetSelector, commonSelectors } from "Selectors/common"; +import { fake } from "Fixtures/fake"; +import { tableText } from "Texts/table"; +import { tableSelector } from "Selectors/table"; +import { + verifyComponent, + deleteComponentAndVerify, + verifyComponentWithOutLabel +} from "Support/utils/basicComponents"; +import { + openAccordion, + verifyAndModifyParameter, + openEditorSidebar, + verifyAndModifyToggleFx, + addDefaultEventHandler, + addAndVerifyTooltip, + editAndVerifyWidgetName, + verifyComponentValueFromInspector, + fillBoxShadowParams, + selectColourFromColourPicker, + addTextWidgetToVerifyValue, + verifyBoxShadowCss, + verifyTooltip, + verifyWidgetText, + closeAccordions, +} from "Support/utils/commonWidget"; +import { + commonText, + commonWidgetText, + codeMirrorInputLabel, +} from "Texts/common"; + +describe("Basic components", () => { + const data = {}; + beforeEach(() => { + data.appName = `${fake.companyName}-App`; + cy.appUILogin(); + cy.createApp(); + cy.modifyCanvasSize(900, 900); + cy.renameApp(data.appName); + }); + + it("Should verify Toggle switch", () => { + cy.dragAndDropWidget("Toggle Switch", 50, 50); + verifyComponent("toggleswitch1"); + + cy.resizeWidget("toggleswitch1", 850, 600); + + openEditorSidebar("toggleswitch1"); + editAndVerifyWidgetName("toggleswitch2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("toggleswitch2"); + + cy.go("back"); + deleteComponentAndVerify("toggleswitch2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Checkbox", () => { + cy.dragAndDropWidget("Checkbox", 50, 50); + cy.resizeWidget("checkbox1", 100, 200); + cy.forceClickOnCanvas(); + verifyComponent("checkbox1"); + + cy.resizeWidget("checkbox1", 850, 600); + + openEditorSidebar("checkbox1"); + editAndVerifyWidgetName("checkbox2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.forceClickOnCanvas(); + cy.get('[data-cy="draggable-widget-checkbox2"] .form-check-label').should( + "have.text", + "label" + ); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("checkbox2"); + + cy.go("back"); + deleteComponentAndVerify("checkbox2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Radio Button", () => { + cy.dragAndDropWidget("Radio Button", 50, 50); + // cy.resizeWidget("radiobutton1", 100, 200); + cy.forceClickOnCanvas(); + verifyComponent("radiobutton1"); + + cy.resizeWidget("radiobutton1", 850, 600); + + openEditorSidebar("radiobutton1"); + editAndVerifyWidgetName("radiobutton2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + cy.get('[data-cy="draggable-widget-radiobutton2"] > .col-auto').should( + "have.text", + "label" + ); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("radiobutton2"); + + cy.go("back"); + deleteComponentAndVerify("radiobutton2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + it("Should verify Dropdown", () => { + cy.dragAndDropWidget("Dropdown", 50, 50); + // cy.resizeWidget("radiobutton1", 100, 200); + cy.forceClickOnCanvas(); + verifyComponent("dropdown1"); + + cy.resizeWidget("dropdown1", 850, 600); + + openEditorSidebar("dropdown1"); + editAndVerifyWidgetName("dropdown2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + cy.get('[data-cy="draggable-widget-dropdown2"] > .col-auto').should( + "have.text", + "label" + ); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("dropdown2"); + + cy.go("back"); + deleteComponentAndVerify("dropdown2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + //pending + it("Should verify Rating", () => { + cy.dragAndDropWidget("Rating", 300, 300); + cy.get('[data-cy="draggable-widget-starrating1"]').click({ force: true }); + // cy.resizeWidget("starrating1", 300, 500); + cy.forceClickOnCanvas(); + verifyComponent("starrating1"); + + cy.resizeWidget("starrating1", 850, 600); + + openEditorSidebar("starrating1"); + editAndVerifyWidgetName("starrating2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + cy.get('[data-cy="draggable-widget-starrating2"] > .col-auto').should( + "have.text", + "label" + ); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("starrating2"); + + cy.go("back"); + deleteComponentAndVerify("starrating2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Button Group", () => { + cy.dragAndDropWidget("Button Group", 300, 300); + cy.forceClickOnCanvas(); + verifyComponent("buttongroup1"); + + cy.resizeWidget("buttongroup1", 850, 600); + + openEditorSidebar("buttongroup1"); + editAndVerifyWidgetName("buttongroup2"); + + verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + cy.get( + '[data-cy="draggable-widget-buttongroup2"] > .widget-buttongroup-label' + ).should("have.text", "label"); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("buttongroup2"); + + cy.go("back"); + deleteComponentAndVerify("buttongroup2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Calendar", () => { + cy.dragAndDropWidget("Calendar", 50, 50); + cy.get('[data-tip="Hide query editor"]').click(); + cy.get('[data-cy="draggable-widget-calendar1"]').click({ force: true }); + cy.forceClickOnCanvas(); + verifyComponent("calendar1"); + + cy.resizeWidget("calendar1", 850, 600); + + openEditorSidebar("calendar1"); + editAndVerifyWidgetName("calendar2"); + + cy.waitForAutoSave(); + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("calendar2"); + + cy.go("back"); + deleteComponentAndVerify("calendar2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Chart", () => { + cy.dragAndDropWidget("Chart", 50, 50); + cy.get('[data-cy="draggable-widget-chart1"]').click({ force: true }); + cy.forceClickOnCanvas(); + verifyComponent("chart1"); + + cy.resizeWidget("chart1", 850, 600); + + openEditorSidebar("chart1"); + editAndVerifyWidgetName("chart2", ["Chart data", "Properties"]); + + verifyAndModifyParameter("Title", "label"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + cy.get('[data-cy="draggable-widget-chart2"]').should( + "contain.text", + "label" + ); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("chart2"); + + cy.go("back"); + deleteComponentAndVerify("chart2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Circular Progress Bar", () => { + cy.dragAndDropWidget("Circular Progressbar", 300, 300); + cy.forceClickOnCanvas(); + verifyComponent("circularprogressbar1"); + + cy.resizeWidget("circularprogressbar1", 850, 600); + + openEditorSidebar("circularprogressbar1"); + editAndVerifyWidgetName("circularprogressbar2"); + + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("circularprogressbar2"); + + cy.go("back"); + deleteComponentAndVerify("circularprogressbar2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Code Editor", () => { + cy.dragAndDropWidget("Code Editor", 300, 300); + cy.get('[data-cy="draggable-widget-codeeditor1"]').click({ force: true }); + cy.forceClickOnCanvas(); + verifyComponent("codeeditor1"); + + cy.resizeWidget("codeeditor1", 850, 600); + + openEditorSidebar("codeeditor1"); + editAndVerifyWidgetName("codeeditor2"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("codeeditor2"); + + cy.go("back"); + deleteComponentAndVerify("codeeditor2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Color Picker", () => { + cy.dragAndDropWidget("Color Picker", 300, 300); + cy.get('[data-cy="draggable-widget-colorpicker1"]').click({ force: true }); + cy.forceClickOnCanvas(); + verifyComponent("colorpicker1"); + + cy.resizeWidget("colorpicker1", 850, 600); + + openEditorSidebar("colorpicker1"); + editAndVerifyWidgetName("colorpicker2"); + + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("colorpicker2"); + + cy.go("back"); + deleteComponentAndVerify("colorpicker2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); +//needed fix + it.skip("Should verify Custom Component", () => { + cy.dragAndDropWidget("Custom Component", 50, 50); + cy.get('[data-cy="draggable-widget-customcomponent1"]').click({ force: true }); + cy.forceClickOnCanvas(); + verifyComponent("customcomponent1"); + openEditorSidebar("customcomponent1"); + + // editAndVerifyWidgetName("customcomponent2", ["Code"]); + closeAccordions(["Code"]); + cy.get(commonWidgetSelector.WidgetNameInputField).type("{selectAll}{backspace}customcomponent2", {delay:30}); + cy.forceClickOnCanvas() + + cy.get(commonWidgetSelector.draggableWidget(name)).trigger("mouseover"); + cy.get(commonWidgetSelector.widgetConfigHandle(name)) + .click() + .should("have.text", name); + + cy.resizeWidget("customcomponent1", 850, 600); + + openEditorSidebar("customcomponent1"); + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("customcomponent2", ["Code"]); + + cy.go("back"); + deleteComponentAndVerify("customcomponent2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Container", () => { + cy.dragAndDropWidget("Container", 50, 50); + cy.forceClickOnCanvas(); + verifyComponent("container1"); + + cy.resizeWidget("container1", 850, 600); + + openEditorSidebar("container1"); + editAndVerifyWidgetName("container2", ["Layout"]); + + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("container2", ["Layout"]); + + cy.go("back"); + deleteComponentAndVerify("container2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Date-Range Picker", () => { + cy.dragAndDropWidget("Range Picker", 300, 300); + + cy.forceClickOnCanvas(); + verifyComponent("daterangepicker1"); + + cy.resizeWidget("daterangepicker1", 850, 600); + + openEditorSidebar("daterangepicker1"); + editAndVerifyWidgetName("daterangepicker2"); + + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("daterangepicker2"); + + cy.go("back"); + deleteComponentAndVerify("daterangepicker2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); +//visible issue + it.skip("Should verify Divider", () => { + verifyComponentWithOutLabel("Divider", "divider1", "divider2", data.appName) + }); + + it("Should verify File Picker", () => { + verifyComponentWithOutLabel("File Picker", "filepicker1", "filepicker2", data.appName) + }); + + it("Should verify Form", () => { + cy.dragAndDropWidget("Form", 50, 50); + verifyComponent("form1"); + + cy.resizeWidget("form1", 850, 600); + + openEditorSidebar("form1"); + editAndVerifyWidgetName("form2"); + + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("form2"); + + cy.go("back"); + deleteComponentAndVerify("form2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify HTML", () => { + cy.dragAndDropWidget("HTML Viewe", 50, 50, "HTML Viewer"); // search logic WIP + verifyComponent("html1"); + + cy.resizeWidget("html1", 850, 600); + + openEditorSidebar("html1"); + editAndVerifyWidgetName("html2"); + + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("html2"); + + cy.go("back"); + deleteComponentAndVerify("html2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Icon", () => { + verifyComponentWithOutLabel("Icon", "icon1", "icon2", data.appName) + }); + + it("Should verify Iframe", () => { + verifyComponentWithOutLabel("Iframe", "iframe1", "iframe2", data.appName) + }); + + it("Should verify Kamban", () => { + verifyComponentWithOutLabel("Kanban Board", "kanbanboard1", "kanbanboard2", data.appName) }); + + it("Should verify Link", () => { + verifyComponentWithOutLabel("Link", "link1", "link2", data.appName) + }); + + it("Should verify Map", () => { + cy.dragAndDropWidget("Map", 50, 50); + cy.get("body").then($body => { + if ($body.find(".dismissButton").length > 0) { + cy.get('.dismissButton').click(); + } + }) + + verifyComponent("map1"); + + cy.resizeWidget("map1", 850, 600); + + openEditorSidebar("map1"); + editAndVerifyWidgetName("map2"); + + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("map2"); + + cy.go("back"); + deleteComponentAndVerify("map2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Modal", () => { + verifyComponentWithOutLabel("Modal", "modal1", "modal2", data.appName) + }); + + it("Should verify PDF", () => { + cy.dragAndDropWidget("PDF", 50, 50); + cy.get('[data-tip="Hide query editor"]').click(); + verifyComponent("pdf1"); + + cy.resizeWidget("pdf1", 850, 600); + + openEditorSidebar("pdf1"); + editAndVerifyWidgetName("pdf2"); + + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("pdf2"); + + cy.go("back"); + deleteComponentAndVerify("pdf2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Pagination", () => { + verifyComponentWithOutLabel("Pagination", "pagination1", "pagination2", data.appName) + }); + + it("Should verify QR Scanner", () => { + verifyComponentWithOutLabel("QR Scanner", "qrscanner1", "qrscanner2", data.appName) + }); + + it("Should verify Range Slider", () => { + verifyComponentWithOutLabel("Range Slider", "rangeslider1", "rangeslider2", data.appName) + }); + + it("Should verify Rich Text Editor", () => { + verifyComponentWithOutLabel("Text Editor", "richtexteditor1", "richtexteditor2", data.appName) + }); + + it("Should verify Spinner", () => { + verifyComponentWithOutLabel("Spinner", "spinner1", "spinner2", data.appName); + }); + + it("Should verify Statistics", () => { + verifyComponentWithOutLabel("Statistics", "statistics1", "statistics2", data.appName) + }); + + it("Should verify Steps", () => { + verifyComponentWithOutLabel("Steps", "steps1", "steps2", data.appName) + }); + + it("Should verify SVG Image", () => { + verifyComponentWithOutLabel("SVG Image", "svgimage1", "svgimage2", data.appName) + }); + + it("Should verify Tabs", () => { + cy.dragAndDropWidget("Tabs", 50, 50); + verifyComponent("tabs1"); + deleteComponentAndVerify("image1"); + + cy.resizeWidget("tabs1", 850, 600); + + openEditorSidebar("tabs1"); + editAndVerifyWidgetName("tabs2"); + + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent("tabs2"); + + cy.go("back"); + deleteComponentAndVerify("tabs2"); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(data.appName); + }); + + it("Should verify Tags", () => { + verifyComponentWithOutLabel("Tags", "tags1", "tags2", data.appName) + }); + + it("Should verify Textarea", () => { + verifyComponentWithOutLabel("Textarea", "textarea1", "textarea2", data.appName) + }); + + it("Should verify Timeline", () => { + verifyComponentWithOutLabel("Timeline", "timeline1", "timeline2", data.appName) + }); + it("Should verify Timer", () => { + verifyComponentWithOutLabel("Timer", "timer1", "timer2", data.appName) + }); + + it("Should verify Tree Select", () => { + verifyComponentWithOutLabel("Tree Select", "treeselect1", "treeselect2", data.appName) + }); + + it("Should verify Vertical Divider", () => { + verifyComponentWithOutLabel("Vertical Divider", "verticaldivider1", "verticaldivider2", data.appName) + }); +}); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 595c4f6d05..7c6e034e52 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -106,11 +106,11 @@ Cypress.Commands.add("createApp", (appName) => { Cypress.Commands.add( "dragAndDropWidget", - (widgetName, positionX = 190, positionY = 80) => { + (widgetName, positionX = 190, positionY = 80, widgetName2=widgetName) => { const dataTransfer = new DataTransfer(); cy.clearAndType(commonSelectors.searchField, widgetName); - cy.get(commonWidgetSelector.widgetBox(widgetName)).trigger( + cy.get(commonWidgetSelector.widgetBox(widgetName2)).trigger( "dragstart", { dataTransfer }, { force: true } diff --git a/cypress-tests/cypress/support/utils/basicComponents.js b/cypress-tests/cypress/support/utils/basicComponents.js new file mode 100644 index 0000000000..4bfbbf28d2 --- /dev/null +++ b/cypress-tests/cypress/support/utils/basicComponents.js @@ -0,0 +1,45 @@ +import { commonWidgetSelector, commonSelectors } from "Selectors/common"; +import { + openAccordion, + verifyAndModifyParameter, + openEditorSidebar, + editAndVerifyWidgetName, +} from "Support/utils/commonWidget"; + +export const verifyComponent = (widgetName) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)).should("be.visible"); +}; + +export const deleteComponentAndVerify = (widgetName) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)).click(); + cy.get(`[data-cy="${widgetName}-delete-button"]`).last().click(); + cy.notVisible(commonWidgetSelector.draggableWidget(widgetName)); +}; + +export const verifyComponentWithOutLabel=(component, defaultName, fakeName, appName, properties=[] )=>{ + cy.dragAndDropWidget(component, 50, 50); + cy.get(`[data-cy="draggable-widget-${defaultName}"]`).click({ force: true }); + verifyComponent(defaultName); + + cy.resizeWidget(defaultName, 850, 600); + + openEditorSidebar(defaultName); + editAndVerifyWidgetName(fakeName, properties); + + cy.forceClickOnCanvas(); + cy.waitForAutoSave(); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + verifyComponent(fakeName); + + cy.go("back"); + deleteComponentAndVerify(fakeName); + cy.get(commonSelectors.editorPageLogo).click(); + + cy.deleteApp(appName); +} + + + + + diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index 1814bc88f2..0dab850244 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -82,8 +82,11 @@ export const addAndVerifyTooltip = (widgetSelector, message) => { verifyTooltip(widgetSelector, message); }; -export const editAndVerifyWidgetName = (name) => { - closeAccordions(["General", "Properties", "Layout"]); +export const editAndVerifyWidgetName = ( + name, + accordion = ["General", "Properties", "Layout"] +) => { + closeAccordions(accordion); cy.clearAndType(commonWidgetSelector.WidgetNameInputField, name); cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); From eaa5b6d4ae47adf987ac7b4d97c81cb537c3c3b3 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 24 Jan 2023 15:03:45 +0530 Subject: [PATCH 30/56] Add data-cy (#5411) --- frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx | 2 ++ frontend/src/Editor/Components/Map/Map.jsx | 2 ++ frontend/src/Editor/Components/Modal.jsx | 3 ++- frontend/src/Editor/Components/QrScanner/QrScanner.jsx | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx b/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx index 3428351f63..251c784c91 100644 --- a/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx +++ b/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx @@ -15,6 +15,7 @@ export const KanbanBoard = ({ containerProps, removeComponent, fireEvent, + dataCy, }) => { const { columns, cardData, enableAddCard } = properties; @@ -115,6 +116,7 @@ export const KanbanBoard = ({ style={{ display: visibility ? '' : 'none' }} data-disabled={disabledState} className={`kanban-container p-0 ${darkMode ? 'dark-themed' : ''}`} + data-cy={dataCy} >
+
{useDefaultButton && (