diff --git a/cli/commands/migrate_test.go b/cli/commands/migrate_test.go index a8677bee25825..15b456d68a767 100644 --- a/cli/commands/migrate_test.go +++ b/cli/commands/migrate_test.go @@ -32,6 +32,7 @@ var testMetadata = map[string][]byte{ tables: - array_relationships: [] delete_permissions: [] + event_triggers: [] insert_permissions: [] object_relationships: [] select_permissions: [] diff --git a/console/cypress/helpers/eventHelpers.js b/console/cypress/helpers/eventHelpers.js new file mode 100644 index 0000000000000..ebeaa84d3df66 --- /dev/null +++ b/console/cypress/helpers/eventHelpers.js @@ -0,0 +1,20 @@ +export const baseUrl = Cypress.config('baseUrl'); +export const queryTypes = ['insert', 'update', 'delete']; +export const getTriggerName = (i, testName = '') => + `apic_test_trigger_${testName}_${i}`; +export const getTableName = (i, testName = '') => + `apic_test_table_${testName}_${i}`; +export const getWebhookURL = () => 'http://httpbin.org/post'; +export const getNoOfRetries = () => '5'; +export const getIntervalSeconds = () => '10'; +export const getElementFromAlias = alias => `[data-test=${alias}]`; +export const makeDataAPIUrl = dataApiUrl => `${dataApiUrl}/v1/query`; +export const makeDataAPIOptions = (dataApiUrl, key, body) => ({ + method: 'POST', + url: makeDataAPIUrl(dataApiUrl), + headers: { + 'x-hasura-access-key': key, + }, + body, + failOnStatusCode: false, +}); diff --git a/console/cypress/integration/data/permissions/spec.js b/console/cypress/integration/data/permissions/spec.js index 45eac57439018..512156346529d 100644 --- a/console/cypress/integration/data/permissions/spec.js +++ b/console/cypress/integration/data/permissions/spec.js @@ -13,7 +13,7 @@ import { testPermissions, permRemove, createView, trackView } from './utils'; const testName = 'perm'; export const passPTCreateTable = () => { - // Click on create tabel + // Click on create table cy.get(getElementFromAlias('data-create-table')).click(); // Match the URL cy.url().should('eq', `${baseUrl}/data/schema/public/table/add`); diff --git a/console/cypress/integration/events/create-trigger/spec.js b/console/cypress/integration/events/create-trigger/spec.js new file mode 100644 index 0000000000000..f90422067ba8f --- /dev/null +++ b/console/cypress/integration/events/create-trigger/spec.js @@ -0,0 +1,188 @@ +import { + getElementFromAlias, + getTableName, + getTriggerName, + getWebhookURL, + getNoOfRetries, + getIntervalSeconds, + baseUrl, +} from '../../../helpers/eventHelpers'; +import { getColName } from '../../../helpers/dataHelpers'; +import { + setMetaData, + validateCT, + validateCTrigger, + validateInsert, +} from '../../validators/validators'; + +const testName = 'ctr'; // create trigger + +export const visitEventsManagePage = () => { + cy.visit('/events/manage'); +}; + +export const passPTCreateTable = () => { + // Click on create table + cy.get(getElementFromAlias('data-create-table')).click(); + // Match the URL + cy.url().should('eq', `${baseUrl}/data/schema/public/table/add`); + // Type table name + cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName)); + // Set first column + cy.get(getElementFromAlias('column-0')).type(getColName(0)); + cy.get(getElementFromAlias('col-type-0')).select('serial'); + // Set second column + cy.get(getElementFromAlias('column-1')).type(getColName(1)); + cy.get(getElementFromAlias('col-type-1')).select('integer'); + // Set third column + cy.get(getElementFromAlias('column-2')).type(getColName(2)); + cy.get(getElementFromAlias('col-type-2')).select('text'); + // Set primary key + cy.get(getElementFromAlias('primary-key-select-0')).select('0'); + // Create + cy.get(getElementFromAlias('table-create')).click(); + cy.wait(7000); + cy.url().should( + 'eq', + `${baseUrl}/data/schema/public/tables/${getTableName(0, testName)}/modify` + ); +}; + +export const checkCreateTriggerRoute = () => { + // Click on the create trigger button + cy.visit('/events/manage'); + cy.wait(15000); + cy.get(getElementFromAlias('data-create-trigger')).click(); + // Match the URL + cy.url().should('eq', `${baseUrl}/events/manage/triggers/add`); +}; + +export const failCTWithoutData = () => { + // Type trigger name + cy.get(getElementFromAlias('trigger-name')).type(getTriggerName(0, testName)); + // Click on create + cy.get(getElementFromAlias('trigger-create')).click(); + // Check if the route didn't change + cy.url().should('eq', `${baseUrl}/events/manage/triggers/add`); + // Validate + validateCT(getTriggerName(0, testName), 'failure'); +}; + +export const passCT = () => { + // Set trigger name and select table + cy.get(getElementFromAlias('trigger-name')) + .clear() + .type(getTriggerName(0, testName)); + cy.get(getElementFromAlias('select-table')).select(getTableName(0, testName)); + + // operations + cy.get(getElementFromAlias('insert-operation')).check(); + cy.get(getElementFromAlias('update-operation')).check(); + cy.get(getElementFromAlias('delete-operation')).check(); + + // webhook url + cy.get(getElementFromAlias('webhook')) + .clear() + .type(getWebhookURL()); + + // advanced settings + cy.get(getElementFromAlias('advanced-settings')).click(); + + // retry configuration + cy.get(getElementFromAlias('no-of-retries')).type(getNoOfRetries()); + cy.get(getElementFromAlias('interval-seconds')).type(getIntervalSeconds()); + + // Click on create + cy.get(getElementFromAlias('trigger-create')).click(); + cy.wait(10000); + // Check if the trigger got created and navigated to processed events page + cy.url().should( + 'eq', + `${baseUrl}/events/manage/triggers/${getTriggerName(0, testName)}/processed` + ); + cy.get(getElementFromAlias(getTriggerName(0, testName))); + // Validate + validateCTrigger(getTriggerName(0, testName), 'success'); +}; + +export const failCTDuplicateTrigger = () => { + // Visit create trigger page + cy.visit('/events/manage/triggers/add'); + // trigger and table name + cy.get(getElementFromAlias('trigger-name')) + .clear() + .type(getTriggerName(0, testName)); + cy.get(getElementFromAlias('select-table')).select(getTableName(0, testName)); + + // operations + cy.get(getElementFromAlias('insert-operation')).check(); + cy.get(getElementFromAlias('update-operation')).check(); + cy.get(getElementFromAlias('delete-operation')).check(); + + // webhook url + cy.get(getElementFromAlias('webhook')) + .clear() + .type(getWebhookURL()); + + // click on create + cy.get(getElementFromAlias('trigger-create')).click(); + cy.wait(5000); + // should be on the same URL + cy.url().should('eq', `${baseUrl}/events/manage/triggers/add`); +}; + +export const insertTableRow = () => { + // visit insert row page + cy.visit(`/data/schema/public/tables/${getTableName(0, testName)}/insert`); + // one serial column. so insert a row directly. + cy.get(getElementFromAlias(`typed-input-${1}`)).type('123'); + cy.get(getElementFromAlias(`typed-input-${2}`)).type('Some text'); + cy.get(getElementFromAlias('insert-save-button')).click(); + cy.wait(300); + validateInsert(getTableName(0, testName), 1); + // now it should invoke the trigger to webhook + cy.wait(10000); + // check if processed events has a row and it is a successful response + cy.visit(`/events/manage/triggers/${getTriggerName(0, testName)}/processed`); + cy.get(getElementFromAlias('trigger-processed-events')).contains('1'); +}; + +export const deleteCTTestTrigger = () => { + // Go to the settings section of the trigger + cy.visit(`/events/manage/triggers/${getTriggerName(0, testName)}/processed`); + // click on settings tab + cy.get(getElementFromAlias('trigger-settings')).click(); + // Click on delete + cy.get(getElementFromAlias('delete-trigger')).click(); + // Confirm + cy.on('window:confirm', str => { + expect(str === 'Are you sure?').to.be.true; + return true; + }); + cy.wait(7000); + // Match the URL + cy.url().should('eq', `${baseUrl}/events/manage/triggers`); + // Validate + validateCTrigger(getTriggerName(0, testName), 'success'); +}; + +export const deleteCTTestTable = () => { + // Go to the modify section of the table + cy.visit(`/data/schema/public/tables/${getTableName(0, testName)}/modify`); + // Click on delete + cy.get(getElementFromAlias('delete-table')).click(); + // Confirm + cy.on('window:confirm', str => { + expect(str === 'Are you sure?').to.be.true; + return true; + }); + cy.wait(7000); + // Match the URL + cy.url().should('eq', `${baseUrl}/data/schema/public`); + // Validate + validateCT(getTableName(0, testName), 'failure'); +}; + +export const setValidationMetaData = () => { + setMetaData(); +}; diff --git a/console/cypress/integration/events/create-trigger/test.js b/console/cypress/integration/events/create-trigger/test.js new file mode 100644 index 0000000000000..06569cbfc1f10 --- /dev/null +++ b/console/cypress/integration/events/create-trigger/test.js @@ -0,0 +1,51 @@ +/* eslint no-unused-vars: 0 */ +/* eslint import/prefer-default-export: 0 */ +import { testMode } from '../../../helpers/common'; +import { setMetaData } from '../../validators/validators'; + +import { + passPTCreateTable, + visitEventsManagePage, + checkCreateTriggerRoute, + failCTWithoutData, + passCT, + failCTDuplicateTrigger, + failAddExistingTrigger, + insertTableRow, + deleteCTTestTrigger, + deleteCTTestTable, +} from './spec'; + +const setup = () => { + describe('Check Data Tab', () => { + it('Clicking on Data tab opens the correct route', () => { + // Visit the index route + cy.visit('/data/schema/public'); + cy.wait(7000); + // Get and set validation metadata + setMetaData(); + }); + }); +}; + +export const runCreateTriggerTests = () => { + describe('Create Trigger', () => { + it('Create table to use in triggers', passPTCreateTable); + it('Visit events manage page', visitEventsManagePage); + it( + 'Create trigger button opens the correct route', + checkCreateTriggerRoute + ); + it('Fails to create trigger without data', failCTWithoutData); + it('Successfuly creates trigger', passCT); + it('Fails to create duplicate trigger', failCTDuplicateTrigger); + it('Insert a row and invoke trigger', insertTableRow); + it('Delete off the test trigger', deleteCTTestTrigger); + it('Delete off the test table', deleteCTTestTable); + }); +}; + +if (testMode !== 'cli') { + setup(); + runCreateTriggerTests(); +} diff --git a/console/cypress/integration/test_complete.js b/console/cypress/integration/test_complete.js index 5ebb4eed14d6a..2dfe66e6a6733 100644 --- a/console/cypress/integration/test_complete.js +++ b/console/cypress/integration/test_complete.js @@ -10,6 +10,8 @@ import { runViewsTest } from './data/views/test'; import { runRawSQLTests } from './data/raw-sql/test'; import { run404Test } from './data/404/test'; +import { runCreateTriggerTests } from './events/create-trigger/test'; + import { runApiExplorerTests } from './api-explorer/graphql/test'; const setup = () => { @@ -28,6 +30,8 @@ describe('Setup route', setup); runMigrationModeTests(); +runCreateTriggerTests(); + runCreateTableTests(); runInsertBrowseTests(); diff --git a/console/cypress/integration/validators/validators.js b/console/cypress/integration/validators/validators.js index a2cde49b609cc..8fd08d02b8a62 100644 --- a/console/cypress/integration/validators/validators.js +++ b/console/cypress/integration/validators/validators.js @@ -216,3 +216,24 @@ export const validateMigrationMode = mode => { expect(response.body.migration_mode == mode.toString()).to.be.true; // eslint-disable-line }); }; + +// ****************** Trigger Validator ********************* + +export const validateCTrigger = (triggerName, result) => { + const reqBody = { + type: 'select', + args: { + table: { name: 'event_triggers', schema: 'hdb_catalog' }, + columns: ['table_name'], + where: { name: triggerName }, + }, + }; + const requestOptions = makeDataAPIOptions(dataApiUrl, accessKey, reqBody); + cy.request(requestOptions).then(response => { + if (result === 'success') { + expect(response.status === 200).to.be.true; + } else { + expect(response.status === 200).to.be.false; + } + }); +}; diff --git a/console/src/components/Common/Common.scss b/console/src/components/Common/Common.scss index b2b6d1363fc7d..b450c80bbb0e6 100644 --- a/console/src/components/Common/Common.scss +++ b/console/src/components/Common/Common.scss @@ -466,9 +466,13 @@ input { { margin-bottom: 20px; } +.add_mar_bottom_mid +{ + margin-bottom: 10px; +} .add_mar_right { - margin-right: 20px; + margin-right: 20px !important; } .add_mar_right_small { diff --git a/console/src/components/Main/Main.js b/console/src/components/Main/Main.js index 84ab629d3375b..fbac26730749e 100644 --- a/console/src/components/Main/Main.js +++ b/console/src/components/Main/Main.js @@ -163,6 +163,27 @@ class Main extends React.Component { + +
  • + +
    +