diff --git a/.github/workflows/cypress-marketplace.yml b/.github/workflows/cypress-marketplace.yml index 4d34523219..7193798ec4 100644 --- a/.github/workflows/cypress-marketplace.yml +++ b/.github/workflows/cypress-marketplace.yml @@ -14,9 +14,9 @@ jobs: Cypress-Marketplace: runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') || - contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') || + contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') strategy: matrix: @@ -159,13 +159,12 @@ jobs: "password": "password" }' - - name: Create Cypress environment file id: create-json uses: jsdaniell/create-json@1.1.2 with: name: "cypress.env.json" - json: ${{ secrets.CYPRESS_SECRETS }} + json: ${{ secrets.CYPRESS_SECRETS_MARKETPLACE }} dir: "./cypress-tests" - name: Marketplace @@ -185,8 +184,8 @@ jobs: Cypress-Marketplace-Subpath: runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath') steps: - name: Checkout diff --git a/.version b/.version index afad818663..92536a9e48 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.11.0 +3.12.0 diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 10c849d807..0acb76c866 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -167,6 +167,10 @@ Cypress.Commands.add("deleteApp", (appName) => { .click(); cy.get(commonSelectors.deleteAppOption).click(); cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + commonText.appDeletedToast + ); cy.wait("@appDeleted"); }); @@ -223,9 +227,9 @@ Cypress.Commands.add( .invoke("text") .then((text) => { cy.wrap(subject).realType(createBackspaceText(text)), - { - delay: 0, - }; + { + delay: 0, + }; }); } ); @@ -398,7 +402,7 @@ Cypress.Commands.add("defaultWorkspaceLogin", () => { // cy.intercept("GET", API_ENDPOINT).as("library_apps"); cy.visit("/my-workspace"); - cy.wait(2000) + cy.wait(2000); cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); // cy.wait("@library_apps"); }); @@ -428,7 +432,6 @@ Cypress.Commands.add( } ); - Cypress.Commands.add("releaseApp", () => { if (Cypress.env("environment") !== "Community") { cy.get(commonEeSelectors.promoteButton).click(); @@ -513,13 +516,58 @@ Cypress.Commands.overwrite( } ); +Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { + const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`; + + cy.visit(MARKETPLACE_URL); + cy.wait(1000); + + cy.get('[data-cy="-list-item"]').eq(0).click(); + cy.wait(1000); + + cy.get("body").then(($body) => { + if ($body.find(".plugins-card").length === 0) { + cy.log("No plugins found, proceeding to install..."); + installPlugin(pluginName); + } else { + cy.get(".plugins-card").then(($cards) => { + const isInstalled = $cards.toArray().some((card) => { + return ( + Cypress.$(card) + .find(".font-weight-medium.text-capitalize") + .text() + .trim() === pluginName + ); + }); + + if (isInstalled) { + cy.log(`${pluginName} is already installed. Skipping installation.`); + cy.get(commonSelectors.globalDataSourceIcon).click(); + } else { + installPlugin(pluginName); + cy.get(commonSelectors.globalDataSourceIcon).click(); + } + }); + } + }); + + function installPlugin(pluginName) { + cy.get('[data-cy="-list-item"]').eq(1).click(); + cy.wait(1000); + + cy.contains(".plugins-card", pluginName).within(() => { + cy.get(".marketplace-install").click(); + cy.wait(1000); + }); + } +}); + Cypress.Commands.add("verifyElement", (selector, text, eqValue) => { const element = eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector); element.should("be.visible").and("have.text", text); }); - Cypress.Commands.add("getAppId", (appName) => { cy.task("dbConnection", { dbconfig: Cypress.env("app_db"), @@ -529,3 +577,33 @@ Cypress.Commands.add("getAppId", (appName) => { return appId; }); }); + +Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => { + const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`; + + cy.visit(MARKETPLACE_URL); + cy.wait(1000); + + cy.get('[data-cy="-list-item"]').eq(0).click(); + cy.wait(1000); + + cy.get(".plugins-card").each(($card) => { + cy.wrap($card) + .find(".font-weight-medium.text-capitalize") + .invoke("text") + .then((text) => { + if (text.trim() === pluginName) { + cy.wrap($card).find(".link-primary").contains("Remove").click(); + cy.wait(1000); + + cy.get('[data-cy="delete-plugin-title"]').should("be.visible"); + cy.get('[data-cy="yes-button"]').click(); + cy.wait(2000); + + cy.log(`${pluginName} has been successfully uninstalled.`); + } else { + cy.log(`${pluginName} is not installed. Skipping uninstallation.`); + } + }); + }); +}); diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js index f7c9baf984..9db745b58d 100644 --- a/cypress-tests/cypress/constants/texts/postgreSql.js +++ b/cypress-tests/cypress/constants/texts/postgreSql.js @@ -4,8 +4,8 @@ export const postgreSqlText = { allDataSources: () => { return Cypress.env("marketplace_action") - ? "All data sources (44)" - : "All data sources (45)"; + ? "All data sources (45)" + : "All data sources (43)"; }, commonlyUsed: "Commonly used (5)", allDatabase: () => { diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js index 46f6339e11..51b65aeb60 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js @@ -66,7 +66,7 @@ describe("Add all Data sources to app", () => { cy.apiLogin(); }); - it("Should verify global data source page", () => { + it.skip("Should verify global data source page", () => { cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.visit(`${data.workspaceSlug}`); @@ -87,7 +87,7 @@ describe("Add all Data sources to app", () => { ); }); - it("Should add all data sources in data source page", () => { + it.skip("Should add all data sources in data source page", () => { cy.visit(`${data.workspaceSlug}`); dataSources.forEach((dsName) => { @@ -109,7 +109,7 @@ describe("Add all Data sources to app", () => { }); }); - it("Should add all data sources in the app", () => { + it.skip("Should add all data sources in the app", () => { cy.visit(`${data.workspaceSlug}`); cy.get(commonSelectors.dashboardIcon).click(); cy.get(commonSelectors.appCreateButton).click(); @@ -135,7 +135,7 @@ describe("Add all Data sources to app", () => { }); }); - it("Should install all makretplace plugins and add them into the app", () => { + it.skip("Should install all makretplace plugins and add them into the app", () => { cy.visit(`${data.workspaceSlug}`); const dataSourcesMarketplace = [ "Plivo", @@ -189,12 +189,15 @@ describe("Add all Data sources to app", () => { cy.wrap(dataSourcesMarketplace).each((dsName) => { cy.get(commonSelectors.globalDataSourceIcon).click(); selectAndAddDataSource("databases", dsName, dsName); - cy.wait(500); + cy.wait(1000); }); - cy.get(commonSelectors.dashboardIcon).click(); - cy.get(commonSelectors.appCreateButton).click(); - cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake1); + cy.get(commonSelectors.dashboardIcon).should("be.visible").click(); + cy.get(commonSelectors.appCreateButton).should("be.visible").click(); + cy.get(commonSelectors.appNameInput) + .should("be.visible") + .click() + .type(data.dsNamefake1); cy.get(commonSelectors.createAppButton).click(); cy.skipWalkthrough(); @@ -203,7 +206,7 @@ describe("Add all Data sources to app", () => { cy.get(".css-4e90k9").type( `cypress-${cyParamName(dsName)}-${cyParamName(dsName)}` ); - cy.wait(500); + cy.wait(1000); cy.contains( `[id*="react-select-"]`, @@ -212,7 +215,7 @@ describe("Add all Data sources to app", () => { .should("be.visible") .click(); - cy.wait(500); + cy.wait(1000); }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js index 1ae1290180..0733373ece 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js @@ -19,13 +19,14 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); -data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + +data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Airtable", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on connection AirTable form", () => { @@ -199,7 +200,7 @@ describe("Data source Airtable", () => { cy.get('[data-cy="show-ds-popover-button"]').click(); cy.get(".css-4e90k9").type(`${data.dsName}`); cy.contains(`[id*="react-select-"]`, data.dsName).click(); - cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName1); + cy.get('[data-cy="query-rename-input"]').clear().type(data.queryName); cy.get(airTableSelector.operationSelectDropdown) .click() @@ -225,7 +226,7 @@ describe("Data source Airtable", () => { cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - `Query (${data.dsName1}) completed.` + `Query (${data.queryName}) completed.` ); // Verify Delete record operation @@ -277,7 +278,7 @@ describe("Data source Airtable", () => { cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - `Query (${data.dsName1}) completed.` + `Query (${data.queryName}) completed.` ); deleteAppandDatasourceAfterExecution( data.dsName, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js similarity index 92% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js index 167ecdb2c5..f207f62058 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source amazon athena", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on amazon athena connection form", () => { + it.skip("Should verify elements on amazon athena connection form", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -97,7 +97,7 @@ describe("Data source amazon athena", () => { deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`); }); - it("Should verify the functionality of amazon athena connection form.", () => { + it.skip("Should verify the functionality of amazon athena connection form.", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -134,7 +134,7 @@ describe("Data source amazon athena", () => { deleteDatasource(`cypress-${data.dsName}-amazon-Athena`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -188,11 +188,13 @@ describe("Data source amazon athena", () => { cy.get(".css-4e90k9").type(`${data.dsName}`); cy.contains(`[id*="react-select-"]`, data.dsName).click(); cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName); - + cy.get(`[data-cy="list-query-${data.dsName}"]`).should("be.visible"); cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror( "SHOW DATABASES;" ); - + cy.get( + '[data-cy="query-input-field"] >>> .cm-editor >> .cm-content > .cm-line' + ).should("have.text", "SHOW DATABASES;"); cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js similarity index 95% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js index 674c694d28..9c3864c9fd 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source amazon ses", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on amazonses connection form", () => { + it.skip("Should verify elements on amazonses connection form", () => { const Accesskey = Cypress.env("amazonSes_accessKey"); const Secretkey = Cypress.env("amazonSes_secretKey"); @@ -80,7 +80,7 @@ describe("Data source amazon ses", () => { deleteDatasource(`cypress-${data.dsName}-Amazon-ses`); }); - it("Should verify the functionality of amazonses connection form.", () => { + it.skip("Should verify the functionality of amazonses connection form.", () => { selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); cy.get(".react-select__dropdown-indicator").eq(1).click(); @@ -112,7 +112,7 @@ describe("Data source amazon ses", () => { deleteDatasource(`cypress-${data.dsName}-amazon-ses`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const email = "adish" + "@" + "tooljet.com"; selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js index 8ed50754ad..27f52fd99d 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AppWrite", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on appwrite connection form", () => { + it.skip("Should verify elements on appwrite connection form", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); @@ -100,7 +100,7 @@ describe("Data source AppWrite", () => { deleteDatasource(`cypress-${data.dsName}-Appwrite`); }); - it("Should verify the functionality of appwrite connection form.", () => { + it.skip("Should verify the functionality of appwrite connection form.", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); @@ -150,7 +150,7 @@ describe("Data source AppWrite", () => { deleteDatasource(`cypress-${data.dsName}-Appwrite`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js similarity index 92% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js index f127beb854..17a3f0426c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AWS Lambda", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on AWS Lambda connection form", () => { + it.skip("Should verify elements on AWS Lambda connection form", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); @@ -80,9 +80,10 @@ describe("Data source AWS Lambda", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-lambda`); + cy.uninstallMarketplacePlugin("AWS Lambda"); }); - it("Should verify the functionality of AWS Lambda connection form", () => { + it.skip("Should verify the functionality of AWS Lambda connection form", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); @@ -113,9 +114,10 @@ describe("Data source AWS Lambda", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-lambda`); + cy.uninstallMarketplacePlugin("AWS Lambda"); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js similarity index 94% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js index 7af21cf467..1a01ecc757 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js @@ -21,15 +21,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AWS Textract", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on AWS Textract connection form", () => { + it.skip("Should verify elements on AWS Textract connection form", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); @@ -87,7 +87,7 @@ describe("Data source AWS Textract", () => { deleteDatasource(`cypress-${data.dsName}-aws-textract`); }); - it("Should verify functionality of AWS Textract connection form", () => { + it.skip("Should verify functionality of AWS Textract connection form", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); @@ -122,9 +122,10 @@ describe("Data source AWS Textract", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-textract`); + cy.uninstallMarketplacePlugin("AWS Textract"); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js index b148f92735..0c505cc417 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js @@ -17,8 +17,8 @@ data.customText = fake.randomSentence; describe("Data source Azure Blob Storage", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js similarity index 95% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js index 05c250d01c..4b03148e9e 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source baserow", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on baserow connection form", () => { + it.skip("Should verify elements on baserow connection form", () => { const Apikey = Cypress.env("baserow_apikey"); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -78,7 +78,7 @@ describe("Data source baserow", () => { deleteDatasource(`cypress-${data.dsName}-baserow`); }); - it("Should verify the functionality of baserow connection form.", () => { + it.skip("Should verify the functionality of baserow connection form.", () => { const Apikey = Cypress.env("baserow_apikey"); selectAndAddDataSource("databases", baseRowText.baserow, data.dsName); @@ -103,7 +103,7 @@ describe("Data source baserow", () => { deleteDatasource(`cypress-${data.dsName}-baserow`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const baserowTableID = Cypress.env("baserow_tableid"); const baserowRowID = Cypress.env("baserow_rowid"); const Apikey = Cypress.env("baserow_apikey"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js similarity index 98% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js index cee23b0807..24a39a3acc 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js @@ -16,8 +16,8 @@ const data = {}; describe("Data source BigQuery", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js index f221ed3c16..6ac8a3c8d1 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js index 53fab94f67..bb5923ec7b 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js index e16c6d5314..8e4a17d173 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js @@ -21,8 +21,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js index eb9a030963..5ff912d2d8 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data source DynamoDB", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js index c7a1f242fa..88627284ad 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js @@ -17,9 +17,12 @@ import { const data = {}; describe("Data source Elasticsearch", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); - data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + cy.apiLogin(); + cy.visit("/"); + + data.dataSourceName = fake.lastName + .toLowerCase() + .replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on Elasticsearch connection form", () => { @@ -123,14 +126,14 @@ describe("Data source Elasticsearch", () => { "have.text", elasticsearchText.errorConnectionRefused ); - deleteDatasource(`cypress-${data.lastName}-elasticsearch`); + deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`); }); it("Should verify the functionality of Elasticsearch connection form.", () => { selectAndAddDataSource( "databases", elasticsearchText.elasticSearch, - data.lastName + data.dataSourceName ); fillDataSourceTextField( @@ -210,12 +213,12 @@ describe("Data source Elasticsearch", () => { ); cy.get( - `[data-cy="cypress-${data.lastName}-elasticsearch-button"]` + `[data-cy="cypress-${data.dataSourceName}-elasticsearch-button"]` ).verifyVisibleElement( "have.text", - `cypress-${data.lastName}-elasticsearch` + `cypress-${data.dataSourceName}-elasticsearch` ); - deleteDatasource(`cypress-${data.lastName}-elasticsearch`); + deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js index 6e703fc895..674501b2db 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js @@ -17,8 +17,8 @@ const data = {}; describe("Data source Firestore", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js index 008121b863..7ad8e5484b 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js @@ -19,12 +19,12 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source GraphQL", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on GraphQL connection form", () => { diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js index 9ec327cf71..12ad23efd9 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js @@ -20,13 +20,13 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source HarperDB", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on HarperDB connection form", () => { @@ -102,6 +102,7 @@ describe("Data source HarperDB", () => { ); deleteDatasource(`cypress-${data.dsName}-HarperDB`); + cy.uninstallMarketplacePlugin("HarperDB"); }); it("Should verify functionality of HarperDB connection form", () => { @@ -156,9 +157,10 @@ describe("Data source HarperDB", () => { ); deleteDatasource(`cypress-${data.dsName}-HarperDB`); + cy.uninstallMarketplacePlugin("HarperDB"); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("harperdb_host"); const Port = Cypress.env("harperdb_port"); const Username = Cypress.env("harperdb_username"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js index 36b39572d4..24dc92359c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js @@ -23,8 +23,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js index 4c6c57d596..58c8c30705 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js index 7ea22b8693..6572724e3d 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js @@ -20,12 +20,12 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source minio", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on minio connection form", () => { @@ -157,7 +157,7 @@ describe("Data source minio", () => { deleteDatasource(`cypress-${data.dsName}-minio`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("minio_host"); const Port = Cypress.env("minio_port"); const AccessKey = Cypress.env("minio_accesskey"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js index 77d2e2ffa4..5729ea18c8 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js @@ -27,8 +27,8 @@ const data = {}; describe("Data source MongoDB", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); @@ -133,7 +133,7 @@ describe("Data source MongoDB", () => { "have.text", mongoDbText.errorConnectionRefused ); - cy.get('[data-cy="query-select-dropdown"]').type( + cy.get('[data-cy="connection-type-select-dropdown"]').type( mongoDbText.optionConnectUsingConnectionString ); cy.get('[data-cy="label-connection-string"]').verifyVisibleElement( diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js index 38e221ba0a..d54e15c933 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js @@ -26,8 +26,8 @@ const data = {}; describe("Data sources MySql", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js index e7ae4f296b..0cd7ea4223 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js @@ -15,7 +15,7 @@ import { describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); // cy.createApp(); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js index a6ff7595e5..2da4902edd 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js @@ -20,14 +20,14 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on connection form", () => { + it.skip("Should verify elements on connection form", () => { cy.log(process.env.NODE_ENV); cy.log(postgreSqlText.allDatabase()); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -140,7 +140,7 @@ describe("Data sources", () => { deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); }); - it("Should verify the functionality of PostgreSQL connection form.", () => { + it.skip("Should verify the functionality of PostgreSQL connection form.", () => { selectAndAddDataSource( "databases", postgreSqlText.postgreSQL, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js index 8217a9db60..ddceea1b52 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js @@ -23,7 +23,7 @@ data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Redis", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); }); it("Should verify elements on connection Redis form", () => { @@ -215,7 +215,7 @@ describe("Data source Redis", () => { deleteDatasource(`cypress-${data.dsName}-redis`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { selectAndAddDataSource("databases", redisText.redis, data.dsName); fillDataSourceTextField( diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js index 0d45f8af89..825439e687 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js @@ -20,7 +20,7 @@ const clientAuthenticationDropdown = describe("Data source Rest API", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); @@ -332,7 +332,6 @@ describe("Data source Rest API", () => { deleteDatasource(`cypress-${data.dataSourceName}-restapi`); }); it("Should verify basic connection for Rest API", () => { - const storedId = Cypress.env("storedId"); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, `cypress-${data.dataSourceName}-restapi`, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js index 267eedea1f..abdde2ba00 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js index 12b3817f16..73ccc703c3 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js @@ -20,7 +20,7 @@ const data = {}; describe("Data sources AWS S3", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js index c240286274..25ec2e4a9c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js @@ -15,7 +15,7 @@ import { describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); // cy.createApp(); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js index 4e824aeda5..506fe5d660 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js @@ -13,8 +13,8 @@ const data = {}; describe("Data source SMTP", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js index 4409c0577b..7c1fb5b588 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js @@ -20,8 +20,8 @@ import { const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js index 9501fdabbb..954fb659b5 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js @@ -21,8 +21,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js similarity index 94% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js index ee2fa9a6e3..4ac2575266 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js @@ -21,15 +21,15 @@ import { dataSourceSelector } from "../../../../../constants/selectors/dataSourc import { pluginSelectors } from "Selectors/plugins"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Twilio", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on Twilio connection form", () => { + it.skip("Should verify elements on Twilio connection form", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); @@ -89,7 +89,7 @@ describe("Data source Twilio", () => { deleteDatasource(`cypress-${data.dsName}-twilio`); }); - it("Should verify functionality of Twilio connection form", () => { + it.skip("Should verify functionality of Twilio connection form", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); @@ -128,7 +128,7 @@ describe("Data source Twilio", () => { deleteDatasource(`cypress-${data.dsName}-twilio`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js index ff15053f09..4b4fe20e62 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js @@ -20,7 +20,7 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/support/utils/dashboard.js b/cypress-tests/cypress/support/utils/dashboard.js index 0809909297..dbc090735b 100644 --- a/cypress-tests/cypress/support/utils/dashboard.js +++ b/cypress-tests/cypress/support/utils/dashboard.js @@ -53,6 +53,8 @@ export const modifyAndVerifyAppCardIcon = (appName) => { }; export const verifyAppDelete = (appName) => { + cy.get("body").should("exist").and("be.visible"); + cy.get('[data-cy="dashboard-section-header"]').should("be.visible"); cy.get("body").then(($title) => { if (!$title.text().includes(commonText.introductionMessage)) { cy.clearAndType(commonSelectors.homePageSearchBar, appName); diff --git a/cypress-tests/cypress/support/utils/restAPI.js b/cypress-tests/cypress/support/utils/restAPI.js index f5a44e3fd5..2dfc225a65 100644 --- a/cypress-tests/cypress/support/utils/restAPI.js +++ b/cypress-tests/cypress/support/utils/restAPI.js @@ -18,79 +18,97 @@ export const createAndRunRestAPIQuery = ( method: "GET", url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, headers, - }).then((response) => { - const editingVersionId = response.body.editing_version.id; - const data_source_id = Cypress.env(`${dsName}-id`); - const useJsonBody = - ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) && - jsonBody !== null; - - const queryOptions = { - method: method.toLowerCase(), - url: url + urlSuffix, - url_params: [["", ""]], - headers: headersList.length ? headersList : [["", ""]], - body: !useJsonBody && bodyList.length ? bodyList : [["", ""]], - json_body: useJsonBody ? jsonBody : null, - body_toggle: useJsonBody, - runOnPageLoad: run, - transformationLanguage: "javascript", - enableTransformation: false, - }; - - const requestBody = { - app_id: Cypress.env("appId"), - app_version_id: editingVersionId, - name: queryName, - kind: "restapi", - options: queryOptions, - data_source_id, - plugin_id: null, - }; + }).then((appResponse) => { + const currentEnvironmentId = appResponse.body.editorEnvironment.id; + const editingVersionId = appResponse.body.editing_version.id; cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, + method: "GET", + url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${currentEnvironmentId}/versions/${editingVersionId}`, headers, - body: requestBody, - }).then((createResponse) => { - expect(createResponse.status).to.equal(201); - const queryId = createResponse.body.id; - cy.log("Query created successfully:", queryId); + }).then((dsResponse) => { + expect(dsResponse.status).to.eq(200); - const createdOptions = createResponse.body.options; - expect(createdOptions.method).to.equal(queryOptions.method); - expect(createdOptions.url).to.equal(queryOptions.url); - expect(createdOptions.headers).to.deep.equal(queryOptions.headers); + const dataSource = dsResponse.body.data_sources.find( + (ds) => ds.name === dsName + ); - if (useJsonBody) { - expect(createdOptions.json_body).to.deep.equal( - queryOptions.json_body - ); - expect(createdOptions.body_toggle).to.equal(true); - } else { - expect(createdOptions.body).to.deep.equal(queryOptions.body); - expect(createdOptions.body_toggle).to.equal(false); + if (!dataSource) { + throw new Error(`Data source '${dsName}' not found.`); } - expect(createdOptions.runOnPageLoad).to.equal(run); - cy.log("Metadata verified successfully"); - if (run) { - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`, - headers, - }).then((runResponse) => { - expect([200, 201]).to.include(runResponse.status); - cy.log("Query executed successfully:", runResponse.body); - if (runResponse.body?.data.id) { - cy.writeFile("cypress/fixtures/restAPI/storedId.json", { - id: runResponse.body.data.id, - }); - cy.log("Stored ID:", runResponse.body.data.id); - } - }); - } + const data_source_id = dataSource.id; + const useJsonBody = + ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) && + jsonBody !== null; + + const queryOptions = { + method: method.toLowerCase(), + url: url + urlSuffix, + url_params: [["", ""]], + headers: headersList.length ? headersList : [["", ""]], + body: !useJsonBody && bodyList.length ? bodyList : [["", ""]], + json_body: useJsonBody ? jsonBody : null, + body_toggle: useJsonBody, + runOnPageLoad: run, + transformationLanguage: "javascript", + enableTransformation: false, + }; + + const requestBody = { + app_id: Cypress.env("appId"), + app_version_id: editingVersionId, + name: queryName, + kind: "restapi", + options: queryOptions, + data_source_id, + plugin_id: null, + }; + + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, + headers, + body: requestBody, + }).then((createResponse) => { + expect(createResponse.status).to.equal(201); + const queryId = createResponse.body.id; + cy.log("Query created successfully:", queryId); + + const createdOptions = createResponse.body.options; + expect(createdOptions.method).to.equal(queryOptions.method); + expect(createdOptions.url).to.equal(queryOptions.url); + expect(createdOptions.headers).to.deep.equal(queryOptions.headers); + + if (useJsonBody) { + expect(createdOptions.json_body).to.deep.equal( + queryOptions.json_body + ); + expect(createdOptions.body_toggle).to.equal(true); + } else { + expect(createdOptions.body).to.deep.equal(queryOptions.body); + expect(createdOptions.body_toggle).to.equal(false); + } + + expect(createdOptions.runOnPageLoad).to.equal(run); + cy.log("Metadata verified successfully"); + if (run) { + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`, + headers, + }).then((runResponse) => { + expect([200, 201]).to.include(runResponse.status); + cy.log("Query executed successfully:", runResponse.body); + if (runResponse.body?.data.id) { + cy.writeFile("cypress/fixtures/restAPI/storedId.json", { + id: runResponse.body.data.id, + }); + cy.log("Stored ID:", runResponse.body.data.id); + } + }); + } + }); }); }); }); diff --git a/frontend/.version b/frontend/.version index afad818663..92536a9e48 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.11.0 +3.12.0 diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx index d1a9dfa3aa..3a74038367 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx @@ -53,7 +53,11 @@ export const BulkUploadPrimaryKey = () => {
{ diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUpsertPrimaryKey.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUpsertPrimaryKey.jsx new file mode 100644 index 0000000000..07ddc48742 --- /dev/null +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUpsertPrimaryKey.jsx @@ -0,0 +1,78 @@ +import React, { useContext, useEffect } from 'react'; +import { TooljetDatabaseContext } from '@/TooljetDatabase/index'; +import { resolveReferences } from '@/AppBuilder/CodeEditor/utils'; +import CodeHinter from '@/AppBuilder/CodeEditor'; + +export const BulkUpsertPrimaryKey = () => { + const { + columns, + bulkUpsertPrimaryKey, + handleBulkUpsertRowsOptionChanged, + handlePrimaryKeyOptionChangedForBulkUpsert, + } = useContext(TooljetDatabaseContext); + + useEffect(() => { + const primaryKeys = columns.reduce((acc, column) => { + if (column?.keytype === 'PRIMARY KEY' || column?.isPrimaryKey) { + acc.push(column?.accessor); + } + return acc; + }, []); + + if (primaryKeys.length > 0) { + handlePrimaryKeyOptionChangedForBulkUpsert(primaryKeys); + } + }, [columns]); + + const handleRowsChange = (value) => { + handleBulkUpsertRowsOptionChanged(value); + }; + + return ( +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ ); +}; + +export default BulkUpsertPrimaryKey; diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx index 89323c1c3c..e273e2ace8 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx @@ -1,5 +1,4 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; -import { resolveReferences } from '@/Editor/CodeEditor/utils'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import Trash from '@/_ui/Icon/solidIcons/Trash'; import React from 'react'; @@ -43,8 +42,7 @@ const RenderColumnUI = ({ placeholder="key" onChange={(newValue) => { if (isJSonTypeColumn) { - const [_, __, resolvedValue] = resolveReferences(`{{${newValue}}}`); - handleValueChange(resolvedValue); + handleValueChange(newValue); } else { handleValueChange(newValue); } diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx index 208f4df816..0ea9538017 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx @@ -220,7 +220,9 @@ function DataSourceSelect({ if (isFirstPageLoaded && offset >= totalRecords) return; if (foreignKeys.length < 1) return; setIsLoadingFKDetails(true); - const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName); + const referencedColumns = Array.isArray(foreignKeys) + ? foreignKeys.find((item) => item.column_names[0] === cellColumnName) + : undefined; if (!referencedColumns?.referenced_column_names?.length) return; const selectQuery = new PostgrestQueryBuilder(); @@ -709,7 +711,8 @@ const MenuList = ({ ...props }) => { const menuListStyles = getStyles('menuList', props); - const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName); + const referencedColumnDetails = + Array.isArray(foreignKeys) && foreignKeys?.find((item) => item?.column_names[0] === cellColumnName); const handleNavigateToReferencedTable = () => { const data = { diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx index e873228888..f25e72cb59 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx @@ -16,6 +16,7 @@ import { getPrivateRoute } from '@/_helpers/routes'; import { useNavigate } from 'react-router-dom'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { BulkUploadPrimaryKey } from './BulkUploadPrimaryKey'; +import BulkUpsertPrimaryKey from './BulkUpsertPrimaryKey'; import './styles.scss'; import CodeHinter from '@/AppBuilder/CodeEditor'; @@ -46,6 +47,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay const [tableForeignKeyInfo, setTableForeignKeyInfo] = useState({}); const [bulkUpdatePrimaryKey, setBulkUpdatePrimaryKey] = useState(() => options['bulk_update_with_primary_key'] || {}); + const [bulkUpsertPrimaryKey, setBulkUpsertPrimaryKey] = useState(() => options['bulk_upsert_with_primary_key'] || {}); const joinOptions = options['join_table']?.['joins'] || [ { conditions: { conditionsList: [{ leftField: { table: selectedTableId } }] } }, @@ -196,6 +198,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay // eslint-disable-next-line react-hooks/exhaustive-deps }, [bulkUpdatePrimaryKey]); + useEffect(() => { + mounted && optionchanged('bulk_upsert_with_primary_key', bulkUpsertPrimaryKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bulkUpsertPrimaryKey]); + useEffect(() => { mounted && optionchanged('update_rows', updateRowsOptions); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -234,10 +241,18 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay setBulkUpdatePrimaryKey((prev) => ({ ...prev, rows_update: value })); }; + const handleBulkUpsertRowsOptionChanged = (value) => { + setBulkUpsertPrimaryKey((prev) => ({ ...prev, rows: value })); + }; + const handlePrimaryKeyOptionChangedForBulkUpdate = (value) => { setBulkUpdatePrimaryKey((prev) => ({ ...prev, primary_key: value })); }; + const handlePrimaryKeyOptionChangedForBulkUpsert = (value) => { + setBulkUpsertPrimaryKey((prev) => ({ ...prev, primary_key: value })); + }; + const loadTableInformation = async (tableId, isNewTableAdded) => { const tableDetails = findTableDetails(tableId); if (tableDetails?.table_name && !tableInfo[tableDetails?.table_name]) { @@ -340,8 +355,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay tableForeignKeyInfo, setTableForeignKeyInfo, bulkUpdatePrimaryKey, + bulkUpsertPrimaryKey, handleBulkUpdateWithPrimaryKeysRowsUpdateOptionChanged, + handleBulkUpsertRowsOptionChanged, handlePrimaryKeyOptionChangedForBulkUpdate, + handlePrimaryKeyOptionChangedForBulkUpsert, }), [ organizationId, @@ -357,6 +375,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay joinOrderByOptions, selectedTableId, bulkUpdatePrimaryKey, + bulkUpsertPrimaryKey, ] ); @@ -517,6 +536,8 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay return JoinTable; case 'bulk_update_with_primary_key': return BulkUploadPrimaryKey; + case 'bulk_upsert_with_primary_key': + return BulkUpsertPrimaryKey; } }; @@ -527,6 +548,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay { label: 'Delete rows', value: 'delete_rows' }, { label: 'Join tables', value: 'join_tables' }, { label: 'Bulk update with primary key', value: 'bulk_update_with_primary_key' }, + { label: 'Bulk upsert with primary key', value: 'bulk_upsert_with_primary_key' }, ]; const ComponentToRender = getComponent(operation); diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 5ca9429693..ddb67a5445 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -425,6 +425,7 @@ export const createQueryPanelSlice = (set, get) => ({ isLoading: false, ...(query.kind === 'restapi' ? { + metadata: data.metadata, request: data.data.requestObject, response: data.data.responseObject, responseHeaders: data.data.responseHeaders, diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx index 92772ffea1..d480f2c698 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx @@ -220,7 +220,9 @@ function DataSourceSelect({ if (isFirstPageLoaded && offset >= totalRecords) return; if (foreignKeys.length < 1) return; setIsLoadingFKDetails(true); - const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName); + const referencedColumns = Array.isArray(foreignKeys) + ? foreignKeys.find((item) => item.column_names[0] === cellColumnName) + : undefined; if (!referencedColumns?.referenced_column_names?.length) return; const selectQuery = new PostgrestQueryBuilder(); @@ -715,7 +717,8 @@ const MenuList = ({ ...props }) => { const menuListStyles = getStyles('menuList', props); - const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName); + const referencedColumnDetails = + Array.isArray(foreignKeys) && foreignKeys.find((item) => item?.column_names[0] === cellColumnName); const handleNavigateToReferencedTable = () => { const data = { diff --git a/frontend/src/MarketplacePage/InstalledPlugins.jsx b/frontend/src/MarketplacePage/InstalledPlugins.jsx index 260b16c189..d0da8512a5 100644 --- a/frontend/src/MarketplacePage/InstalledPlugins.jsx +++ b/frontend/src/MarketplacePage/InstalledPlugins.jsx @@ -68,7 +68,7 @@ export const InstalledPlugins = () => { })} {!fetching && installedPlugins?.length === 0 && (
-

No results found

+

No plugins added. Please add a plugin from the Marketplace.

)}
diff --git a/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx b/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx index 41cedf6bc4..d847f4238c 100644 --- a/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx @@ -342,8 +342,39 @@ const ColumnForm = ({ )}
-
- Default value +
+
+ Default value +
+ {foreignKeyDetails?.length > 0 && isForeignKey && ( + 0 && isForeignKey} + > +
+ Set default value to Null + +
+
+ )}
-
+
{isTimestamp ? ( ) : ( - - - No data found -
- } - loader={ - <> - - - - - } - isLoading={true} - value={foreignKeyDefaultValue} - foreignKeyAccessInRowForm={true} - disabled={dataType === 'serial'} - topPlaceHolder={dataType === 'serial' ? 'Auto-generated' : 'Enter a value'} - onChange={(value) => { - setForeignKeyDefaultValue(value); - setDefaultValue(value?.value); - }} - onAdd={true} - addBtnLabel={'Open referenced table'} - foreignKeys={foreignKeyDetails} - setReferencedColumnDetails={setReferencedColumnDetails} - scrollEventForColumnValues={true} - cellColumnName={columnName} - columnDataType={dataType?.value} - isCreateColumn={true} - /> + <> + + + No data found +
+ } + loader={ + <> + + + + + } + isLoading={true} + value={foreignKeyDefaultValue} + foreignKeyAccessInRowForm={true} + disabled={dataType === 'serial'} + topPlaceHolder={ + dataType === 'serial' + ? 'Auto-generated' + : foreignKeyDefaultValue?.value === null + ? 'Null' + : 'Enter a value' + } + onChange={(value) => { + setForeignKeyDefaultValue(value); + setDefaultValue(value?.value); + }} + onAdd={true} + addBtnLabel={'Open referenced table'} + foreignKeys={foreignKeyDetails} + setReferencedColumnDetails={setReferencedColumnDetails} + scrollEventForColumnValues={true} + cellColumnName={columnName} + columnDataType={dataType?.value} + isCreateColumn={true} + /> + {defaultValue === null &&

Null

} + )}
- {isNotNull === true && dataType?.value !== 'serial' && rows.length > 0 && defaultValue.length <= 0 ? ( + {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? ( Default value is required to populate this field in existing rows as NOT NULL constraint is added @@ -546,6 +596,10 @@ const ColumnForm = ({ checked={isNotNull} onChange={(e) => { setIsNotNull(e.target.checked); + if (e.target.checked && defaultValue === null) { + setForeignKeyDefaultValue({ label: '', value: '' }); + setDefaultValue(''); + } }} disabled={dataType?.value === 'serial'} /> @@ -602,7 +656,7 @@ const ColumnForm = ({ shouldDisableCreateBtn={ isEmpty(columnName) || isEmpty(dataType) || - (isNotNull === true && rows.length > 0 && isEmpty(defaultValue) && dataType?.value !== 'serial') || + (dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue)) || disabledSaveButton } showToolTipForFkOnReadDocsSection={true} diff --git a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx index 3943964a16..071faf5e3e 100644 --- a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx @@ -31,6 +31,8 @@ import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/D import { getLocalTimeZone, timeZonesWithOffsets } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util'; import CodeHinter from '@/AppBuilder/CodeEditor'; import { resolveReferences } from '@/AppBuilder/CodeEditor/utils'; +import Switch from '@/AppBuilder/CodeBuilder/Elements/Switch'; +import PostgrestQueryBuilder from '@/_helpers/postgrestQueryBuilder'; const ColumnForm = ({ onClose, @@ -102,6 +104,68 @@ const ColumnForm = ({ const [foreignKeyDetails, setForeignKeyDetails] = useState([]); + // Add function to validate default value + const validateDefaultValue = async () => { + if (!isMatchingForeignKeyColumn(selectedColumn?.Header)) return; + + try { + const referencedColumns = foreignKeys.find((item) => item.column_names[0] === selectedColumn?.Header); + + if (!referencedColumns?.referenced_column_names?.length) { + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + return; + } + + const selectQuery = new PostgrestQueryBuilder(); + selectQuery.select(referencedColumns.referenced_column_names[0]); + selectQuery.eq(referencedColumns.referenced_column_names[0], defaultValue); + + const query = selectQuery.url.toString(); + + const { data = [], error } = await tooljetDatabaseService.findOne( + organizationId, + referencedColumns.referenced_table_id, + query + ); + + if (error) { + toast.error(error?.message ?? `Failed to validate default value`); + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + return; + } + + if (data.length === 0) { + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + } + } catch (error) { + console.error('Error validating default value:', error); + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + } + }; + + // Add useEffect to validate on mount + useEffect(() => { + if (isMatchingForeignKeyColumn(selectedColumn?.Header) && defaultValue) { + validateDefaultValue(); + } + }, []); + useEffect(() => { toast.dismiss(); setForeignKeyDetails( @@ -471,6 +535,11 @@ const ColumnForm = ({ setDisabledSaveButton(columnName === ''); }, [columnName]); + useEffect(() => { + const shouldDisableForNullValue = dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue); + setDisabledSaveButton(shouldDisableForNullValue); + }, [isNotNull, defaultValue, dataType]); + const handleInputError = (bool = false) => { setDisabledSaveButton(bool); }; @@ -621,16 +690,52 @@ const ColumnForm = ({
)}
-
- Default value +
+
+ Default value +
+ {isMatchingForeignKeyColumn(selectedColumn?.Header) && ( + +
+ Set default value to Null + +
+
+ )}
+ -
+
{isTimestamp ? ( ) : ( - - - No data found -
- } - loader={ - <> - - - - - } - isLoading={true} - value={foreignKeyDefaultValue} - foreignKeyAccessInRowForm={true} - disabled={ - selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true - } - topPlaceHolder={selectedColumn?.dataType === 'serial' ? 'Auto-generated' : 'Enter a value'} - onChange={(value) => { - setForeignKeyDefaultValue(value); - setDefaultValue(value?.value); - }} - onAdd={true} - addBtnLabel={'Open referenced table'} - foreignKeys={foreignKeys} - setReferencedColumnDetails={setReferencedColumnDetails} - scrollEventForColumnValues={true} - cellColumnName={selectedColumn?.Header} - columnDataType={dataType} - isEditColumn={true} - /> + <> + + + No data found +
+ } + loader={ + <> + + + + + } + isLoading={true} + value={foreignKeyDefaultValue} + foreignKeyAccessInRowForm={true} + disabled={ + selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true + } + topPlaceHolder={ + selectedColumn?.dataType === 'serial' + ? 'Auto-generated' + : foreignKeyDefaultValue?.value === null || defaultValue === null + ? 'Null' + : 'Enter a value' + } + onChange={(value) => { + setForeignKeyDefaultValue(value); + setDefaultValue(value?.value); + }} + onAdd={true} + addBtnLabel={'Open referenced table'} + foreignKeys={foreignKeys} + setReferencedColumnDetails={setReferencedColumnDetails} + scrollEventForColumnValues={true} + cellColumnName={selectedColumn?.Header} + columnDataType={dataType} + isEditColumn={true} + /> + {defaultValue === null &&

Null

} + )}
+ {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? ( + + Default value is required to populate this field in existing rows as NOT NULL constraint is added + + ) : null} {isNotNull === true && selectedColumn?.dataType !== 'serial' && rows.length > 0 && @@ -866,6 +990,10 @@ const ColumnForm = ({ checked={isNotNull} onChange={(e) => { setIsNotNull(e.target.checked); + if (e.target.checked && defaultValue === null) { + setForeignKeyDefaultValue({ label: '', value: '' }); + setDefaultValue(''); + } }} disabled={selectedColumn?.dataType === 'serial' || selectedColumn?.constraints_type?.is_primary_key} /> diff --git a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx index 5451cc353f..98ff603242 100644 --- a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx @@ -177,7 +177,9 @@ const EditRowForm = ({ } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/TooljetDatabase/Forms/RowForm.jsx b/frontend/src/TooljetDatabase/Forms/RowForm.jsx index 8fcc9ef152..58dd3d1000 100644 --- a/frontend/src/TooljetDatabase/Forms/RowForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/RowForm.jsx @@ -151,7 +151,9 @@ const RowForm = ({ } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/TooljetDatabase/Table/index.jsx b/frontend/src/TooljetDatabase/Table/index.jsx index 5d98ce70fe..3a1cafa460 100644 --- a/frontend/src/TooljetDatabase/Table/index.jsx +++ b/frontend/src/TooljetDatabase/Table/index.jsx @@ -958,7 +958,9 @@ const Table = ({ collapseSidebar }) => { } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/_components/DynamicFormV2.jsx b/frontend/src/_components/DynamicFormV2.jsx index 1a6269976f..bada082e16 100644 --- a/frontend/src/_components/DynamicFormV2.jsx +++ b/frontend/src/_components/DynamicFormV2.jsx @@ -30,6 +30,8 @@ const DynamicFormV2 = ({ validationMessages, setValidationMessages, clearValidationMessages, + showValidationErrors, + clearValidationErrorBanner, }) => { const uiProperties = schema['tj:ui:properties'] || {}; const dsm = React.useMemo(() => new DataSourceSchemaManager(schema), [schema]); @@ -89,18 +91,97 @@ const DynamicFormV2 = ({ React.useEffect(() => { if (!hasUserInteracted) return; - const { valid, errors } = dsm.validateData(options); - if (valid) { - clearValidationMessages(); - } else { - setValidationMessages(errors, schema); - const requiredFields = errors - .filter((error) => error.keyword === 'required') - .map((error) => error.params.missingProperty); - setConditionallyRequiredProperties(requiredFields); + const timeout = setTimeout(() => { + validateOptions(); + }, 300); + + return () => clearTimeout(timeout); + }, [options, hasUserInteracted, validateOptions]); + + const validateOptions = React.useCallback(async () => { + try { + const { valid, errors } = await dsm.validateData(options); + + const conditionallyRequiredFields = processAllOfConditions(schema, options); + setConditionallyRequiredProperties(conditionallyRequiredFields); + + if (valid) { + clearValidationMessages(); + clearValidationErrorBanner(); + } else { + setValidationMessages(errors, schema, interactedFields); + } + } catch (error) { + console.error('Validation error:', error); } - }, [options]); + }, [ + dsm, + options, + processAllOfConditions, + schema, + clearValidationMessages, + clearValidationErrorBanner, + setValidationMessages, + interactedFields, + ]); + + const processAllOfConditions = React.useCallback((schema, options, path = []) => { + let requiredFields = []; + + if (schema.allOf) { + schema.allOf.forEach((condition) => { + if (condition.if && condition.then) { + const conditionMatches = Object.entries(condition.if.properties || {}).every(([propName, propCondition]) => { + const propertyPath = [...path, propName]; + + let currentValue = options; + for (const segment of propertyPath) { + if (!currentValue || typeof currentValue !== 'object') { + return false; + } + currentValue = currentValue[segment]?.value; + } + + return propCondition.const === currentValue; + }); + + if (conditionMatches) { + if (condition.then.required) { + requiredFields = [...requiredFields, ...condition.then.required]; + } + + if (condition.then.allOf) { + const nestedRequired = processAllOfConditions({ allOf: condition.then.allOf }, options, path); + requiredFields = [...requiredFields, ...nestedRequired]; + } + + if (condition.then.properties) { + Object.entries(condition.then.properties).forEach(([propName, propSchema]) => { + if (propSchema.allOf) { + const nestedRequired = processAllOfConditions({ allOf: propSchema.allOf }, options, [ + ...path, + propName, + ]); + requiredFields = [...requiredFields, ...nestedRequired]; + } + }); + } + } + } + }); + } + + return requiredFields; + }, []); + + React.useEffect(() => { + if (showValidationErrors) { + setHasUserInteracted(true); + const allFieldKeys = Object.keys(options); + setInteractedFields(new Set(allFieldKeys)); + } + }, [showValidationErrors, options]); React.useEffect(() => { const prevDataSourceId = prevDataSourceIdRef.current; @@ -189,6 +270,7 @@ const DynamicFormV2 = ({ return Input; case 'password-v3': case 'text-v3': + case 'password-v3-textarea': return InputV3; case 'textarea': return Textarea; @@ -210,8 +292,10 @@ const DynamicFormV2 = ({ const isRequired = required || conditionallyRequiredProperties.includes(key); const isEncrypted = widget === 'password-v3' || encryptedProperties.includes(key); const currentValue = options?.[key]?.value; + const skipValidation = + (!hasUserInteracted && !showValidationErrors) || (!interactedFields.has(key) && !showValidationErrors); - const handleOptionChange = (key, value, flag) => { + const handleOptionChange = (key, value, flag = true) => { if (!hasUserInteracted) { setHasUserInteracted(true); } @@ -243,6 +327,7 @@ const DynamicFormV2 = ({ }; } case 'password-v3': + case 'password-v3-textarea': case 'text-v3': { return { key, @@ -262,14 +347,13 @@ const DynamicFormV2 = ({ encrypted: isEncrypted, onBlur, isRequired: isRequired, - isValidatedMessages: - !hasUserInteracted || !interactedFields.has(key) - ? { valid: null, message: '' } // skip validation for initial render and untouched elements - : validationMessages[key] - ? { valid: false, message: validationMessages[key] } - : isRequired && !isEncrypted - ? { valid: true, message: '' } - : { valid: null, message: '' }, // handle optional && encrypted fields + isValidatedMessages: skipValidation + ? { valid: null, message: '' } // skip validation for initial render and untouched elements + : validationMessages[key] + ? { valid: false, message: validationMessages[key] } + : isRequired + ? { valid: true, message: '' } + : { valid: null, message: '' }, // handle optional && encrypted fields isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(), }; } @@ -285,7 +369,7 @@ const DynamicFormV2 = ({ options: isRenderedAsQueryEditor ? options?.[key] ?? schema?.defaults?.[key] : options?.[key]?.value ?? schema?.defaults?.[key]?.value, - optionchanged, + handleOptionChange, isRenderedAsQueryEditor, workspaceConstants: currentOrgEnvironmentConstants, isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(), @@ -298,14 +382,14 @@ const DynamicFormV2 = ({ return { defaultChecked: currentValue, checked: currentValue, - onChange: (e) => optionchanged(key, e.target.checked), + onChange: (e) => handleOptionChange(key, e.target.checked, true), }; case 'dropdown': case 'dropdown-component-flip': return { options: list, value: options?.[key]?.value || options?.[key], - onChange: (value) => optionchanged(key, value), + onChange: (value) => handleOptionChange(key, value, true), width: width || '100%', encrypted: options?.[key]?.encrypted, }; @@ -404,6 +488,7 @@ const DynamicFormV2 = ({ {label && widget !== 'text-v3' && widget !== 'password-v3' && + widget !== 'password-v3-textarea' && renderLabel(label, uiProperties[key].tooltip)}
)} diff --git a/frontend/src/_helpers/dataSourceSchemaManager.js b/frontend/src/_helpers/dataSourceSchemaManager.js index abf2f3c9bb..6ce4f6f9a7 100644 --- a/frontend/src/_helpers/dataSourceSchemaManager.js +++ b/frontend/src/_helpers/dataSourceSchemaManager.js @@ -1,3 +1,4 @@ +import { datasourceService } from '@/_services'; import Ajv2020 from 'ajv'; const ajvOptions = { @@ -18,13 +19,18 @@ export default class DataSourceSchemaManager { this.validate = this.ajv.compile(this.schema); } - validateData(options) { - const data = this._convertDataSourceOptionsToData(options); - const valid = this.validate(data); - if (!valid) { - return { valid: false, errors: this.validate.errors }; + async validateData(options) { + const decryptedOptions = await datasourceService.getDecryptedOptions(options); + const data = this._convertDataSourceOptionsToData(decryptedOptions); + try { + const valid = this.validate(data); + if (!valid) { + return { valid: false, errors: this.validate.errors }; + } + return { valid: true, errors: [] }; + } catch (error) { + console.log('Validtion error: ', error); } - return { valid: true, errors: [] }; } getDefaults(options = {}) { @@ -95,10 +101,6 @@ export default class DataSourceSchemaManager { result[key] = value; } - // Add a dummy value to pass validation for encrypted keys - if (this.getEncryptedProperties().includes(key)) { - result[key] = 'REDACTED'; - } return result; }, {}); } diff --git a/frontend/src/_services/datasource.service.js b/frontend/src/_services/datasource.service.js index ef1a878db7..9ea24e99d0 100644 --- a/frontend/src/_services/datasource.service.js +++ b/frontend/src/_services/datasource.service.js @@ -11,8 +11,19 @@ export const datasourceService = { save, fetchOauth2BaseUrl, testSampleDb, + getDecryptedOptions, }; +function getDecryptedOptions(options) { + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(options), + }; + return fetch(`${config.apiUrl}/data-sources/decrypt`, requestOptions).then(handleResponse); +} + function getAll(appVersionId, environment_id, includeStaticSources = false) { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; let searchParams = new URLSearchParams( diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index f293731a4a..4889a2f4e6 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -12367,6 +12367,17 @@ tbody { } } + .design-component-inputs textarea { + + &.valid-textarea { + border: 1.5px solid #519b62 !important; + } + + &.invalid-textarea { + border: 1.5px solid #e26367 !important; + } + } + } .tj-app-input-wrapper { @@ -13163,6 +13174,10 @@ tbody { background-color: var(--slate3) !important; } + textarea:disabled { + background-color: var(--slate3) !important; + } + .react-select__control--is-disabled { background-color: var(--slate3) !important; } diff --git a/frontend/src/_ui/HttpHeaders/index.js b/frontend/src/_ui/HttpHeaders/index.js index 04bbd8e3b5..3f254d1497 100644 --- a/frontend/src/_ui/HttpHeaders/index.js +++ b/frontend/src/_ui/HttpHeaders/index.js @@ -1,13 +1,14 @@ -import React from "react"; -import _ from "lodash"; -import QueryEditor from "./QueryEditor"; -import SourceEditor from "./SourceEditor"; -import { deepClone } from "@/_helpers/utilities/utils.helpers"; +import React from 'react'; +import _ from 'lodash'; +import QueryEditor from './QueryEditor'; +import SourceEditor from './SourceEditor'; +import { deepClone } from '@/_helpers/utilities/utils.helpers'; export default ({ getter, - options = [["", ""]], + options = [['', '']], optionchanged, + handleOptionChange, isRenderedAsQueryEditor, workspaceConstants, isDisabled, @@ -16,27 +17,46 @@ export default ({ dataCy, }) => { function addNewKeyValuePair(options) { - const newPairs = [...options, ["", ""]]; - optionchanged(getter, newPairs); + const newPairs = [...options, ['', '']]; + + if (handleOptionChange) { + handleOptionChange(getter, newPairs, true); + } else { + optionchanged(getter, newPairs); + } } function removeKeyValuePair(index) { const newOptions = [...options]; newOptions.splice(index, 1); - optionchanged(getter, newOptions); + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } } function keyValuePairValueChanged(value, keyIndex, index) { if (!isRenderedAsQueryEditor) { const newOptions = deepClone(options); newOptions[index][keyIndex] = value; - options.length - 1 === index - ? addNewKeyValuePair(newOptions) - : optionchanged(getter, newOptions); + if (options.length - 1 === index) { + addNewKeyValuePair(newOptions); + } else { + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } + } } else { let newOptions = deepClone(options); newOptions[index][keyIndex] = value; - optionchanged(getter, newOptions); + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } } } @@ -53,10 +73,6 @@ export default ({ return isRenderedAsQueryEditor ? ( ) : ( - + ); }; diff --git a/frontend/src/_ui/Input-V3/index.js b/frontend/src/_ui/Input-V3/index.js index abc819c80a..71b2b3cf2a 100644 --- a/frontend/src/_ui/Input-V3/index.js +++ b/frontend/src/_ui/Input-V3/index.js @@ -43,7 +43,7 @@ const InputV3 = ({ helpText, ...props }) => { required={props.isRequired} /> )} - {(widget === 'password-v3' || encrypted) && ( + {(widget === 'password-v3' || widget === 'password-v3-textarea' || encrypted) && (
{ label={props.label} placeholder={props.placeholder} required={props.isRequired} + multiline={widget === 'password-v3-textarea'} />
)} diff --git a/frontend/src/components/ui/Input/CommonInput/Index.jsx b/frontend/src/components/ui/Input/CommonInput/Index.jsx index 0710876ac3..baee3ad5d8 100644 --- a/frontend/src/components/ui/Input/CommonInput/Index.jsx +++ b/frontend/src/components/ui/Input/CommonInput/Index.jsx @@ -32,21 +32,28 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, } }, [isValidatedMessages]); + useEffect(() => { + if (isValid === true && (!isValidatedMessages || isValidatedMessages.valid === null)) { + setIsValid(true); + } + }, [isValid, isValidatedMessages]); + const toggleEditing = () => { if (isDisabled) return; const willBeInEditMode = !isEditing; setIsEditing(willBeInEditMode); - - if (willBeInEditMode) { - change({ target: { value: '' } }); - } + change({ target: { value: '' } }); }; return (
- {label && } + {label && ( +
+ +
+ )} {type === 'password' && (
diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx index 1834d4799d..978e69798f 100644 --- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx +++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx @@ -14,14 +14,14 @@ const TextInput = ({ readOnly, ...restProps }) => { - const inputStyle = `tw-border-border-default placeholder:tw-text-text-placeholder tw-font-normal disabled:tw-bg-[#CCD1D5]/30 ${ + const inputStyle = `placeholder:tw-text-text-placeholder tw-font-normal disabled:tw-bg-[#CCD1D5]/30 ${ leadingIcon ? (size === 'small' ? 'tw-pl-[32px]' : 'tw-pl-[34px]') : 'tw-pl-[12px]' } ${trailingAction ? (size === 'small' ? 'tw-pr-[40px]' : 'tw-pr-[44px]') : 'tw-pr-[12px]'} ${ response === true ? '!tw-border-border-success-strong focus-visible:!tw-ring-0 focus-visible:!tw-ring-offset-0 focus-visible:!tw-border-border-success-strong' : response === false ? '!tw-border-border-danger-strong focus-visible:!tw-ring-0 focus-visible:!tw-ring-offset-0 focus-visible:!tw-border-border-danger-strong' - : '' + : 'tw-border-border-default' }`; return ( @@ -39,6 +39,7 @@ const TextInput = ({ size={size} placeholder={disabled && readOnly ? readOnly : placeholder} disabled={disabled} + response={response} {...restProps} className={inputStyle} /> diff --git a/frontend/src/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 33c3194f0b..9d09b97009 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'; import { inputVariants } from './InputUtils/Variants'; import SolidIcon from '../../../_ui/Icon/SolidIcons'; -const Input = React.forwardRef(({ className, size, type, ...props }, ref) => { +const Input = React.forwardRef(({ className, size, type, multiline, response, rows = 3, ...props }, ref) => { const [isPasswordVisible, setIsPasswordVisible] = React.useState(false); const isPasswordField = type === 'password'; @@ -13,19 +13,34 @@ const Input = React.forwardRef(({ className, size, type, ...props }, ref) => { } }; + const validationClass = response === true ? 'valid-textarea' : response === false ? 'invalid-textarea' : ''; + return ( - <> - - {isPasswordField && ( +
+ {multiline ? ( +