Skip to content

Commit

Permalink
[GEN-1924]: add CRUD E2E tests for UI sources (#1958)
Browse files Browse the repository at this point in the history
This pull request includes several changes to improve the functionality
and user experience of the frontend web application. The most important
changes include adjustments to the notification toast positioning,
enhancements to the multi-source control component, updates to the
source body form, new end-to-end tests for source CRUD operations, and
improvements to the `useSourceCRUD` hook.

### UI/UX Improvements:
*
[`frontend/webapp/components/notification/toast-list.tsx`](diffhunk://#diff-c6ead7587d1e5c52295c921dde2a1a5b9b06977ab3900c7dbf475c47cc1c664cL10-R10):
Changed the position of the notification toast from the bottom to the
top of the screen.
*
[`frontend/webapp/containers/main/overview/multi-source-control/index.tsx`](diffhunk://#diff-cdd99a8fcd0484b24586f22b1f7b1bcf8d964bead53eeb0b2c07646c7301af70L60-R60):
Added a `data-id` attribute to the `Transition` component for better
identification.
*
[`frontend/webapp/containers/main/sources/update-source-body/index.tsx`](diffhunk://#diff-c5285ffb349464d72b44f6c535fbe1a02959ef2213441d34027c3fa579207565R25):
Added a `name` attribute to the input element for the source name to
improve form handling.

### Testing Enhancements:
*
[`frontend/webapp/cypress/e2e/03-sources.cy.ts`](diffhunk://#diff-981488421b684f7ca98e7e8b147b14c23fe2c57e876d88ca981c563c8ba003cdR1-R105):
Added new Cypress end-to-end tests for creating, updating, and deleting
CRDs in the cluster.

### Codebase Improvements:
*
[`frontend/webapp/hooks/sources/useSourceCRUD.ts`](diffhunk://#diff-1be86445dc0ed6bfdc08f7525c5800ef39bdcd70318a3c130f83f36f1ae9f5c8L43-R50):
Improved error handling and variable access by adding optional chaining
to the `sources` array.
  • Loading branch information
BenElferink authored Dec 9, 2024
1 parent 6ec613c commit cde5e8f
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 6 deletions.
2 changes: 1 addition & 1 deletion frontend/webapp/components/notification/toast-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NotificationNote } from '@/reuseable-components';

const Container = styled.div`
position: fixed;
bottom: 20px;
top: 14px;
left: 50%;
transform: translateX(-50%);
z-index: 10000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const MultiSourceControl = () => {

return (
<>
<Transition enter={!!totalSelected}>
<Transition data-id='multi-source-control' enter={!!totalSelected}>
<Text>Selected sources</Text>
<Badge label={totalSelected} filled />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const UpdateSourceBody: React.FC<Props> = ({ formData, handleFormChange }
return (
<Container>
<Input
name='sourceName'
title='Source name'
tooltip='This overrides the default service name that runs in your cluster.'
placeholder='Use a name that overrides the source name'
Expand Down
105 changes: 105 additions & 0 deletions frontend/webapp/cypress/e2e/03-sources.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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('Sources CRUD', () => {
const namespace = 'default';
const crdName = 'instrumentationconfigs.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-source]').click();
cy.get('[data-id=modal-Add-Source]').should('exist');
cy.get('[data-id=namespace-default]').find('[data-id=checkbox]').click();

// Wait for 3 seconds to allow the namespace & it's resources to be loaded into the UI
cy.wait(3000).then(() => {
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(5);
});
});
});
});
});

it('Should update the CRD in the cluster', () => {
cy.visit(ROUTES.OVERVIEW);

const node = cy.contains('[data-id=source-1]', 'frontend');
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=sourceName]').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);
const crdId = 'deployment-frontend';
expect(crdIds.length).to.eq(5);
expect(crdIds).includes(crdId);

cy.exec(`kubectl get ${crdName} ${crdId} -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.serviceName).to.eq('Cypress Test');
});
});
});
});

it('Should delete the CRD from the cluster', () => {
cy.visit(ROUTES.OVERVIEW);

cy.get('[data-id=source-header]').find('[data-id=checkbox]').click();
cy.get('[data-id=multi-source-control]').should('exist');
cy.get('[data-id=multi-source-control]').find('button').contains('Uninstrument').click();
cy.get('[data-id=modal]').contains('Uninstrument 5 sources').should('exist');
cy.get('[data-id=modal]').contains("You're about to uninstrument the last source").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);
});
});
});
});
4 changes: 2 additions & 2 deletions frontend/webapp/hooks/sources/useSourceCRUD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ export const useSourceCRUD = (params?: Params) => {

const [createOrDeleteSources, cdState] = useMutation<{ persistK8sSources: boolean }>(PERSIST_SOURCE, {
onError: (error, req) => {
const { selected } = req?.variables?.sources[0];
const { selected } = req?.variables?.sources?.[0] || {};
const action = selected ? ACTION.CREATE : ACTION.DELETE;

handleError(action, error.message);
},
onCompleted: (res, req) => {
const namespace = req?.variables?.namespace;
const { name, kind, selected } = req?.variables?.sources[0];
const { name, kind, selected } = req?.variables?.sources?.[0] || {};

const count = req?.variables?.sources.length;
const action = selected ? ACTION.CREATE : ACTION.DELETE;
Expand Down
2 changes: 1 addition & 1 deletion frontend/webapp/reuseable-components/checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const Checkbox: React.FC<CheckboxProps> = ({ title, titleColor, tooltip, initial
};

return (
<Container $disabled={disabled} onClick={handleToggle} style={style}>
<Container data-id={`checkbox${!!title ? `-${title}` : ''}`} $disabled={disabled} onClick={handleToggle} style={style}>
<CheckboxWrapper $isChecked={isChecked} $disabled={disabled}>
{isChecked && <Image src='/icons/common/check.svg' alt='' width={12} height={12} />}
</CheckboxWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Container = styled.div<{ $isLeaving?: boolean }>`
overflow: hidden;
padding-bottom: 1px;
border-radius: 32px;
animation: ${({ $isLeaving }) => ($isLeaving ? slide.out['bottom'] : slide.in['bottom'])} ${TRANSITION_DURATION}ms forwards;
animation: ${({ $isLeaving }) => ($isLeaving ? slide.out['right'] : slide.in['right'])} ${TRANSITION_DURATION}ms forwards;
}
`;

Expand Down

0 comments on commit cde5e8f

Please sign in to comment.