diff --git a/.gitignore b/.gitignore index 6ba2f61ed7..2376553d6d 100644 --- a/.gitignore +++ b/.gitignore @@ -93,5 +93,5 @@ hosting/.generated-envoy.dev.yaml # Sublime text *.sublime-project *.sublime-workspace - +packages/builder/cypress.env.json bin/ diff --git a/lerna.json b/lerna.json index ad89e1c404..f7f5e3f11e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.27-alpha.19", + "version": "1.0.44-alpha.2", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 9cb36575eb..ebf402add6 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.27-alpha.19", + "version": "1.0.44-alpha.2", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 28b9ced49b..8e6b01608e 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -8,6 +8,7 @@ exports.Cookies = { Auth: "budibase:auth", Init: "budibase:init", OIDC_CONFIG: "budibase:oidc:config", + RETURN_URL: "budibase:returnurl", } exports.Headers = { diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js index 8c00f2a8b8..85dd32946f 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -96,7 +96,12 @@ exports.getCookie = (ctx, name) => { * @param {string|object} value The value of cookie which will be set. * @param {object} opts options like whether to sign. */ -exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { +exports.setCookie = ( + ctx, + value, + name = "builder", + opts = { sign: true, requestDomain: false } +) => { if (value && opts && opts.sign) { value = jwt.sign(value, options.secretOrKey) } @@ -108,7 +113,7 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { overwrite: true, } - if (environment.COOKIE_DOMAIN) { + if (environment.COOKIE_DOMAIN && !opts.requestDomain) { config.domain = environment.COOKIE_DOMAIN } diff --git a/packages/bbui/package.json b/packages/bbui/package.json index a1304dd6b9..87651d5020 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.27-alpha.19", + "version": "1.0.44-alpha.2", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index bbaf5a3ff9..f7fed78b70 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -22,6 +22,7 @@ export let error = null export let fileTags = [] export let maximum = null + export let extensions = "*" const dispatch = createEventDispatcher() const imageExtensions = [ @@ -207,6 +208,7 @@ {disabled} type="file" multiple + accept={extensions} on:change={handleFile} /> { cy.addRowMultiValue(["1", "2", "3", "4", "5"]) }) - it("should add form with multi select picker, containing 5 options", () => { - cy.navigateToFrontend() - cy.wait(500) - // Add data provider - cy.get(`[data-cy="category-Data"]`).click() - cy.get(`[data-cy="component-Data Provider"]`).click() - cy.get('[data-cy="dataSource-prop-control"]').click() - cy.get(".dropdown").contains("Multi Data").click() - cy.wait(500) - // Add Form with schema to match table - cy.addComponent("Form", "Form") - cy.get('[data-cy="dataSource-prop-control"').click() - cy.get(".dropdown").contains("Multi Data").click() - cy.wait(500) - // Add multi-select picker to form - cy.addComponent("Form", "Multi-select Picker").then(componentId => { - cy.get('[data-cy="field-prop-control"]').type("Test Data").type("{enter}") - cy.wait(1000) - cy.getComponent(componentId).contains("Choose some options").click() - // Check picker has 5 items - cy.getComponent(componentId).find("li").should("have.length", 5) - // Select all items - for (let i = 1; i < 6; i++) { - cy.getComponent(componentId).find("li").contains(i).click() - } - // Check items have been selected - cy.getComponent(componentId) - .find(".spectrum-Picker-label") - .contains("(5)") + it ("should add form with multi select picker, containing 5 options", () => { + cy.navigateToFrontend() + cy.wait(500) + // Add data provider + cy.addComponent("Data", "Data Provider") + cy.get('[data-cy="dataSource-prop-control"]').click() + cy.get(".dropdown").contains("Multi Data").click() + cy.wait(500) + // Add Form with schema to match table + cy.addComponent("Form", "Form") + cy.get('[data-cy="dataSource-prop-control"').click() + cy.get(".dropdown").contains("Multi Data").click() + cy.wait(500) + // Add multi-select picker to form + cy.addComponent("Form", "Multi-select Picker").then((componentId) => { + cy.get('[data-cy="field-prop-control"]').type("Test Data").type('{enter}') + cy.wait(1000) + cy.getComponent(componentId).contains("Choose some options").click() + // Check picker has 5 items + cy.getComponent(componentId).find('li').should('have.length', 5) + // Select all items + for (let i = 1; i < 6; i++) { + cy.getComponent(componentId).find('li').contains(i).click() + } + // Check items have been selected + cy.getComponent(componentId).find('.spectrum-Picker-label').contains("(5)") + }) }) }) -}) + diff --git a/packages/builder/cypress/integration/autoScreensUI.spec.js b/packages/builder/cypress/integration/autoScreensUI.spec.js new file mode 100644 index 0000000000..780d7da076 --- /dev/null +++ b/packages/builder/cypress/integration/autoScreensUI.spec.js @@ -0,0 +1,47 @@ +context("Auto Screens UI", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should generate internal table screens", () => { + // Create autogenerated screens from the internal table + cy.createAutogeneratedScreens(["Cypress Tests"]) + // Confirm screens have been auto generated + cy.get(".nav-items-container").contains("cypress-tests").click() + cy.get(".nav-items-container").should('contain', 'cypress-tests/:id') + .and('contain', 'cypress-tests/new/row') + }) + + it("should generate multiple internal table screens at once", () => { + // Create a second internal table + const initialTable = "Cypress Tests" + const secondTable = "Table Two" + cy.createTable(secondTable) + // Create autogenerated screens from the internal tables + cy.createAutogeneratedScreens([initialTable, secondTable]) + // Confirm screens have been auto generated + cy.get(".nav-items-container").contains("cypress-tests").click() + // Previously generated tables are suffixed with numbers - as expected + cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id') + .and('contain', 'cypress-tests-2/new/row') + cy.get(".nav-items-container").contains("table-two").click() + cy.get(".nav-items-container").should('contain', 'table-two/:id') + .and('contain', 'table-two/new/row') + }) + + if (Cypress.env("TEST_ENV")) { + it("should generate data source screens", () => { + // Using MySQL data source for testing this + const datasource = "MySQL" + // Select & configure MySQL data source + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource) + // Create autogenerated screens from a MySQL table - MySQL contains books table + cy.createAutogeneratedScreens(["books"]) + cy.get(".nav-items-container").contains("books").click() + cy.get(".nav-items-container").should('contain', 'books/:id') + .and('contain', 'books/new/row') + }) + } +}) diff --git a/packages/builder/cypress/integration/changeAppIconAndColour.spec.js b/packages/builder/cypress/integration/changeAppIconAndColour.spec.js new file mode 100644 index 0000000000..4d4180fc55 --- /dev/null +++ b/packages/builder/cypress/integration/changeAppIconAndColour.spec.js @@ -0,0 +1,39 @@ +context("Change Application Icon and Colour", () => { + before(() => { + cy.login() + }) + + it("should change the icon and colour for an application", () => { + // Search for test application + cy.searchForApplication("Cypress Tests") + cy.get(".appTable") + .within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Menu").contains("Edit icon").click() + // Select random icon + cy.get(".grid").within(() => { + cy.get(".icon-item").eq(Math.floor(Math.random() * 23) + 1).click() + }) + // Select random colour + cy.get(".fill").click() + cy.get(".colors").within(() => { + cy.get(".color").eq(Math.floor(Math.random() * 33) + 1).click() + }) + cy.intercept('**/applications/**').as('iconChange') + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.wait("@iconChange") + cy.get("@iconChange").its('response.statusCode') + .should('eq', 200) + cy.wait(1000) + // Confirm icon has changed from default + // Confirm colour has been applied - There is no default colour + cy.get(".appTable") + .within(() => { + cy.get('[aria-label]').eq(0).children() + .should('have.attr', 'xlink:href').and('not.contain', '#spectrum-icon-18-Apps') + cy.get(".title").children().children() + .should('have.attr', 'style').and('contains', 'color') + }) + }) +}) diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index 34f152b540..d5d56d1ddc 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -2,7 +2,7 @@ context("Create an Application", () => { it("should create a new application", () => { cy.login() cy.createTestApp() - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.contains("Cypress Tests").should("exist") }) }) diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js index efd3c7d023..2658905de8 100644 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ b/packages/builder/cypress/integration/createAutomation.spec.js @@ -4,7 +4,6 @@ context("Create a automation", () => { cy.createTestApp() }) - // https://on.cypress.io/interacting-with-elements it("should create a automation", () => { cy.createTestTableWithData() cy.wait(2000) @@ -24,7 +23,7 @@ context("Create a automation", () => { cy.contains("dog").click() cy.wait(2000) // Create action - cy.get(".block > .spectrum-Icon").click() + cy.get('[aria-label="AddCircle"]').eq(1).click() cy.get(".modal-inner-wrapper").within(() => { cy.wait(1000) cy.contains("Create Row").trigger('mouseover').click().click() diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index 20ec919b57..55dceebab2 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -30,7 +30,6 @@ context("Create a Table", () => { cy.contains("Save Column").click() cy.contains("nameupdated ").should("contain", "nameupdated") }) - it("edits a row", () => { cy.contains("button", "Edit").click({ force: true }) @@ -47,13 +46,43 @@ context("Create a Table", () => { cy.get(".spectrum-Modal").contains("Delete").click() cy.contains("RoverUpdated").should("not.exist") }) + + it("Adds 15 rows and checks pagination", () => { + // 10 rows per page, 15 rows should create 2 pages within table + const totalRows = 16 + for (let i = 1; i < totalRows; i++){ + cy.addRow([i]) + } + cy.wait(1000) + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-ActionButton").eq(1).click() + }) + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-Body--secondary").contains("Page 2") + }) + }) + + it("Deletes rows and checks pagination", () => { + // Delete rows, removing second page of rows from table + const deleteRows = 5 + cy.get(".spectrum-Checkbox-input").check({ force: true }) + cy.get(".spectrum-Table-body") + cy.contains("Delete 5 row(s)").click() + cy.get(".spectrum-Modal").contains("Delete").click() + cy.wait(1000) + + // Confirm table only has one page + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-ActionButton").eq(1).should('not.be.enabled') + }) + }) it("deletes a column", () => { + const columnName = "nameupdated" cy.get(".title").click() cy.get(".spectrum-Table-editIcon > use").click() cy.contains("Delete").click() - cy.wait(50) - cy.get(`[data-cy="delete-column-confirm"]`).type("nameupdated") + cy.get('[data-cy="delete-column-confirm"]').type(columnName) cy.contains("Delete Column").click() cy.contains("nameupdated").should("not.exist") }) @@ -67,7 +96,7 @@ context("Create a Table", () => { cy.get(".actions .spectrum-Icon").click({ force: true }) }) cy.get(".spectrum-Menu > :nth-child(2)").click() - cy.get(`[data-cy="delete-table-confirm"]`).type("dog") + cy.get('[data-cy="delete-table-confirm"]').type("dog") cy.contains("Delete Table").click() cy.contains("dog").should("not.exist") }) diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js deleted file mode 100644 index 18ae8a16a0..0000000000 --- a/packages/builder/cypress/integration/createUser.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -context("Create a User", () => { - before(() => { - cy.login() - }) - - it("should create a user", () => { - cy.createUser("bbuser@test.com") - cy.contains("bbuser").should("be.visible") - }) -}) diff --git a/packages/builder/cypress/integration/createUserAndRoles.spec.js b/packages/builder/cypress/integration/createUserAndRoles.spec.js new file mode 100644 index 0000000000..538b910bb4 --- /dev/null +++ b/packages/builder/cypress/integration/createUserAndRoles.spec.js @@ -0,0 +1,128 @@ +context("Create a User and Assign Roles", () => { + before(() => { + cy.login() + }) + + it("should create a user", () => { + cy.createUser("bbuser@test.com") + cy.get(".spectrum-Table-body").should('contain', 'bbuser') + }) + + it("should confirm there is No Access for a New User", () => { + // Click into the user + cy.contains("bbuser").click() + cy.wait(500) + // Get No Access table - Confirm it has apps in it + cy.get(".spectrum-Table").eq(1).should('not.contain', 'No rows found') + // Get Configure Roles table - Confirm it has no apps + cy.get(".spectrum-Table").eq(0).contains('No rows found') + }) + + it("should assign role types", () => { + // 3 apps minimum required - to assign an app to each role type + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + if (val.length < 3) { + for (let i = 1; i < 3; i++) { + const uuid = () => Cypress._.random(0, 1e6) + const name = uuid() + cy.createApp(name) + } + } + }) + // Navigate back to the user + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(1000) + cy.get(".spectrum-SideNav").contains("Users").click() + cy.get(".spectrum-Table").contains("bbuser").click() + cy.wait(500) + for (let i = 0; i < 3; i++) { + cy.get(".spectrum-Table-body").eq(1).find('tr').eq(0).click() + cy.wait(500) + cy.get(".spectrum-Dialog-grid").contains("Choose an option").click().then(() => { + cy.wait(500) + if (i == 0) { + cy.get(".spectrum-Popover").contains("Admin").click() + } + if (i == 1) { + cy.get(".spectrum-Popover").contains("Power").click() + } + if (i == 2) { + cy.get(".spectrum-Popover").contains("Basic").click() + } + cy.wait(500) + cy.get(".spectrum-Button").contains("Update role").click({ force: true }) + }) + } + // Confirm roles exist within Configure roles table + cy.wait(500) + cy.get(".spectrum-Table-body").eq(0).within((assginedRoles) => { + expect(assginedRoles).to.contain("Admin") + expect(assginedRoles).to.contain("Power") + expect(assginedRoles).to.contain("Basic") + }) + }) + + it("should unassign role types", () => { + // Set each app within Configure roles table to 'No Access' + cy.get(".spectrum-Table-body").eq(0).find('tr').its('length').then((len) => { + for (let i = 0; i < len; i ++){ + cy.get(".spectrum-Table-body").eq(0).find('tr').eq(0).click().then(() => { + cy.get(".spectrum-Form-item").contains("Role").parent().within(() => { + cy.get(".spectrum-Picker").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Popover").contains("No Access").click() + }) + cy.get(".spectrum-Button").contains("Update role").click({ force: true }) + cy.wait(1000) + }) + } + }) + // Confirm Configure roles table no longer has any apps in it + cy.get(".spectrum-Table-body").eq(0).contains('No rows found') + }) + + it("should enable Developer access", () => { + // Enable Developer access + cy.get(".field").eq(4).within(() => { + cy.get(".spectrum-Switch-input").click({ force: true }) + }) + // No Access table should now be empty + cy.get(".container").contains("No Access").parent().within(() => { + cy.get(".spectrum-Table").contains("No rows found") + }) + + // Each app within Configure roles should have Admin access + cy.get(".spectrum-Table-body").eq(0).find('tr').its('length').then((len) => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table-body").eq(0).find('tr').eq(i).contains("Admin") + cy.wait(500) + } + }) + }) + + it("should disable Developer access", () => { + // Disable Developer access + cy.get(".field").eq(4).within(() => { + cy.get(".spectrum-Switch-input").click({ force: true }) + }) + // Configure roles table should now be empty + cy.get(".container").contains("Configure roles").parent().within(() => { + cy.get(".spectrum-Table").contains("No rows found") + }) + }) + + it("should delete a user", () => { + // Click Delete user button + cy.get(".spectrum-Button").contains("Delete user").click({force: true}).then(() => { + // Confirm deletion within modal + cy.wait(500) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete user").click({force: true}) + cy.wait(4000) + }) + }) + cy.get(".spectrum-Table-body").should("not.have.text", "bbuser") + }) +}) diff --git a/packages/builder/cypress/integration/customThemingProperties.spec.js b/packages/builder/cypress/integration/customThemingProperties.spec.js index 5b7922bde7..99e923d2a2 100644 --- a/packages/builder/cypress/integration/customThemingProperties.spec.js +++ b/packages/builder/cypress/integration/customThemingProperties.spec.js @@ -80,5 +80,4 @@ xcontext("Custom Theming Properties", () => { .parent().find(".container.svelte-z3cm5a").click() .get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]') } - }) diff --git a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js new file mode 100644 index 0000000000..5d8614bf01 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js @@ -0,0 +1,37 @@ +context("Datasource Wizard", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should navigate in and out of a datasource via wizard", () => { + // Select PostgreSQL and add config (without fetch) + const datasource = "Oracle" + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource, true) + + // Navigate back within datasource wizard + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Back").click({ force: true }) + cy.wait(1000) + }) + + // Select PostgreSQL datasource again + cy.get(".item-list").contains(datasource).click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + }) + + // Fetch tables after selection + // Previously entered config should not have been saved + // Config is back to default values + // Modal will close and provide 500 error + cy.intercept('**/datasources').as('datasourceConnection') + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Save and fetch tables").click({ force: true }) + }) + cy.wait("@datasourceConnection") + cy.get("@datasourceConnection").its('response.body') + .should('have.property', 'status', 500) + }) +}) diff --git a/packages/builder/cypress/integration/datasources/mySql.spec.js b/packages/builder/cypress/integration/datasources/mySql.spec.js new file mode 100644 index 0000000000..5cb579a447 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/mySql.spec.js @@ -0,0 +1,187 @@ +context("MySQL Datasource Testing", () => { + if (Cypress.env("TEST_ENV")) { + + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "MySQL" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add MySQL data source without configuration", () => { + // Select MySQL data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept('**/datasources').as('datasource') + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource").its('response.body') + .should('have.property', 'message', 'connect ECONNREFUSED 127.0.0.1:3306') + cy.get("@datasource").its('response.body') + .should('have.property', 'status', 500) + }) + + it("should add MySQL data source and fetch tables", () => { + // Add & configure MySQL data source + cy.selectExternalDatasource(datasource) + cy.intercept('**/datasources').as('datasource') + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its('response.statusCode') + .should('eq', 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table-body").eq(0) + .find('tr') + .its('length') + .should('be.gt', 0) + }) + + it("should check table fetching error", () => { + // MySQL test data source contains tables without primary keys + cy.get(".spectrum-InLineAlert") + .should('contain', 'Error fetching tables') + .and('contain', 'No primary key constraint found') + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 1) + cy.get(".spectrum-Table-cell").should('contain', "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + cy.wait(1000) + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 2) + cy.get(".spectrum-Table-cell") + .should('contain', "LOCATIONS through COUNTRIES → REGIONS") + }) + + it("should delete relationships", () => { + // Delete both relationships + cy.get(".spectrum-Table-body") + .eq(1).find('tr').its('length') + .then((len) => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table-body").eq(1).within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete").click({ force: true }) + }) + cy.reload() + } + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should('contain', 'No relationships configured') + }) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea").eq(0) + .type("SELECT * FROM books", { force: true }) + // Intercept query execution + cy.intercept('**/queries/preview').as('query') + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its('response.statusCode') + .should('eq', 200) + cy.get("@query").its('response.body') + .should('not.be.empty') + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should('contain', queryName) + }) + + it("should duplicate a query", () => { + // Get last nav item - The query + cy.get(".nav-item").last().within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should('contain', queryName + ' (1)') + }) + + it("should edit a query name", () => { + // Rename query + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").clear().type(queryRename) + }) + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should('contain', queryRename) + }) + + it("should delete a query", () => { + // Get last nav item - The query + for (let i = 0; i < 2; i++) { + cy.get(".nav-item").last().within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button").contains("Delete Query").click({ force: true }) + cy.wait(1000) + } + // Confirm deletion + cy.get(".nav-item").should('not.contain', queryName) + cy.get(".nav-item").should('not.contain', queryRename) + }) + } +}) diff --git a/packages/builder/cypress/integration/datasources/oracle.spec.js b/packages/builder/cypress/integration/datasources/oracle.spec.js new file mode 100644 index 0000000000..0a41ead4f3 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/oracle.spec.js @@ -0,0 +1,191 @@ +context("Oracle Datasource Testing", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "Oracle" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add Oracle data source and skip table fetch", () => { + // Select Oracle data source + cy.selectExternalDatasource(datasource) + // Skip table fetch - no config added + cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) + cy.wait(500) + // Confirm config contains localhost + cy.get(".spectrum-Textfield-input").eq(1).should('have.value', 'localhost') + // Add another Oracle data source, configure & skip table fetch + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource, true) + // Confirm config and no tables + cy.get(".spectrum-Textfield-input").eq(1).should('have.value', Cypress.env("oracle").HOST) + cy.get(".spectrum-Body").eq(2).should('contain', 'No tables found.') + }) + + it("Should add Oracle data source and fetch tables without configuration", () => { + // Select Oracle data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept('**/datasources').as('datasource') + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource").its('response.body') + .should('have.property', 'message', 'NJS-007: invalid value for "user" in parameter 1') + cy.get("@datasource").its('response.body') + .should('have.property', 'status', 500) + }) + + it("should add Oracle data source and fetch tables", () => { + // Add & configure Oracle data source + cy.selectExternalDatasource(datasource) + cy.intercept('**/datasources').as('datasource') + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its('response.statusCode') + .should('eq', 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table-body").eq(0) + .find('tr') + .its('length') + .should('be.gt', 0) + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 1) + cy.get(".spectrum-Table-cell").should('contain', "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 2) + cy.get(".spectrum-Table-cell") + .should('contain', "LOCATIONS through COUNTRIES → REGIONS") + }) + + it("should delete relationships", () => { + // Delete both relationships + cy.get(".spectrum-Table-body") + .eq(1).find('tr').its('length') + .then((len) => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table-body").eq(1).within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete").click({ force: true }) + }) + cy.reload() + } + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should('contain', 'No relationships configured') + }) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea").eq(0) + .type("SELECT * FROM JOBS", { force: true }) + // Intercept query execution + cy.intercept('**/queries/preview').as('query') + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its('response.statusCode') + .should('eq', 200) + cy.get("@query").its('response.body') + .should('not.be.empty') + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should('contain', queryName) + }) + + it("should duplicate a query", () => { + // Get query nav item + cy.get(".nav-item").contains(queryName).parent().within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should('contain', queryName + ' (1)') + }) + + it("should edit a query name", () => { + // Rename query + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").clear().type(queryRename) + }) + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should('contain', queryRename) + }) + + it("should delete a query", () => { + // Get query nav item - QueryName + cy.get(".nav-item").contains(queryName).parent().within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button").contains("Delete Query").click({ force: true }) + cy.wait(1000) + + // Confirm deletion + cy.get(".nav-item").should('not.contain', queryName) + }) +}) diff --git a/packages/builder/cypress/integration/datasources/postgreSql.spec.js b/packages/builder/cypress/integration/datasources/postgreSql.spec.js new file mode 100644 index 0000000000..4bd2f7c22f --- /dev/null +++ b/packages/builder/cypress/integration/datasources/postgreSql.spec.js @@ -0,0 +1,237 @@ +context("PostgreSQL Datasource Testing", () => { + if (Cypress.env("TEST_ENV")) { + + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "PostgreSQL" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add PostgreSQL data source without configuration", () => { + // Select PostgreSQL data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept('**/datasources').as('datasource') + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource").its('response.body') + .should('have.property', 'message', 'connect ECONNREFUSED 127.0.0.1:5432') + cy.get("@datasource").its('response.body') + .should('have.property', 'status', 500) + }) + + it("should add PostgreSQL data source and fetch tables", () => { + // Add & configure PostgreSQL data source + cy.selectExternalDatasource(datasource) + cy.intercept('**/datasources').as('datasource') + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its('response.statusCode') + .should('eq', 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table-body").eq(0) + .find('tr') + .its('length') + .should('be.gt', 0) + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 1) + cy.get(".spectrum-Table-cell").should('contain', "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button").contains("Define relationship").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table-body").eq(1) + .find('tr') + .its('length') + .should('eq', 2) + cy.get(".spectrum-Table-cell") + .should('contain', "LOCATIONS through COUNTRIES → REGIONS") + }) + + it("should delete a relationship", () => { + cy.get(".hierarchy-items-container").contains(datasource).click() + cy.reload() + // Delete one relationship + cy.get(".spectrum-Table-body").eq(1).within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete").click({ force: true }) + }) + cy.reload() + // Confirm relationship was deleted + cy.get(".spectrum-Table-body") + .eq(1).find('tr').its('length').should('eq', 1) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea").eq(0) + .type("SELECT * FROM books", { force: true }) + // Intercept query execution + cy.intercept('**/queries/preview').as('query') + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its('response.statusCode') + .should('eq', 200) + cy.get("@query").its('response.body') + .should('not.be.empty') + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".hierarchy-items-container").should('contain', queryName) + }) + + it("should switch to schema with no tables", () => { + // Switch Schema - To one without any tables + cy.get(".hierarchy-items-container").contains(datasource).click() + switchSchema("randomText") + + // No tables displayed + cy.get(".spectrum-Body").eq(2).should('contain', 'No tables found') + + // Previously created query should be visible + cy.get(".spectrum-Table-body").should('contain', queryName) + }) + + it("should switch schemas", () => { + // Switch schema - To one with tables + switchSchema("1") + + // Confirm tables exist - Check for specific one + cy.get(".spectrum-Table-body").eq(0).should('contain', 'test') + cy.get(".spectrum-Table-body").eq(0).find('tr').its('length').should('eq', 1) + + // Confirm specific table visible within left nav bar + cy.get(".hierarchy-items-container").should('contain', 'test') + + // Switch back to public schema + switchSchema("public") + + // Confirm tables exist - again + cy.get(".spectrum-Table-body").eq(0).should('contain', 'REGIONS') + cy.get(".spectrum-Table-body").eq(0) + .find('tr').its('length').should('be.gt', 1) + + // Confirm specific table visible within left nav bar + cy.get(".hierarchy-items-container").should('contain', 'REGIONS') + + // No relationships and one query + cy.get(".spectrum-Body").eq(3).should('contain', 'No relationships configured.') + cy.get(".spectrum-Table-body").eq(1).should('contain', queryName) + }) + + it("should duplicate a query", () => { + // Get last nav item - The query + cy.get(".nav-item").last().within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should('contain', queryName + ' (1)') + }) + + it("should edit a query name", () => { + // Access query + cy.get(".hierarchy-items-container").contains(queryName + ' (1)').click() + + // Rename query + cy.get(".spectrum-Form-item").eq(0).within(() => { + cy.get("input").clear().type(queryRename) + }) + + // Run and Save query + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should('contain', queryRename) + }) + + it("should delete a query", () => { + // Get last nav item - The query + for (let i = 0; i < 2; i++) { + cy.get(".nav-item").last().within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button").contains("Delete Query").click({ force: true }) + cy.wait(1000) + } + // Confirm deletion + cy.get(".nav-item").should('not.contain', queryName) + cy.get(".nav-item").should('not.contain', queryRename) + }) + + const switchSchema = (schema) => { + // Edit configuration - Change Schema + cy.get(".spectrum-Textfield").eq(6).within(() => { + cy.get('input').clear().type(schema) + }) + // Save configuration & fetch + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.get(".spectrum-Button").contains("Fetch tables").click({ force: true }) + // Click fetch tables again within modal + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Fetch tables").click({ force: true }) + }) + cy.reload() + cy.wait(5000) + } + } +}) diff --git a/packages/builder/cypress/integration/datasources/rest.spec.js b/packages/builder/cypress/integration/datasources/rest.spec.js new file mode 100644 index 0000000000..9999b47660 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/rest.spec.js @@ -0,0 +1,39 @@ +context("REST Datasource Testing", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + + it("Should add REST data source with incorrect API", () => { + // Select REST data source + cy.selectExternalDatasource(datasource) + // Enter incorrect api & attempt to send query + cy.wait(500) + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.intercept('**/preview').as('queryError') + cy.get("input").clear().type("random text") + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@queryError") + cy.get("@queryError").its('response.body') + .should('have.property', 'message', 'request to http://random/%20text? failed, reason: getaddrinfo ENOTFOUND random') + cy.get("@queryError").its('response.body') + .should('have.property', 'status', 400) + }) + + it("should add and configure a REST datasource", () => { + // Select REST datasource and create query + cy.selectExternalDatasource(datasource) + cy.wait(500) + // createRestQuery confirms query creation + cy.createRestQuery("GET", restUrl) + // Confirm status code response within REST datasource + cy.get(".spectrum-FieldLabel") + .contains("Status") + .children() + .should('contain', 200) + }) +}) diff --git a/packages/builder/cypress/integration/queryLevelTransformers.spec.js b/packages/builder/cypress/integration/queryLevelTransformers.spec.js new file mode 100644 index 0000000000..018627a80c --- /dev/null +++ b/packages/builder/cypress/integration/queryLevelTransformers.spec.js @@ -0,0 +1,112 @@ +context("Query Level Transformers", () => { + before(() => { + cy.login() + cy.deleteApp("Cypress Tests") + cy.createApp("Cypress Tests") + }) + + it("should write a transformer function", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl) + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Get Transformer Function from file + cy.readFile("cypress/support/queryLevelTransformerFunction.js").then((transformerFunction) => { + cy.get(".CodeMirror textarea") + // Highlight current text and overwrite with file contents + .type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true }) + .type(transformerFunction, { parseSpecialCharSequences: false }) + }) + // Send Query + cy.intercept('**/queries/preview').as('query') + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@query") + // Assert against Status Code, body, & body rows + cy.get("@query").its('response.statusCode') + .should('eq', 200) + cy.get("@query").its('response.body').should('not.be.empty') + cy.get("@query").its('response.body.rows').should('not.be.empty') + }) + + it("should add data to the previous query", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl) + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Get Transformer Function with Data from file + cy.readFile("cypress/support/queryLevelTransformerFunctionWithData.js").then((transformerFunction) => { + //console.log(transformerFunction[1]) + cy.get(".CodeMirror textarea") + // Highlight current text and overwrite with file contents + .type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true }) + .type(transformerFunction, { parseSpecialCharSequences: false }) + }) + // Send Query + cy.intercept('**/queries/preview').as('query') + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@query") + // Assert against Status Code, body, & body rows + cy.get("@query").its('response.statusCode') + .should('eq', 200) + cy.get("@query").its('response.body').should('not.be.empty') + cy.get("@query").its('response.body.rows').should('not.be.empty') + }) + + it("should run an invalid query within the transformer section", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl) + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Clear the code box and add "test" + cy.get(".CodeMirror textarea") + .type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true }) + .type("test") + // Run Query and intercept + cy.intercept('**/preview').as('queryError') + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@queryError") + cy.wait(500) + // Assert against message and status for the query error + cy.get("@queryError").its('response.body').should('have.property', 'message', "test is not defined") + cy.get("@queryError").its('response.body').should('have.property', 'status', 400) + }) + + it("should run an invalid query via POST request", () => { + // POST request with transformer as null + cy.request({method: 'POST', + url: `${Cypress.config().baseUrl}/api/queries/`, + body: {fields : {"headers":{},"queryString":null,"path":null}, + parameters : [], + schema : {}, + name : "test", + queryVerb : "read", + transformer : null, + datasourceId: "test"}, + // Expected 400 error - Transformer must be a string + failOnStatusCode: false}).then((response) => { + expect(response.status).to.equal(400) + expect(response.body.message).to.include('Invalid body - "transformer" must be a string') + }) + }) + + it("should run an empty query", () => { + // POST request with Transformer as an empty string + cy.request({method: 'POST', + url: `${Cypress.config().baseUrl}/api/queries/preview`, + body: {fields : {"headers":{},"queryString":null,"path":null}, + queryVerb : "read", + transformer : "", + datasourceId: "test"}, + // Expected 400 error - Transformer is not allowed to be empty + failOnStatusCode: false}).then((response) => { + expect(response.status).to.equal(400) + expect(response.body.message).to.include('Invalid body - "transformer" is not allowed to be empty') + }) + }) +}) diff --git a/packages/builder/cypress/integration/renameAnApplication.spec.js b/packages/builder/cypress/integration/renameAnApplication.spec.js index a954faee95..49efb341c6 100644 --- a/packages/builder/cypress/integration/renameAnApplication.spec.js +++ b/packages/builder/cypress/integration/renameAnApplication.spec.js @@ -5,17 +5,24 @@ context("Rename an App", () => { }) it("should rename an unpublished application", () => { + const appName = "Cypress Tests" const appRename = "Cypress Renamed" // Rename app, Search for app, Confirm name was changed cy.get(".home-logo").click() - renameApp(appRename) + renameApp(appName, appRename) + cy.reload() + cy.wait(1000) cy.searchForApplication(appRename) cy.get(".appTable").find(".title").should("have.length", 1) - cy.deleteApp(appRename) + // Set app name back to Cypress Tests + cy.reload() + cy.wait(1000) + renameApp(appRename, appName) }) xit("Should rename a published application", () => { // It is not possible to rename a published application + const appName = "Cypress Tests" const appRename = "Cypress Renamed" // Publish the app cy.get(".toprightnav") @@ -27,24 +34,28 @@ xit("Should rename a published application", () => { }) // Rename app, Search for app, Confirm name was changed cy.get(".home-logo").click() - renameApp(appRename, true) + renameApp(appName, appRename, true) cy.searchForApplication(appRename) - cy.get(".appTable").find(".title").should("have.length", 1) + cy.get(".appTable").find(".wrapper").should("have.length", 1) }) it("Should try to rename an application to have no name", () => { + const appName = "Cypress Tests" cy.get(".home-logo").click() - renameApp(" ", false, true) + renameApp(appName, " ", false, true) + cy.wait(500) // Close modal and confirm name has not been changed cy.get(".spectrum-Dialog-grid").contains("Cancel").click() - cy.searchForApplication("Cypress Tests") + cy.reload() + cy.wait(1000) + cy.searchForApplication(appName) cy.get(".appTable").find(".title").should("have.length", 1) }) xit("Should create two applications with the same name", () => { // It is not possible to have applications with the same name const appName = "Cypress Tests" - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) cy.get(".spectrum-Button").contains("Create app").click({force: true}) cy.contains(/Start from scratch/).click() @@ -59,22 +70,35 @@ xit("Should create two applications with the same name", () => { it("should validate application names", () => { // App name must be letters, numbers and spaces only // This test checks numbers and special characters specifically + const appName = "Cypress Tests" const numberName = 12345 const specialCharName = "£$%^" cy.get(".home-logo").click() - renameApp(numberName) + renameApp(appName, numberName) + cy.reload() + cy.wait(1000) cy.searchForApplication(numberName) cy.get(".appTable").find(".title").should("have.length", 1) - renameApp(specialCharName) + cy.reload() + cy.wait(1000) + renameApp(numberName, specialCharName) cy.get(".error").should("have.text", "App name must be letters, numbers and spaces only") + // Set app name back to Cypress Tests + cy.reload() + cy.wait(1000) + renameApp(numberName, appName) }) - const renameApp = (appName, published, noName) => { - cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`) + const renameApp = (originalName, changedName, published, noName) => { + cy.searchForApplication(originalName) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) .its("body") .then(val => { if (val.length > 0) { - cy.get(".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon").click() + cy.get(".appTable") + .within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) // Check for when an app is published if (published == true){ // Should not have Edit as option, will unpublish app @@ -93,7 +117,7 @@ it("should validate application names", () => { return cy } cy.get("input").clear() - cy.get("input").eq(0).type(appName).should("have.value", appName).blur() + cy.get("input").eq(0).type(changedName).should("have.value", changedName).blur() cy.get(".spectrum-ButtonGroup").contains("Save").click({force: true}) cy.wait(500) }) diff --git a/packages/builder/cypress/integration/revertApp.spec.js b/packages/builder/cypress/integration/revertApp.spec.js new file mode 100644 index 0000000000..2f49e64bf3 --- /dev/null +++ b/packages/builder/cypress/integration/revertApp.spec.js @@ -0,0 +1,62 @@ +context("Revert apps", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should try to revert an unpublished app", () => { + // Click revert icon + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Dialog-grid").within(() => { + // Enter app name before revert + cy.get("input").type("Cypress Tests") + cy.intercept('**/revert').as('revertApp') + // Click Revert + cy.get(".spectrum-Button").contains("Revert").click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@revertApp") + cy.get("@revertApp").its('response.body').should('have.property', 'message', "App has not yet been deployed") + cy.get("@revertApp").its('response.body').should('have.property', 'status', 400) + }) + }) + + it("should revert a published app", () => { + // Add initial component - Paragraph + cy.addComponent("Elements", "Paragraph") + // Publish app + cy.get(".spectrum-Button").contains("Publish").click({ force: true }) + cy.get(".spectrum-ButtonGroup").within(() => { + cy.get(".spectrum-Button").contains("Publish").click({ force: true }) + }) + // Add second component - Button + cy.addComponent("Elements", "Button") + // Click Revert + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Dialog-grid").within(() => { + // Click Revert + cy.get(".spectrum-Button").contains("Revert").click({ force: true }) + cy.wait(1000) + }) + // Confirm Paragraph component is still visible + cy.get(".root").contains("New Paragraph") + // Confirm Button component is not visible + cy.get(".root").should("not.have.text", "New Button") + }) + + it("should enter incorrect app name when reverting", () => { + // Click Revert + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + // Enter incorrect app name + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("Cypress Tests") + // Revert button within modal should be disabled + cy.get(".spectrum-Button").eq(1).should('be.disabled') + }) + }) +}) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index e67057344a..e69bc8badc 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -10,7 +10,7 @@ Cypress.on("uncaught:exception", () => { }) Cypress.Commands.add("login", () => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(2000) cy.url().then(url => { if (url.includes("builder/admin")) { @@ -33,31 +33,77 @@ Cypress.Commands.add("login", () => { }) Cypress.Commands.add("createApp", name => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(body => { + if (body.length > 0) { + cy.get(".spectrum-Button").contains("Create app").click({ force: true }) + } + }) cy.contains(/Start from scratch/).dblclick() + cy.wait(2000) cy.get(".spectrum-Modal").within(() => { cy.get("input").eq(0).type(name).should("have.value", name).blur() cy.get(".spectrum-ButtonGroup").contains("Create app").click() - cy.wait(7000) + cy.wait(5000) }) + cy.createTable("Cypress Tests", true) }) -Cypress.Commands.add("deleteApp", appName => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.wait(1000) - cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`) +Cypress.Commands.add("deleteApp", name => { + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(2000) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) .its("body") .then(val => { if (val.length > 0) { - cy.get( - ".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon" - ).click() - cy.contains("Delete").click() - cy.get(".spectrum-Modal").within(() => { - cy.get("input").type(appName) - cy.get(".spectrum-Button--warning").click() + cy.searchForApplication(name) + cy.get(".appTable").within(() => { + cy.get(".spectrum-Icon").eq(1).click() }) + cy.get(".spectrum-Menu").then($menu => { + if ($menu.text().includes("Unpublish")) { + cy.get(".spectrum-Menu").contains("Unpublish").click() + cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() + } else { + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type(name) + }) + cy.get(".spectrum-Button--warning").click() + } + }) + } else { + return + } + }) +}) + +Cypress.Commands.add("deleteAllApps", () => { + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(500) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + for (let i = 0; i < val.length; i++) { + cy.get(".spectrum-Heading") + .eq(1) + .then(app => { + const name = app.text() + cy.get(".title") + .children() + .within(() => { + cy.get(".spectrum-Icon").eq(0).click() + }) + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type(name) + cy.get(".spectrum-Button--warning").click() + }) + cy.reload() + }) } }) }) @@ -66,6 +112,7 @@ Cypress.Commands.add("createTestApp", () => { const appName = "Cypress Tests" cy.deleteApp(appName) cy.createApp(appName, "This app is used for Cypress testing.") + cy.createScreen("home", "home") }) Cypress.Commands.add("createTestTableWithData", () => { @@ -74,10 +121,18 @@ Cypress.Commands.add("createTestTableWithData", () => { cy.addColumn("dog", "age", "Number") }) -Cypress.Commands.add("createTable", tableName => { - cy.contains("Budibase DB").click() - cy.contains("Create new table").click() - +Cypress.Commands.add("createTable", (tableName, initialTable) => { + if (!initialTable) { + cy.navigateToDataSection() + cy.get(".add-button").click() + } + cy.wait(7000) + cy.get(".spectrum-Modal") + .contains("Budibase DB") + .click() + .then(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + }) cy.get(".spectrum-Modal").within(() => { cy.wait(1000) cy.get("input").first().type(tableName).blur() @@ -184,22 +239,49 @@ Cypress.Commands.add("navigateToFrontend", () => { cy.wait(1000) cy.contains("Design").click() cy.get(".spectrum-Search").type("/") - cy.createScreen("home", "home") - cy.addComponent("Elements", "Headline") cy.get(".nav-item").contains("home").click() }) +Cypress.Commands.add("navigateToDataSection", () => { + // Clicks on the Data tab + cy.wait(500) + cy.contains("Data").click() +}) + Cypress.Commands.add("createScreen", (screenName, route) => { + cy.contains("Design").click() cy.get("[aria-label=AddCircle]").click() cy.get(".spectrum-Modal").within(() => { - cy.get(".item").first().click() - cy.get(".spectrum-Button--cta").click() + cy.get(".item").contains("Blank").click() + cy.get(".spectrum-Button").contains("Add Screens").click({ force: true }) + cy.wait(500) }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Form-itemField").eq(0).type(screenName) + cy.get(".spectrum-Form-itemField").eq(1).type(route) + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + cy.wait(1000) + }) +}) + +Cypress.Commands.add("createAutogeneratedScreens", screenNames => { + // Screen name must already exist within data source + cy.contains("Design").click() + cy.get("[aria-label=AddCircle]").click() + for (let i = 0; i < screenNames.length; i++) { + cy.get(".item").contains(screenNames[i]).click() + } + cy.get(".spectrum-Button").contains("Add Screens").click({ force: true }) + cy.wait(2000) +}) + +Cypress.Commands.add("addRow", values => { + cy.contains("Create row").click() cy.get(".spectrum-Modal").within(() => { - cy.get("input").first().clear().type(screenName) - cy.get("input").eq(1).clear().type(route) - cy.get(".spectrum-Button--cta").click() - cy.wait(2000) + for (let i = 0; i < values.length; i++) { + cy.get("input").eq(i).type(values[i]).blur() + } + cy.get(".spectrum-ButtonGroup").contains("Create").click() }) }) @@ -237,7 +319,145 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => { }) Cypress.Commands.add("searchForApplication", appName => { - cy.get(".spectrum-Textfield").within(() => { - cy.get("input").eq(0).type(appName) + cy.wait(1000) + // Searches for the app + cy.get(".filter").then(() => { + cy.get(".spectrum-Textfield").within(() => { + cy.get("input").eq(0).type(appName) + }) + }) + // Confirms app exists after search + cy.get(".appTable").contains(appName) +}) + +Cypress.Commands.add("selectExternalDatasource", datasourceName => { + // Navigates to Data Section + cy.navigateToDataSection() + // Open Data Source modal + cy.get(".nav").within(() => { + cy.get(".add-button").click() + }) + // Clicks specified datasource & continue + cy.get(".item-list").contains(datasourceName).click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) }) }) + +Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => { + // selectExternalDatasource should be called prior to this + // Adds the config for specified datasource & fetches tables + // Currently supports MySQL, PostgreSQL, Oracle + // Host IP Address + cy.wait(500) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".form-row") + .eq(0) + .within(() => { + cy.get(".spectrum-Textfield").within(() => { + cy.log(datasource) + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").HOST) + } else { + cy.get("input").clear().type(Cypress.env("HOST_IP")) + } + }) + }) + }) + // Database Name + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(4) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").DATABASE) + }) + } else { + cy.get(".form-row") + .eq(2) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").DATABASE) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").DATABASE) + } + }) + } + }) + // User + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(2) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").USER) + }) + } else { + cy.get(".form-row") + .eq(3) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").USER) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").USER) + } + }) + } + }) + // Password + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(3) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").PASSWORD) + }) + } else { + cy.get(".form-row") + .eq(4) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").PASSWORD) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").PASSWORD) + } + }) + } + }) + // Click to fetch tables + if (skipFetch) { + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Skip table fetch") + .click({ force: true }) + }) + } else { + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + cy.wait(1000) + }) + } +}) + +Cypress.Commands.add("createRestQuery", (method, restUrl) => { + // addExternalDatasource should be called prior to this + // Configures REST datasource & sends query + cy.wait(500) + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + // Select Method & add Rest URL + cy.get(".spectrum-Picker-label").eq(1).click() + cy.get(".spectrum-Menu").contains(method).click() + cy.get("input").clear().type(restUrl) + // Send query + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Button").contains("Save query").click({ force: true }) + cy.get(".hierarchy-items-container") + .should("contain", method) + .and("contain", restUrl) +}) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunction.js b/packages/builder/cypress/support/queryLevelTransformerFunction.js new file mode 100644 index 0000000000..213d2abc20 --- /dev/null +++ b/packages/builder/cypress/support/queryLevelTransformerFunction.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line +const breweries = data +const totals = {} + +for (let brewery of breweries) { + const state = brewery.state + if (totals[state] == null) { + totals[state] = 1 + } else { + totals[state]++ + } +} +const entries = Object.entries(totals) +return entries.map(([state, count]) => ({ state, count })) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js new file mode 100644 index 0000000000..3d4f6024d2 --- /dev/null +++ b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js @@ -0,0 +1,31 @@ +// eslint-disable-next-line +const breweries = data +const totals = {} +for (let brewery of breweries) { + const state = brewery.state + if (totals[state] == null) { + totals[state] = 1 + } else { + totals[state]++ + } +} +const stateCodes = { + texas: "tx", + colorado: "co", + florida: "fl", + iwoa: "ia", + louisiana: "la", + california: "ca", + pennsylvania: "pa", + georgia: "ga", + "new hampshire": "nh", + virginia: "va", + michigan: "mi", + maryland: "md", + ohio: "oh", +} +const entries = Object.entries(totals) +return entries.map(([state, count]) => { + stateCodes[state.toLowerCase()] + return { state, count, flag: "http://flags.ox3.in/svg/us/${stateCode}.svg" } +}) diff --git a/packages/builder/package.json b/packages/builder/package.json index 43f94b5ef0..b6d08f0af2 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.0.27-alpha.19", + "version": "1.0.44-alpha.2", "license": "GPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^1.0.27-alpha.19", - "@budibase/client": "^1.0.27-alpha.19", + "@budibase/bbui": "^1.0.44-alpha.2", + "@budibase/client": "^1.0.44-alpha.2", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^1.0.27-alpha.19", + "@budibase/string-templates": "^1.0.44-alpha.2", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", @@ -95,7 +95,7 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/svelte": "^3.0.0", "babel-jest": "^26.6.3", - "cypress": "^5.1.0", + "cypress": "9.2.1", "cypress-terminal-report": "^1.4.1", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 4bcb9b74c6..897d3a74db 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -13,6 +13,9 @@ const apiCall = headers, }) if (resp.status === 403) { + if (url.includes("/api/templates")) { + return { json: () => [] } + } removeCookie(Cookies.Auth) // reload after removing cookie, go to login if (!url.includes("self") && !url.includes("login")) { diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 752f291019..1fa5c6e073 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -126,7 +126,12 @@ } } + function cancelEdit() { + field.name = originalName + } + function deleteColumn() { + field.name = deleteColName if (field.name === $tables.selected.primaryDisplay) { notifications.error("You cannot delete the display column") } else { @@ -307,6 +312,7 @@ title={originalName ? "Edit Column" : "Create Column"} confirmText="Save Column" onConfirm={saveColumn} + onCancel={cancelEdit} disabled={invalid} >

