diff --git a/frontend/webapp/components/overview/add-entity/index.tsx b/frontend/webapp/components/overview/add-entity/index.tsx index 1480e50da..b9c449995 100644 --- a/frontend/webapp/components/overview/add-entity/index.tsx +++ b/frontend/webapp/components/overview/add-entity/index.tsx @@ -90,7 +90,7 @@ const AddEntity: React.FC = ({ options = DEFAULT_O return ( - + {isPolling ? : Add} {placeholder} @@ -98,7 +98,7 @@ const AddEntity: React.FC = ({ options = DEFAULT_O {isDropdownOpen && ( {options.map((option) => ( - handleSelect(option)}> + handleSelect(option)}> {`Add {option.value} diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx index 80eaeaf54..ac4736b17 100644 --- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx @@ -92,7 +92,7 @@ export const DestinationListItem: React.FC = ({ item, }; return ( - onSelect(item)}> + onSelect(item)}> destination diff --git a/frontend/webapp/containers/main/overview/overview-drawer/drawer-footer/index.tsx b/frontend/webapp/containers/main/overview/overview-drawer/drawer-footer/index.tsx index 2009a67c9..2ee92a343 100644 --- a/frontend/webapp/containers/main/overview/overview-drawer/drawer-footer/index.tsx +++ b/frontend/webapp/containers/main/overview/overview-drawer/drawer-footer/index.tsx @@ -44,15 +44,15 @@ const DrawerFooter: React.FC = ({ isOpen, onSave, saveLabel = 'Save', onC return ( - + {saveLabel} - + {cancelLabel} - + Delete {deleteLabel} diff --git a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx index 427002838..ea94656d2 100644 --- a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx +++ b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx @@ -100,19 +100,19 @@ const DrawerHeader = forwardRef(({ title, ti {/* "titleTooltip" is currently used only by sources, if we add tooltip to other entities we will have to define a "hideTitleInput" prop */} {isEdit && !titleTooltip && ( - setInputValue(e.target.value)} /> + setInputValue(e.target.value)} /> )} {!isEdit && ( - + Edit Edit )} - - Close + + Close diff --git a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx index 1b95461fc..2788b5069 100644 --- a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx +++ b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx @@ -136,7 +136,7 @@ export const SourcesList: React.FC = ({ const hasFilteredSources = !!filtered.length; return ( - + onSelectNamespace(namespace)}> onSelectAll(bool, namespace)} /> diff --git a/frontend/webapp/cypress/e2e/01-connection.cy.ts b/frontend/webapp/cypress/e2e/01-connection.cy.ts index c9fdd70c0..8b92877ea 100644 --- a/frontend/webapp/cypress/e2e/01-connection.cy.ts +++ b/frontend/webapp/cypress/e2e/01-connection.cy.ts @@ -1,3 +1,5 @@ +import { ROUTES } from '../../utils/constants/routes'; + describe('Root Connection', () => { it('Should fetch a config with GraphQL. A redirect of any kind confirms Frontend + Backend connections.', () => { cy.intercept('/graphql').as('gql'); @@ -6,7 +8,7 @@ describe('Root Connection', () => { cy.wait('@gql').then(() => { cy.location().should((loc) => { // If GraphQL failed to fetch the config, the app will remain on "/", thereby failing the test. - expect(loc.pathname).to.be.oneOf(['/choose-sources', '/overview']); + expect(loc.pathname).to.be.oneOf([ROUTES.CHOOSE_SOURCES, ROUTES.OVERVIEW]); }); }); }); diff --git a/frontend/webapp/cypress/e2e/02-onboarding.cy.ts b/frontend/webapp/cypress/e2e/02-onboarding.cy.ts index a3ceafa1f..783e7ee8f 100644 --- a/frontend/webapp/cypress/e2e/02-onboarding.cy.ts +++ b/frontend/webapp/cypress/e2e/02-onboarding.cy.ts @@ -1,42 +1,38 @@ import { ROUTES } from '../../utils/constants/routes'; describe('Onboarding', () => { - it('Should contain at least a "default" namespace', () => { + beforeEach(() => { cy.intercept('/graphql').as('gql'); - cy.visit(ROUTES.CHOOSE_SOURCES); + }); + it('Should contain at least a "default" namespace', () => { + cy.visit(ROUTES.CHOOSE_SOURCES); cy.wait('@gql').then(() => { - expect('#namespace-default').to.exist; + cy.get('[data-id=namespace-default]').contains('default').should('exist'); }); }); it('Should contain at least a "Jaeger" destination', () => { - cy.intercept('/graphql').as('gql'); cy.visit(ROUTES.CHOOSE_DESTINATION); - + cy.contains('button', 'ADD DESTINATION').click(); cy.wait('@gql').then(() => { - cy.contains('button', 'ADD DESTINATION').click(); - expect('#destination-jaeger').to.exist; + cy.get('[data-id=destination-jaeger]').contains('Jaeger').should('exist'); }); }); it('Should autocomplete the "Jaeger" destination', () => { - cy.intercept('/graphql').as('gql'); cy.visit(ROUTES.CHOOSE_DESTINATION); - + cy.contains('button', 'ADD DESTINATION').click(); cy.wait('@gql').then(() => { - cy.contains('button', 'ADD DESTINATION').click(); - cy.get('#destination-jaeger').click(); - expect('#JAEGER_URL').to.not.be.empty; + cy.get('[data-id=destination-jaeger]').contains('Jaeger').click(); + expect('[data-id=JAEGER_URL]').to.not.be.empty; }); }); it('Should allow the user to pass every step, and end-up on the "Overview" page.', () => { cy.visit(ROUTES.CHOOSE_SOURCES); - cy.contains('button', 'NEXT').click(); cy.location('pathname').should('eq', ROUTES.CHOOSE_DESTINATION); - cy.contains('button', 'DONE').click(); cy.location('pathname').should('eq', ROUTES.OVERVIEW); }); diff --git a/frontend/webapp/cypress/e2e/05-actions.cy.ts b/frontend/webapp/cypress/e2e/05-actions.cy.ts new file mode 100644 index 000000000..551c4c182 --- /dev/null +++ b/frontend/webapp/cypress/e2e/05-actions.cy.ts @@ -0,0 +1,103 @@ +import { ROUTES } from '../../utils/constants/routes'; + +// The number of CRDs that exist in the cluster before running any tests should be 0. +// Tests will fail if you have existing CRDs in the cluster. +// If you have to run tests locally, make sure to clean up the cluster before running the tests. + +describe('Actions CRUD', () => { + const namespace = 'odigos-system'; + const crdName = 'piimaskings.actions.odigos.io'; + const noResourcesFound = `No resources found in ${namespace} namespace.`; + + beforeEach(() => { + cy.intercept('/graphql').as('gql'); + }); + + it('Should create a CRD in the cluster', () => { + cy.visit(ROUTES.OVERVIEW); + + cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListBefore) => { + expect(crdListBefore.stderr).to.eq(noResourcesFound); + expect(crdListBefore.stdout).to.eq(''); + + const crdIdsBefore = crdListBefore.stdout.split('\n').filter((str) => !!str); + expect(crdIdsBefore.length).to.eq(0); + + cy.get('[data-id=add-entity]').click(); + cy.get('[data-id=add-action]').click(); + cy.get('[data-id=modal-Add-Action]').should('exist'); + cy.get('[data-id=modal-Add-Action]').find('input').should('have.attr', 'placeholder', 'Type to search...').click(); + cy.get('[data-id=option-pii-masking]').click(); + cy.get('button').contains('DONE').click(); + + cy.wait('@gql').then(() => { + cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListAfter) => { + expect(crdListAfter.stderr).to.eq(''); + expect(crdListAfter.stdout).to.not.be.empty; + + const crdIdsAfter = crdListAfter.stdout.split('\n').filter((str) => !!str); + expect(crdIdsAfter.length).to.eq(1); + }); + }); + }); + }); + + it('Should update the CRD in the cluster', () => { + cy.visit(ROUTES.OVERVIEW); + + const node = cy.contains('[data-id=action-0]', 'PiiMasking'); + expect(node).to.exist; + node.click(); + + cy.get('[data-id=drawer]').should('exist'); + cy.get('button[data-id=drawer-edit]').click(); + cy.get('input[data-id=title]').clear().type('Cypress Test'); + cy.get('button[data-id=drawer-save]').click(); + cy.get('button[data-id=drawer-close]').click(); + + cy.wait('@gql').then(() => { + cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { + expect(crdList.stderr).to.eq(''); + expect(crdList.stdout).to.not.be.empty; + + const crdIds = crdList.stdout.split('\n').filter((str) => !!str); + expect(crdIds.length).to.eq(1); + + cy.exec(`kubectl get ${crdName} ${crdIds[0]} -n ${namespace} -o json`).then((crd) => { + expect(crd.stderr).to.eq(''); + expect(crd.stdout).to.not.be.empty; + + const parsed = JSON.parse(crd.stdout); + const { spec } = parsed?.items?.[0] || parsed || {}; + + expect(spec).to.not.be.empty; + expect(spec.actionName).to.eq('Cypress Test'); + }); + }); + }); + }); + + it('Should delete the CRD from the cluster', () => { + cy.visit(ROUTES.OVERVIEW); + + const node = cy.contains('[data-id=action-0]', 'PiiMasking'); + expect(node).to.exist; + node.click(); + + cy.get('[data-id=drawer]').should('exist'); + cy.get('button[data-id=drawer-edit]').click(); + cy.get('button[data-id=drawer-delete]').click(); + cy.get('[data-id=modal]').contains('Delete action').should('exist'); + cy.get('button[data-id=approve]').click(); + + cy.wait('@gql').then(() => { + cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { + expect(crdList.stderr).to.eq(noResourcesFound); + expect(crdList.stdout).to.eq(''); + + const crdIds = crdList.stdout.split('\n').filter((str) => !!str); + expect(crdIds.length).to.eq(0); + }); + }); + }); +}); diff --git a/frontend/webapp/reuseable-components/auto-complete-input/index.tsx b/frontend/webapp/reuseable-components/auto-complete-input/index.tsx index 74bf32836..b37f2a019 100644 --- a/frontend/webapp/reuseable-components/auto-complete-input/index.tsx +++ b/frontend/webapp/reuseable-components/auto-complete-input/index.tsx @@ -119,7 +119,7 @@ const OptionItem: FC = ({ option, isActive, renderIcon = true, const hasSubItems = !!option.items && option.items.length > 0; return ( - (hasSubItems ? null : onClick(option))}> + (hasSubItems ? null : onClick(option))}> {option.icon && renderIcon && } diff --git a/frontend/webapp/reuseable-components/drawer/index.tsx b/frontend/webapp/reuseable-components/drawer/index.tsx index fef39cd29..ddc16acab 100644 --- a/frontend/webapp/reuseable-components/drawer/index.tsx +++ b/frontend/webapp/reuseable-components/drawer/index.tsx @@ -43,7 +43,7 @@ export const Drawer: React.FC = ({ isOpen, onClose, position = 'right', w <>