- Are you sure you wish to delete the column {field.name}? + Are you sure you wish to delete the column {originalName}? Your data will be deleted and this action cannot be undone - enter the column name to confirm.

diff --git a/packages/builder/src/components/backend/DataTable/modals/JSONSchemaModal.svelte b/packages/builder/src/components/backend/DataTable/modals/JSONSchemaModal.svelte index a575d0c288..1d0266100b 100644 --- a/packages/builder/src/components/backend/DataTable/modals/JSONSchemaModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/JSONSchemaModal.svelte @@ -9,6 +9,7 @@ Select, Body, Layout, + ActionButton, } from "@budibase/bbui" import { onMount, createEventDispatcher } from "svelte" import { FIELDS } from "constants/backend" @@ -20,8 +21,8 @@ let dispatcher = createEventDispatcher() let mode = "Form" let fieldCount = 0 - let fieldKeys = {}, - fieldTypes = {} + let fieldKeys = [], + fieldTypes = [] let keyValueOptions = [ { label: "String", value: FIELDS.STRING.type }, { label: "Number", value: FIELDS.NUMBER.type }, @@ -50,27 +51,48 @@ if (!schema) { schema = {} } - let i = 0 - for (let [key, value] of Object.entries(schema)) { - fieldKeys[i] = key - fieldTypes[i] = value.type - i++ + // find the entries which aren't in the list + const schemaEntries = Object.entries(schema).filter( + ([key]) => !fieldKeys.includes(key) + ) + for (let [key, value] of schemaEntries) { + fieldKeys.push(key) + fieldTypes.push(value.type) } - fieldCount = i + fieldCount = fieldKeys.length } function saveSchema() { - for (let i of Object.keys(fieldKeys)) { - const key = fieldKeys[i] + const newSchema = {} + for (let [index, key] of fieldKeys.entries()) { // they were added to schema, rather than generated - if (!schema[key]) { - schema[key] = { - type: fieldTypes[i], - } + newSchema[key] = { + ...schema[key], + type: fieldTypes[index], } } + dispatcher("save", { schema: newSchema, json }) + schema = newSchema + } - dispatcher("save", { schema, json }) + function removeKey(index) { + const keyToRemove = fieldKeys[index] + if (fieldKeys[index + 1] != null) { + fieldKeys[index] = fieldKeys[index + 1] + fieldTypes[index] = fieldTypes[index + 1] + } + fieldKeys.splice(index, 1) + fieldTypes.splice(index, 1) + fieldCount-- + if (json) { + try { + const parsed = JSON.parse(json) + delete parsed[keyToRemove] + json = JSON.stringify(parsed, null, 2) + } catch (err) { + // json not valid, ignore + } + } } onMount(() => { @@ -97,6 +119,7 @@ getOptionValue={field => field.value} getOptionLabel={field => field.label} /> + removeKey(i)} /> {/each}
@@ -118,9 +141,9 @@