From 81d62064050d09736a0e46cf0bdba1294d696e9f Mon Sep 17 00:00:00 2001 From: lgalis Date: Thu, 22 Aug 2024 17:06:37 -0400 Subject: [PATCH 1/5] Add User and team RBAC tabs --- .../EdaSelectResourcesStep.tsx | 6 +- .../access/roles/hooks/useEdaRoleMetadata.tsx | 4 + .../EventStreamPage/EventStreamPage.tsx | 2 + .../EventStreamPage/EventStreamTeamAccess.tsx | 15 ++ .../EventStreamPage/EventStreamUserAccess.tsx | 15 ++ .../components/EdaEventStreamAddTeams.cy.tsx | 91 ++++++++++ .../components/EdaEventStreamAddTeams.tsx | 158 +++++++++++++++++ .../components/EdaEventStreamAddUsers.cy.tsx | 93 ++++++++++ .../components/EdaEventStreamAddUsers.tsx | 159 ++++++++++++++++++ frontend/eda/main/EdaRoutes.tsx | 4 + frontend/eda/main/useEdaNavigation.tsx | 24 +++ 11 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 frontend/eda/event-streams/EventStreamPage/EventStreamTeamAccess.tsx create mode 100644 frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx create mode 100644 frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx create mode 100644 frontend/eda/event-streams/components/EdaEventStreamAddTeams.tsx create mode 100644 frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx create mode 100644 frontend/eda/event-streams/components/EdaEventStreamAddUsers.tsx diff --git a/frontend/eda/access/common/EdaRolesWizardSteps/EdaSelectResourcesStep.tsx b/frontend/eda/access/common/EdaRolesWizardSteps/EdaSelectResourcesStep.tsx index b04fbe8c72..5ef8720297 100644 --- a/frontend/eda/access/common/EdaRolesWizardSteps/EdaSelectResourcesStep.tsx +++ b/frontend/eda/access/common/EdaRolesWizardSteps/EdaSelectResourcesStep.tsx @@ -15,6 +15,7 @@ import { EdaCredentialType } from '../../../interfaces/EdaCredentialType'; import { useEdaMultiSelectListView } from '../../../common/useEdaMultiSelectListView'; import { edaAPI } from '../../../common/eda-utils'; import styled from 'styled-components'; +import { EdaEventStream } from '../../../interfaces/EdaEventStream'; export type EdaResourceType = | EdaActivationInstance @@ -24,7 +25,8 @@ export type EdaResourceType = | EdaRulebookActivation | EdaRuleAudit | EdaProject - | EdaCredentialType; + | EdaCredentialType + | EdaEventStream; const resourceToEndpointMapping: { [key: string]: string } = { 'eda.edacredential': edaAPI`/eda-credentials/`, @@ -35,6 +37,7 @@ const resourceToEndpointMapping: { [key: string]: string } = { 'eda.credentialtype': edaAPI`/credential-types/`, 'eda.decisionenvironment': edaAPI`/decision-environments/`, 'eda.auditrule': edaAPI`/audit-rules/`, + 'eda.eventstream': edaAPI`/event-streams/`, }; const StyledTitle = styled(Title)` @@ -57,6 +60,7 @@ export function EdaSelectResourcesStep() { 'eda.credentialtype': t('Select credential types'), 'eda.decisionenvironment': t('Select decision environments'), 'eda.auditrule': t('Select audit rules'), + 'eda.eventstream': t('Select event stream'), }; }, [t]); const tableColumns = useMemo[]>( diff --git a/frontend/eda/access/roles/hooks/useEdaRoleMetadata.tsx b/frontend/eda/access/roles/hooks/useEdaRoleMetadata.tsx index c650ea66d7..5487d64808 100644 --- a/frontend/eda/access/roles/hooks/useEdaRoleMetadata.tsx +++ b/frontend/eda/access/roles/hooks/useEdaRoleMetadata.tsx @@ -130,6 +130,10 @@ export function useEdaRoleMetadata(): EdaRoleMetadata { 'shared.change_team': t('Change team'), 'shared.delete_team': t('Delete team'), 'shared.view_team': t('View team'), + 'eda.add_eventstream': t('Add event stream'), + 'eda.change_eventstream': t('Change event stream'), + 'eda.delete_eventstream': t('Delete event stream'), + 'eda.view_eventstream': t('View event stream'), }, }, 'eda.project': { diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx index 17b859bc95..f6333037ab 100644 --- a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx @@ -160,6 +160,8 @@ export function EventStreamPage() { tabs={[ { label: t('Details'), page: EdaRoute.EventStreamDetails }, { label: t('Activations'), page: EdaRoute.EventStreamActivations }, + { label: t('Team Access'), page: EdaRoute.EventStreamTeamAccess }, + { label: t('User Access'), page: EdaRoute.EventStreamUserAccess }, ]} params={{ id: eventStream?.id }} /> diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamTeamAccess.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamTeamAccess.tsx new file mode 100644 index 0000000000..53bda1620e --- /dev/null +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamTeamAccess.tsx @@ -0,0 +1,15 @@ +import { useParams } from 'react-router-dom'; +import { EdaRoute } from '../../main/EdaRoutes'; +import { TeamAccess } from '../../../common/access/components/TeamAccess'; + +export function EventStreamTeamAccess() { + const params = useParams<{ id: string }>(); + return ( + + ); +} diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx new file mode 100644 index 0000000000..85ffc5422d --- /dev/null +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx @@ -0,0 +1,15 @@ +import { useParams } from 'react-router-dom'; +import { EdaRoute } from '../../main/EdaRoutes'; +import { UserAccess } from '../../../common/access/components/UserAccess'; + +export function EventStreamUserAccess() { + const params = useParams<{ id: string }>(); + return ( + + ); +} diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx new file mode 100644 index 0000000000..0f9b44b4ed --- /dev/null +++ b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx @@ -0,0 +1,91 @@ +import { edaAPI } from '../../common/eda-utils'; +import { EdaEventStreamAddTeams } from './EdaEventStreamAddTeams'; + +describe('EdaEventStreamAddTeams', () => { + const component = ; + const path = '/event-streams/:id/team-access/add-teams'; + const initialEntries = [`/event-streams/1/team-access/add-teams`]; + const params = { + path, + initialEntries, + }; + + beforeEach(() => { + cy.intercept('GET', edaAPI`/event-streams/*`, { fixture: 'edaEventStream.json' }); + cy.intercept('GET', edaAPI`/teams/?order_by=name*`, { fixture: 'edaTeams.json' }); + cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=event-stream*`, { + fixture: 'edaEventStreamRoles.json', + }); + cy.mount(component, params); + }); + it('should render with correct steps', () => { + cy.get('[data-cy="wizard-nav"] li').eq(0).should('contain.text', 'Select team(s)'); + cy.get('[data-cy="wizard-nav"] li').eq(1).should('contain.text', 'Select roles to apply'); + cy.get('[data-cy="wizard-nav"] li').eq(2).should('contain.text', 'Review'); + cy.get('[data-cy="wizard-nav-item-teams"] button').should('have.class', 'pf-m-current'); + cy.get('table tbody').find('tr').should('have.length', 4); + }); + it('can filter teams by name', () => { + cy.intercept(edaAPI`/teams/?name=Gal*`, { fixtures: 'edaTeams.json' }).as('nameFilterRequest'); + cy.filterTableByText('Gal'); + cy.wait('@nameFilterRequest'); + cy.clearAllFilters(); + }); + it('should validate that at least one team is selected for moving to next step', () => { + cy.get('table tbody').find('tr').should('have.length', 4); + cy.clickButton(/^Next$/); + cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one team.'); + cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-teams"] button').should('not.have.class', 'pf-m-current'); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); + }); + it('should validate that at least one role is selected for moving to Review step', () => { + cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); + cy.clickButton(/^Next$/); + cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one role.'); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('not.have.class', 'pf-m-current'); + cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); + }); + it('should display selected team and role in the Review step', () => { + cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); + cy.get('[data-cy="expandable-section-teams"]').should('contain.text', 'Teams'); + cy.get('[data-cy="expandable-section-teams"]').should('contain.text', '1'); + cy.get('[data-cy="expandable-section-teams"]').should('contain.text', 'Demo'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Roles'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', '1'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'EventStream Admin'); + cy.get('[data-cy="expandable-section-edaRoles"]').should( + 'contain.text', + 'Has all permissions to a single event-stream and its child resources - rulebook' + ); + }); + it('should trigger bulk action dialog on submit', () => { + cy.intercept('POST', edaAPI`/role_team_assignments/`, { + statusCode: 201, + body: { team: 3, role_definition: 14, content_type: 'eda.event-stream', object_id: 1 }, + }).as('createRoleAssignment'); + cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.clickButton(/^Finish$/); + cy.wait('@createRoleAssignment'); + // Bulk action modal is displayed with success + cy.get('.pf-v5-c-modal-box').within(() => { + cy.get('table tbody').find('tr').should('have.length', 1); + cy.get('table tbody').should('contain.text', 'Demo'); + cy.get('table tbody').should('contain.text', 'EventStream Admin'); + cy.get('div.pf-v5-c-progress__description').should('contain.text', 'Success'); + cy.get('div.pf-v5-c-progress__status').should('contain.text', '100%'); + }); + }); +}); diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddTeams.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.tsx new file mode 100644 index 0000000000..62e198281c --- /dev/null +++ b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.tsx @@ -0,0 +1,158 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { + LoadingPage, + PageHeader, + PageLayout, + PageWizard, + PageWizardStep, + useGetPageUrl, + usePageNavigate, +} from '../../../../framework'; +import { RoleAssignmentsReviewStep } from '../../../common/access/RolesWizard/steps/RoleAssignmentsReviewStep'; +import { postRequest } from '../../../common/crud/Data'; +import { useGet } from '../../../common/crud/useGet'; +import { EdaSelectRolesStep } from '../../access/common/EdaRolesWizardSteps/EdaSelectRolesStep'; +import { EdaSelectTeamsStep } from '../../access/common/EdaRolesWizardSteps/EdaSelectTeamsStep'; +import { edaAPI } from '../../common/eda-utils'; +import { edaErrorAdapter } from '../../common/edaErrorAdapter'; +import { useEdaBulkActionDialog } from '../../common/useEdaBulkActionDialog'; +import { EdaEventStream } from '../../interfaces/EdaEventStream'; +import { EdaRbacRole } from '../../interfaces/EdaRbacRole'; +import { EdaTeam } from '../../interfaces/EdaTeam'; +import { EdaRoute } from '../../main/EdaRoutes'; + +interface WizardFormValues { + teams: EdaTeam[]; + edaRoles: EdaRbacRole[]; +} + +interface TeamRolePair { + team: EdaTeam; + role: EdaRbacRole; +} + +export function EdaEventStreamAddTeams() { + const { t } = useTranslation(); + const getPageUrl = useGetPageUrl(); + const params = useParams<{ id: string }>(); + const pageNavigate = usePageNavigate(); + const { data: eventstream, isLoading } = useGet( + edaAPI`/event-streams/${params.id ?? ''}/` + ); + const teamRoleProgressDialog = useEdaBulkActionDialog(); + + if (isLoading || !eventstream) return ; + + const steps: PageWizardStep[] = [ + { + id: 'teams', + label: t('Select team(s)'), + inputs: ( + + ), + validate: (formData, _) => { + const { teams } = formData as { teams: EdaTeam[] }; + if (!teams?.length) { + throw new Error(t('Select at least one team.')); + } + }, + }, + { + id: 'roles', + label: t('Select roles to apply'), + inputs: ( + + ), + validate: (formData, _) => { + const { edaRoles } = formData as { edaRoles: EdaRbacRole[] }; + if (!edaRoles?.length) { + throw new Error(t('Select at least one role.')); + } + }, + }, + { + id: 'review', + label: t('Review'), + inputs: , + }, + ]; + + const onSubmit = async (data: WizardFormValues) => { + const { teams, edaRoles } = data; + const items: TeamRolePair[] = []; + for (const team of teams) { + for (const role of edaRoles) { + items.push({ team, role }); + } + } + return new Promise((resolve) => { + teamRoleProgressDialog({ + title: t('Add roles'), + keyFn: ({ team, role }) => `${team.id}_${role.id}`, + items, + actionColumns: [ + { header: t('Team'), cell: ({ team }) => team.name }, + { header: t('Role'), cell: ({ role }) => role.name }, + ], + actionFn: ({ team, role }) => + postRequest(edaAPI`/role_team_assignments/`, { + team: team.id, + role_definition: role.id, + content_type: 'eda.eventstream', + object_id: eventstream.id, + }), + onComplete: () => { + resolve(); + }, + onClose: () => { + pageNavigate(EdaRoute.EventStreamTeamAccess, { + params: { id: eventstream.id.toString() }, + }); + }, + }); + }); + }; + + return ( + + + + errorAdapter={edaErrorAdapter} + steps={steps} + onSubmit={onSubmit} + disableGrid + onCancel={() => { + pageNavigate(EdaRoute.EventStreamTeamAccess, { params: { id: eventstream?.id } }); + }} + /> + + ); +} diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx new file mode 100644 index 0000000000..e989471330 --- /dev/null +++ b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx @@ -0,0 +1,93 @@ +import { edaAPI } from '../../common/eda-utils'; +import { EdaEventStreamAddUsers } from './EdaEventStreamAddUsers'; + +describe('EdaEventStreamAddUsers', () => { + const component = ; + const path = '/event-streams/:id/user-access/add-users'; + const initialEntries = [`/event-streams/1/user-access/add-users`]; + const params = { + path, + initialEntries, + }; + + beforeEach(() => { + cy.intercept('GET', edaAPI`/event-streams/*`, { fixture: 'edaEventStream.json' }); + cy.intercept('GET', edaAPI`/users/*`, { fixture: 'edaNormalUsers.json' }); + cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=event-stream*`, { + fixture: 'edaEventStreamRoles.json', + }); + cy.mount(component, params); + }); + it('should render with correct steps', () => { + cy.get('[data-cy="wizard-nav"] li').eq(0).should('contain.text', 'Select user(s)'); + cy.get('[data-cy="wizard-nav"] li').eq(1).should('contain.text', 'Select roles to apply'); + cy.get('[data-cy="wizard-nav"] li').eq(2).should('contain.text', 'Review'); + cy.get('[data-cy="wizard-nav-item-users"] button').should('have.class', 'pf-m-current'); + cy.get('table tbody').find('tr').should('have.length', 2); + }); + it('can filter users by username', () => { + cy.intercept(edaAPI`/users/?is_superuser=false&name=demo*`, { + fixture: 'edaNormalUsers.json', + }).as('nameFilterRequest'); + cy.filterTableByText('demo'); + cy.wait('@nameFilterRequest'); + cy.clearAllFilters(); + }); + it('should validate that at least one user is selected for moving to next step', () => { + cy.get('table tbody').find('tr').should('have.length', 2); + cy.clickButton(/^Next$/); + cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one user.'); + cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-users"] button').should('not.have.class', 'pf-m-current'); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); + }); + it('should validate that at least one role is selected for moving to Review step', () => { + cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); + cy.clickButton(/^Next$/); + cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one role.'); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-roles"] button').should('not.have.class', 'pf-m-current'); + cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); + }); + it('should display selected user and role in the Review step', () => { + cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); + cy.get('[data-cy="expandable-section-users"]').should('contain.text', 'Users'); + cy.get('[data-cy="expandable-section-users"]').should('contain.text', '1'); + cy.get('[data-cy="expandable-section-users"]').should('contain.text', 'demo-user'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Roles'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', '1'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'EventStream Admin'); + cy.get('[data-cy="expandable-section-edaRoles"]').should( + 'contain.text', + 'Has all permissions to a single event-stream and its child resources - rulebook' + ); + }); + it('should trigger bulk action dialog on submit', () => { + cy.intercept('POST', edaAPI`/role_user_assignments/`, { + statusCode: 201, + body: { user: 5, role_definition: 14, content_type: 'eda.event-stream', object_id: 1 }, + }).as('createRoleAssignment'); + cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.clickButton(/^Next$/); + cy.clickButton(/^Finish$/); + cy.wait('@createRoleAssignment'); + // Bulk action modal is displayed with success + cy.get('.pf-v5-c-modal-box').within(() => { + cy.get('table tbody').find('tr').should('have.length', 1); + cy.get('table tbody').should('contain.text', 'demo-user'); + cy.get('table tbody').should('contain.text', 'EventStream Admin'); + cy.get('div.pf-v5-c-progress__description').should('contain.text', 'Success'); + cy.get('div.pf-v5-c-progress__status').should('contain.text', '100%'); + }); + }); +}); diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddUsers.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.tsx new file mode 100644 index 0000000000..25fe2acd55 --- /dev/null +++ b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.tsx @@ -0,0 +1,159 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { + LoadingPage, + PageHeader, + PageLayout, + PageWizard, + PageWizardStep, + useGetPageUrl, + usePageNavigate, +} from '../../../../framework'; +import { RoleAssignmentsReviewStep } from '../../../common/access/RolesWizard/steps/RoleAssignmentsReviewStep'; +import { postRequest } from '../../../common/crud/Data'; +import { useGet } from '../../../common/crud/useGet'; +import { EdaSelectRolesStep } from '../../access/common/EdaRolesWizardSteps/EdaSelectRolesStep'; +import { EdaSelectUsersStep } from '../../access/common/EdaRolesWizardSteps/EdaSelectUsersStep'; +import { edaAPI } from '../../common/eda-utils'; +import { edaErrorAdapter } from '../../common/edaErrorAdapter'; +import { useEdaBulkActionDialog } from '../../common/useEdaBulkActionDialog'; +import { EdaEventStream } from '../../interfaces/EdaEventStream'; +import { EdaRbacRole } from '../../interfaces/EdaRbacRole'; +import { EdaUser } from '../../interfaces/EdaUser'; +import { EdaRoute } from '../../main/EdaRoutes'; + +interface WizardFormValues { + users: EdaUser[]; + edaRoles: EdaRbacRole[]; +} + +interface UserRolePair { + user: EdaUser; + role: EdaRbacRole; +} + +export function EdaEventStreamAddUsers() { + const { t } = useTranslation(); + const getPageUrl = useGetPageUrl(); + const params = useParams<{ id: string }>(); + + const { data: eventstream, isLoading } = useGet( + edaAPI`/event-streams/${params.id ?? ''}/` + ); + const userProgressDialog = useEdaBulkActionDialog(); + const pageNavigate = usePageNavigate(); + + if (isLoading || !eventstream) return ; + + const steps: PageWizardStep[] = [ + { + id: 'users', + label: t('Select user(s)'), + inputs: ( + + ), + validate: (formData, _) => { + const { users } = formData as { users: EdaUser[] }; + if (!users?.length) { + throw new Error(t('Select at least one user.')); + } + }, + }, + { + id: 'roles', + label: t('Select roles to apply'), + inputs: ( + + ), + validate: (formData, _) => { + const { edaRoles } = formData as { edaRoles: EdaRbacRole[] }; + if (!edaRoles?.length) { + throw new Error(t('Select at least one role.')); + } + }, + }, + { + id: 'review', + label: t('Review'), + inputs: , + }, + ]; + + const onSubmit = (data: WizardFormValues) => { + const { users, edaRoles } = data; + const items: UserRolePair[] = []; + for (const user of users) { + for (const role of edaRoles) { + items.push({ user, role }); + } + } + return new Promise((resolve) => { + userProgressDialog({ + title: t('Add roles'), + keyFn: ({ user, role }) => `${user.id}_${role.id}`, + items, + actionColumns: [ + { header: t('User'), cell: ({ user }) => user.username }, + { header: t('Role'), cell: ({ role }) => role.name }, + ], + actionFn: ({ user, role }) => + postRequest(edaAPI`/role_user_assignments/`, { + user: user.id, + role_definition: role.id, + content_type: 'eda.eventstream', + object_id: eventstream.id, + }), + onComplete: () => { + resolve(); + }, + onClose: () => { + pageNavigate(EdaRoute.EventStreamUserAccess, { + params: { id: eventstream.id.toString() }, + }); + }, + }); + }); + }; + + return ( + + + + errorAdapter={edaErrorAdapter} + steps={steps} + onSubmit={onSubmit} + disableGrid + onCancel={() => { + pageNavigate(EdaRoute.EventStreamUserAccess, { params: { id: eventstream?.id } }); + }} + /> + + ); +} diff --git a/frontend/eda/main/EdaRoutes.tsx b/frontend/eda/main/EdaRoutes.tsx index c41be31751..62b4cfff0a 100644 --- a/frontend/eda/main/EdaRoutes.tsx +++ b/frontend/eda/main/EdaRoutes.tsx @@ -92,6 +92,10 @@ export enum EdaRoute { EventStreamPage = 'eda-event-stream-page', EventStreamDetails = 'eda-event-stream-details', EventStreamActivations = 'eda-event-stream-activations', + EventStreamTeamAccess = 'eda-event-stream-team-access', + EventStreamUserAccess = 'eda-event-stream-user-access', + EventStreamAddTeams = 'eda-event-stream-add-teams', + EventStreamAddUsers = 'eda-event-stream-add-users', Settings = 'eda-settings', SettingsPreferences = 'eda-settings-preferences', diff --git a/frontend/eda/main/useEdaNavigation.tsx b/frontend/eda/main/useEdaNavigation.tsx index b046a3e5ff..a4cd6ca44d 100644 --- a/frontend/eda/main/useEdaNavigation.tsx +++ b/frontend/eda/main/useEdaNavigation.tsx @@ -82,6 +82,10 @@ import { EventStreams } from '../event-streams/EventStreams'; import { EdaRoute } from './EdaRoutes'; import { useEdaOrganizationRoutes } from './routes/useEdaOrganizationsRoutes'; import { EventStreamActivations } from '../event-streams/EventStreamPage/EventStreamActivations'; +import { EdaEventStreamAddTeams } from '../event-streams/components/EdaEventStreamAddTeams'; +import { EdaEventStreamAddUsers } from '../event-streams/components/EdaEventStreamAddUsers'; +import { EventStreamUserAccess } from '../event-streams/EventStreamPage/EventStreamUserAccess'; +import { EventStreamTeamAccess } from '../event-streams/EventStreamPage/EventStreamTeamAccess'; export function useEdaNavigation() { const { t } = useTranslation(); @@ -347,12 +351,32 @@ export function useEdaNavigation() { path: 'activations', element: , }, + { + id: EdaRoute.EventStreamTeamAccess, + path: 'team-access', + element: , + }, + { + id: EdaRoute.EventStreamUserAccess, + path: 'user-access', + element: , + }, { path: '', element: , }, ], }, + { + id: EdaRoute.EventStreamAddUsers, + path: ':id/user-access/add', + element: , + }, + { + id: EdaRoute.EventStreamAddTeams, + path: ':id/team-access/add', + element: , + }, { path: '', element: , From 5c7683a42744095e1c06e6b726e42ac7759204ad Mon Sep 17 00:00:00 2001 From: lgalis Date: Mon, 26 Aug 2024 14:07:16 -0400 Subject: [PATCH 2/5] Update empty pages and add permission checks for the create button Add rbac checks for the Event stream actions and the edit page --- .../hooks/useMapContentTypeToDisplayName.tsx | 1 + .../eda/event-streams/EventStreamForm.tsx | 56 ++++++++++++++----- .../EventStreamPage/EventStreamPage.tsx | 30 ++++++++-- .../EventStreamPage/EventStreamUserAccess.tsx | 2 +- frontend/eda/event-streams/EventStreams.tsx | 27 +++++++-- .../hooks/useEventStreamsActions.tsx | 12 +++- 6 files changed, 102 insertions(+), 26 deletions(-) diff --git a/frontend/common/access/hooks/useMapContentTypeToDisplayName.tsx b/frontend/common/access/hooks/useMapContentTypeToDisplayName.tsx index b93ba34bd2..1b4bdb2089 100644 --- a/frontend/common/access/hooks/useMapContentTypeToDisplayName.tsx +++ b/frontend/common/access/hooks/useMapContentTypeToDisplayName.tsx @@ -22,6 +22,7 @@ export function useMapContentTypeToDisplayName() { decisionenvironment: options?.isTitleCase ? t('Decision Environment') : t('decision environment'), + eventstream: options?.isTitleCase ? t('Event Stream') : t('event stream'), auditrule: options?.isTitleCase ? t('Rule Audit') : t('rule audit'), team: options?.isTitleCase ? t('Team') : t('team'), organization: options?.isTitleCase ? t('Organization') : t('organization'), diff --git a/frontend/eda/event-streams/EventStreamForm.tsx b/frontend/eda/event-streams/EventStreamForm.tsx index 50d97a3bba..2586a08c80 100644 --- a/frontend/eda/event-streams/EventStreamForm.tsx +++ b/frontend/eda/event-streams/EventStreamForm.tsx @@ -27,6 +27,10 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { EdaCredentialType } from '../interfaces/EdaCredentialType'; import { useEffect } from 'react'; import { PageFormHidden } from '../../../framework/PageForm/Utils/PageFormHidden'; +import { useOptions } from '../../common/crud/useOptions'; +import { ActionsResponse, OptionsResponse } from '../interfaces/OptionsResponse'; +import { Alert } from '@patternfly/react-core'; +import { EventStreamDetails } from './EventStreamPage/EventStreamDetails'; // eslint-disable-next-line react/prop-types function EventStreamInputs() { @@ -216,6 +220,10 @@ export function EditEventStream() { const navigate = useNavigate(); const params = useParams<{ id?: string }>(); const id = Number(params.id); + const { data } = useOptions>( + edaAPI`/event-streams/${params.id ?? ''}/` + ); + const canEditEventStream = Boolean(data && data.actions && data.actions['PATCH']); const { data: eventStream } = useGet(edaAPI`/event-streams/${id.toString()}/`); const { cache } = useSWRConfig(); @@ -253,20 +261,40 @@ export function EditEventStream() { { label: `${t('Edit')} ${eventStream?.name || t('event stream')}` }, ]} /> - - - + {!canEditEventStream ? ( + <> + + + + ) : ( + + + + )} ); } diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx index f6333037ab..0567af90a0 100644 --- a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx @@ -24,15 +24,21 @@ import { useDeleteEventStreams } from '../hooks/useDeleteEventStreams'; import { usePatchRequest } from '../../../common/crud/usePatchRequest'; import { useDisableEventStreams } from '../hooks/useDisableEventStreams'; import { EdaResult } from '../../interfaces/EdaResult'; +import { useOptions } from '../../../common/crud/useOptions'; +import { ActionsResponse, OptionsResponse } from '../../interfaces/OptionsResponse'; export function EventStreamPage() { const { t } = useTranslation(); const params = useParams<{ id: string }>(); const pageNavigate = usePageNavigate(); - const getPageUrl = useGetPageUrl(); + const { data } = useOptions>( + edaAPI`/event-streams/${params.id ?? ''}/` + ); + const canEditEventStream = Boolean(data && data.actions && data.actions['PATCH']); const { data: eventStream, refresh } = useGet( edaAPI`/event-streams/${params.id ?? ''}/` ); + const getPageUrl = useGetPageUrl(); const patchRequest = usePatchRequest(); const alertToaster = usePageAlertToaster(); const disableEventStreams = useDisableEventStreams((disabled) => { @@ -96,6 +102,10 @@ export function EventStreamPage() { else void disableEventStreams([eventStream]); }, isSwitchOn: (eventStream: EdaEventStream) => !eventStream.test_mode, + isDisabled: () => + canEditEventStream + ? '' + : t(`The event stream cannot be edited due to insufficient permission`), }, { type: PageActionType.Button, @@ -104,6 +114,10 @@ export function EventStreamPage() { icon: PencilAltIcon, isPinned: true, label: t('Edit event stream'), + isDisabled: () => + canEditEventStream + ? '' + : t(`The event stream cannot be edited due to insufficient permission`), onClick: (eventStream: EdaEventStream) => pageNavigate(EdaRoute.EditEventStream, { params: { id: eventStream.id } }), }, @@ -115,16 +129,22 @@ export function EventStreamPage() { selection: PageActionSelection.Single, icon: TrashIcon, label: t('Delete event stream'), - isDisabled: () => - esActivations?.results && esActivations.results.length > 0 - ? t('To delete this event stream, disconnect it from all rulebook activations') - : undefined, + isDisabled: () => { + if (canEditEventStream) { + return esActivations?.results && esActivations.results.length > 0 + ? t('To delete this event stream, disconnect it from all rulebook activations') + : ''; + } else { + return t(`The event stream cannot be deleted due to insufficient permission`); + } + }, onClick: (eventStream: EdaEventStream) => deleteEventStreams([eventStream]), isDanger: true, }, ] : [], [ + canEditEventStream, deleteEventStreams, disableEventStreams, enableEventStream, diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx index 85ffc5422d..a6aefb0cf2 100644 --- a/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamUserAccess.tsx @@ -8,7 +8,7 @@ export function EventStreamUserAccess() { ); diff --git a/frontend/eda/event-streams/EventStreams.tsx b/frontend/eda/event-streams/EventStreams.tsx index 3983bf79b7..b5a56baa7e 100644 --- a/frontend/eda/event-streams/EventStreams.tsx +++ b/frontend/eda/event-streams/EventStreams.tsx @@ -8,7 +8,9 @@ import { useEventStreamActions } from './hooks/useEventStreamActions'; import { useEventStreamColumns } from './hooks/useEventStreamColumns'; import { useEventStreamFilters } from './hooks/useEventStreamFilters'; import { useEventStreamsActions } from './hooks/useEventStreamsActions'; -import { PlusCircleIcon } from '@patternfly/react-icons'; +import { CubesIcon, PlusCircleIcon } from '@patternfly/react-icons'; +import { useOptions } from '../../common/crud/useOptions'; +import { ActionsResponse, OptionsResponse } from '../interfaces/OptionsResponse'; export function EventStreams() { const { t } = useTranslation(); @@ -21,6 +23,8 @@ export function EventStreams() { tableColumns, }); const toolbarActions = useEventStreamsActions(view); + const { data } = useOptions>(edaAPI`/event-streams/`); + const canCreateEventStream = Boolean(data && data.actions && data.actions['POST']); const rowActions = useEventStreamActions(view); return ( @@ -38,11 +42,24 @@ export function EventStreams() { toolbarFilters={toolbarFilters} rowActions={rowActions} errorStateTitle={t('Error loading event streams')} - emptyStateTitle={t('There are currently no event streams created for your organization.')} - emptyStateDescription={t('Please create an event stream by using the button below.')} + emptyStateTitle={ + canCreateEventStream + ? t('There are currently no event streams created for your organization.') + : t('You do not have permission to create an event stream.') + } + emptyStateDescription={ + canCreateEventStream + ? t('Please create an event stream by using the button below.') + : t( + 'Please contact your organization administrator if there is an issue with your access.' + ) + } + emptyStateIcon={canCreateEventStream ? undefined : CubesIcon} emptyStateButtonIcon={} - emptyStateButtonText={t('Create event stream')} - emptyStateButtonClick={() => pageNavigate(EdaRoute.CreateEventStream)} + emptyStateButtonText={canCreateEventStream ? t('Create event stream') : undefined} + emptyStateButtonClick={ + canCreateEventStream ? () => pageNavigate(EdaRoute.CreateEventStream) : undefined + } {...view} defaultSubtitle={t('Event stream')} /> diff --git a/frontend/eda/event-streams/hooks/useEventStreamsActions.tsx b/frontend/eda/event-streams/hooks/useEventStreamsActions.tsx index 74b696ee25..0cefed6d6c 100644 --- a/frontend/eda/event-streams/hooks/useEventStreamsActions.tsx +++ b/frontend/eda/event-streams/hooks/useEventStreamsActions.tsx @@ -12,11 +12,16 @@ import { IEdaView } from '../../common/useEventDrivenView'; import { EdaEventStream } from '../../interfaces/EdaEventStream'; import { EdaRoute } from '../../main/EdaRoutes'; import { useDeleteEventStreams } from './useDeleteEventStreams'; +import { useOptions } from '../../../common/crud/useOptions'; +import { ActionsResponse, OptionsResponse } from '../../interfaces/OptionsResponse'; +import { edaAPI } from '../../common/eda-utils'; export function useEventStreamsActions(view: IEdaView) { const { t } = useTranslation(); const pageNavigate = usePageNavigate(); const deleteEventStreams = useDeleteEventStreams(view.unselectItemsAndRefresh); + const { data } = useOptions>(edaAPI`/event-streams/`); + const canCreateEventStream = Boolean(data && data.actions && data.actions['POST']); return useMemo[]>( () => [ { @@ -26,6 +31,11 @@ export function useEventStreamsActions(view: IEdaView) { isPinned: true, icon: PlusCircleIcon, label: t('Create event stream'), + isDisabled: canCreateEventStream + ? undefined + : t( + 'You do not have permission to create a project. Please contact your organization administrator if there is an issue with your access.' + ), onClick: () => pageNavigate(EdaRoute.CreateEventStream), }, { @@ -37,6 +47,6 @@ export function useEventStreamsActions(view: IEdaView) { isDanger: true, }, ], - [deleteEventStreams, pageNavigate, t] + [canCreateEventStream, deleteEventStreams, pageNavigate, t] ); } From 2963fda5d3f3d5c1a213564500e49131f8ba6bb1 Mon Sep 17 00:00:00 2001 From: lgalis Date: Mon, 26 Aug 2024 16:51:32 -0400 Subject: [PATCH 3/5] Update rbac message for teh forward switch --- frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx index 0567af90a0..150d964d2d 100644 --- a/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx +++ b/frontend/eda/event-streams/EventStreamPage/EventStreamPage.tsx @@ -105,7 +105,7 @@ export function EventStreamPage() { isDisabled: () => canEditEventStream ? '' - : t(`The event stream cannot be edited due to insufficient permission`), + : t(`The event stream cannot be updated due to insufficient permission`), }, { type: PageActionType.Button, From 01e4bfc7a5cf4bcd6ccefd081c26e9d9b690c284 Mon Sep 17 00:00:00 2001 From: lgalis Date: Mon, 26 Aug 2024 18:10:35 -0400 Subject: [PATCH 4/5] Update event stream component tests, add fixtures. --- cypress/fixtures/edaEventStream.json | 36 ++ cypress/fixtures/edaEventStreamOptions.json | 333 ++++++++++++++++++ cypress/fixtures/edaEventStreamRoles.json | 119 +++++++ cypress/fixtures/edaEventStreamsOptions.json | 326 +++++++++++++++++ .../eda/event-streams/EventStreams.cy.tsx | 18 +- .../components/EdaEventStreamAddTeams.cy.tsx | 14 +- .../components/EdaEventStreamAddUsers.cy.tsx | 14 +- 7 files changed, 844 insertions(+), 16 deletions(-) create mode 100644 cypress/fixtures/edaEventStream.json create mode 100644 cypress/fixtures/edaEventStreamOptions.json create mode 100644 cypress/fixtures/edaEventStreamRoles.json create mode 100644 cypress/fixtures/edaEventStreamsOptions.json diff --git a/cypress/fixtures/edaEventStream.json b/cypress/fixtures/edaEventStream.json new file mode 100644 index 0000000000..40151cf0c5 --- /dev/null +++ b/cypress/fixtures/edaEventStream.json @@ -0,0 +1,36 @@ +{ + "name": "ev7", + "test_mode": true, + "additional_data_headers": "", + "organization": { + "id": 1, + "name": "Default", + "description": "The default organization" + }, + "eda_credential": { + "id": 1, + "name": "basic es1", + "description": "", + "inputs": { + "username": "a", + "password": "$encrypted$", + "auth_type": "basic", + "http_header_key": "Authorization" + }, + "managed": false, + "credential_type_id": 7, + "organization_id": 1 + }, + "event_stream_type": "basic", + "id": 8, + "owner": "admin", + "url": "https://localhost:8443/api/eda/v1/external_event_stream/87731dc0-cc3a-440a-af06-cdc24b52cfa7/post/", + "created_at": "2024-08-26T02:34:59.981077Z", + "modified_at": "2024-08-26T19:41:03.296883Z", + "test_content_type": "", + "test_content": "", + "test_error_message": "", + "test_headers": "", + "events_received": 0, + "last_event_received_at": null +} diff --git a/cypress/fixtures/edaEventStreamOptions.json b/cypress/fixtures/edaEventStreamOptions.json new file mode 100644 index 0000000000..1037c8b293 --- /dev/null +++ b/cypress/fixtures/edaEventStreamOptions.json @@ -0,0 +1,333 @@ +{ + "name": "Event Stream Instance", + "description": "", + "renders": [ + "application/json", + "text/html" + ], + "parses": [ + "application/json", + "application/x-www-form-urlencoded", + "multipart/form-data" + ], + "actions": { + "PATCH": { + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name", + "help_text": "The name of the webhook" + }, + "test_mode": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Test mode", + "help_text": "Enable test mode" + }, + "additional_data_headers": { + "type": "string", + "required": false, + "read_only": false, + "label": "Additional data headers", + "help_text": "The additional http headers which will be added to the event data. The headers are comma delimited" + }, + "organization": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization" + }, + "eda_credential": { + "type": "nested object", + "required": true, + "read_only": false, + "label": "Eda credential", + "children": { + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name" + }, + "description": { + "type": "string", + "required": false, + "read_only": false, + "label": "Description" + }, + "inputs": { + "type": "field", + "required": false, + "read_only": true, + "label": "Inputs" + }, + "managed": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Managed" + }, + "credential_type_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Credential type id" + }, + "organization_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization id" + } + } + }, + "event_stream_type": { + "type": "string", + "required": false, + "read_only": false, + "label": "Event stream type", + "help_text": "The type of the event stream based on credential type" + }, + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "owner": { + "type": "field", + "required": false, + "read_only": true, + "label": "Owner" + }, + "url": { + "type": "string", + "required": false, + "read_only": true, + "label": "Url", + "help_text": "The URL which will be used to post to the event stream" + }, + "created_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Created at" + }, + "modified_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Modified at" + }, + "test_content_type": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content type", + "help_text": "The content type of test data, when in test mode" + }, + "test_content": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content", + "help_text": "The content recieved, when in test mode, stored as a yaml string" + }, + "test_error_message": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test error message", + "help_text": "The error message, when in test mode" + }, + "test_headers": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test headers", + "help_text": "The headers recieved, when in test mode, stored as a yaml string" + }, + "events_received": { + "type": "integer", + "required": false, + "read_only": true, + "label": "Events received", + "help_text": "The total number of events received by event stream" + }, + "last_event_received_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Last event received at", + "help_text": "The date/time when the last event was received" + } + }, + "GET": { + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name", + "help_text": "The name of the webhook" + }, + "test_mode": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Test mode", + "help_text": "Enable test mode" + }, + "additional_data_headers": { + "type": "string", + "required": false, + "read_only": false, + "label": "Additional data headers", + "help_text": "The additional http headers which will be added to the event data. The headers are comma delimited" + }, + "organization": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization" + }, + "eda_credential": { + "type": "nested object", + "required": true, + "read_only": false, + "label": "Eda credential", + "children": { + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name" + }, + "description": { + "type": "string", + "required": false, + "read_only": false, + "label": "Description" + }, + "inputs": { + "type": "field", + "required": false, + "read_only": true, + "label": "Inputs" + }, + "managed": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Managed" + }, + "credential_type_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Credential type id" + }, + "organization_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization id" + } + } + }, + "event_stream_type": { + "type": "string", + "required": false, + "read_only": false, + "label": "Event stream type", + "help_text": "The type of the event stream based on credential type" + }, + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "owner": { + "type": "field", + "required": false, + "read_only": true, + "label": "Owner" + }, + "url": { + "type": "string", + "required": false, + "read_only": true, + "label": "Url", + "help_text": "The URL which will be used to post to the event stream" + }, + "created_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Created at" + }, + "modified_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Modified at" + }, + "test_content_type": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content type", + "help_text": "The content type of test data, when in test mode" + }, + "test_content": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content", + "help_text": "The content recieved, when in test mode, stored as a yaml string" + }, + "test_error_message": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test error message", + "help_text": "The error message, when in test mode" + }, + "test_headers": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test headers", + "help_text": "The headers recieved, when in test mode, stored as a yaml string" + }, + "events_received": { + "type": "integer", + "required": false, + "read_only": true, + "label": "Events received", + "help_text": "The total number of events received by event stream" + }, + "last_event_received_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Last event received at", + "help_text": "The date/time when the last event was received" + } + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/edaEventStreamRoles.json b/cypress/fixtures/edaEventStreamRoles.json new file mode 100644 index 0000000000..3a0447d963 --- /dev/null +++ b/cypress/fixtures/edaEventStreamRoles.json @@ -0,0 +1,119 @@ +{ + "count": 4, + "next": null, + "previous": null, + "page_size": 50, + "page": 1, + "results": [ + { + "id": 22, + "url": "/api/eda/v1/role_definitions/22/", + "related": { + "team_assignments": "/api/eda/v1/role_definitions/22/team_assignments/", + "user_assignments": "/api/eda/v1/role_definitions/22/user_assignments/" + }, + "summary_fields": {}, + "permissions": ["eda.change_eventstream", "eda.delete_eventstream", "eda.view_eventstream"], + "content_type": "eda.eventstream", + "modified": "2024-08-25T06:17:24.364224Z", + "created": "2024-08-21T22:20:59.657360Z", + "name": "Event Stream Admin", + "description": "Has all permissions to a single event stream", + "managed": true, + "modified_by": null, + "created_by": null + }, + { + "id": 23, + "url": "/api/eda/v1/role_definitions/23/", + "related": { + "team_assignments": "/api/eda/v1/role_definitions/23/team_assignments/", + "user_assignments": "/api/eda/v1/role_definitions/23/user_assignments/" + }, + "summary_fields": {}, + "permissions": ["eda.change_eventstream", "eda.view_eventstream"], + "content_type": "eda.eventstream", + "modified": "2024-08-25T06:17:24.367576Z", + "created": "2024-08-21T22:20:59.661014Z", + "name": "Event Stream Use", + "description": "Has use permissions to a single event stream", + "managed": true, + "modified_by": null, + "created_by": null + }, + { + "id": 27, + "url": "/api/eda/v1/role_definitions/27/", + "related": { + "created_by": "/api/eda/v1/users/1/", + "modified_by": "/api/eda/v1/users/1/", + "team_assignments": "/api/eda/v1/role_definitions/27/team_assignments/", + "user_assignments": "/api/eda/v1/role_definitions/27/user_assignments/" + }, + "summary_fields": { + "modified_by": { + "id": 1, + "username": "admin", + "email": "admin@test.com", + "first_name": "", + "last_name": "", + "is_superuser": true + }, + "created_by": { + "id": 1, + "username": "admin", + "email": "admin@test.com", + "first_name": "", + "last_name": "", + "is_superuser": true + } + }, + "permissions": ["eda.view_eventstream"], + "content_type": "eda.eventstream", + "modified": "2024-08-26T20:16:26.148302Z", + "created": "2024-08-26T20:16:26.148326Z", + "name": "LGView", + "description": "", + "managed": false, + "modified_by": 1, + "created_by": 1 + }, + { + "id": 25, + "url": "/api/eda/v1/role_definitions/25/", + "related": { + "created_by": "/api/eda/v1/users/1/", + "modified_by": "/api/eda/v1/users/1/", + "team_assignments": "/api/eda/v1/role_definitions/25/team_assignments/", + "user_assignments": "/api/eda/v1/role_definitions/25/user_assignments/" + }, + "summary_fields": { + "modified_by": { + "id": 1, + "username": "admin", + "email": "admin@test.com", + "first_name": "", + "last_name": "", + "is_superuser": true + }, + "created_by": { + "id": 1, + "username": "admin", + "email": "admin@test.com", + "first_name": "", + "last_name": "", + "is_superuser": true + } + }, + "permissions": ["eda.view_eventstream"], + "content_type": "eda.eventstream", + "modified": "2024-08-26T18:20:07.813852Z", + "created": "2024-08-22T17:06:59.107929Z", + "name": "test", + "description": "", + "managed": false, + "modified_by": 1, + "created_by": 1 + } + ] +} diff --git a/cypress/fixtures/edaEventStreamsOptions.json b/cypress/fixtures/edaEventStreamsOptions.json new file mode 100644 index 0000000000..c87560eec4 --- /dev/null +++ b/cypress/fixtures/edaEventStreamsOptions.json @@ -0,0 +1,326 @@ +{ + "name": "Event Stream List", + "description": "", + "renders": ["application/json", "text/html"], + "parses": ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"], + "actions": { + "POST": { + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name", + "help_text": "The name of the webhook" + }, + "test_mode": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Test mode", + "help_text": "Enable test mode" + }, + "additional_data_headers": { + "type": "string", + "required": false, + "read_only": false, + "label": "Additional data headers", + "help_text": "The additional http headers which will be added to the event data. The headers are comma delimited" + }, + "organization": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization" + }, + "eda_credential": { + "type": "nested object", + "required": true, + "read_only": false, + "label": "Eda credential", + "children": { + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name" + }, + "description": { + "type": "string", + "required": false, + "read_only": false, + "label": "Description" + }, + "inputs": { + "type": "field", + "required": false, + "read_only": true, + "label": "Inputs" + }, + "managed": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Managed" + }, + "credential_type_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Credential type id" + }, + "organization_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization id" + } + } + }, + "event_stream_type": { + "type": "string", + "required": false, + "read_only": false, + "label": "Event stream type", + "help_text": "The type of the event stream based on credential type" + }, + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "owner": { + "type": "field", + "required": false, + "read_only": true, + "label": "Owner" + }, + "url": { + "type": "string", + "required": false, + "read_only": true, + "label": "Url", + "help_text": "The URL which will be used to post to the event stream" + }, + "created_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Created at" + }, + "modified_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Modified at" + }, + "test_content_type": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content type", + "help_text": "The content type of test data, when in test mode" + }, + "test_content": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content", + "help_text": "The content recieved, when in test mode, stored as a yaml string" + }, + "test_error_message": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test error message", + "help_text": "The error message, when in test mode" + }, + "test_headers": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test headers", + "help_text": "The headers recieved, when in test mode, stored as a yaml string" + }, + "events_received": { + "type": "integer", + "required": false, + "read_only": true, + "label": "Events received", + "help_text": "The total number of events received by event stream" + }, + "last_event_received_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Last event received at", + "help_text": "The date/time when the last event was received" + } + }, + "GET": { + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name", + "help_text": "The name of the webhook" + }, + "test_mode": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Test mode", + "help_text": "Enable test mode" + }, + "additional_data_headers": { + "type": "string", + "required": false, + "read_only": false, + "label": "Additional data headers", + "help_text": "The additional http headers which will be added to the event data. The headers are comma delimited" + }, + "organization": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization" + }, + "eda_credential": { + "type": "nested object", + "required": true, + "read_only": false, + "label": "Eda credential", + "children": { + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "name": { + "type": "string", + "required": true, + "read_only": false, + "label": "Name" + }, + "description": { + "type": "string", + "required": false, + "read_only": false, + "label": "Description" + }, + "inputs": { + "type": "field", + "required": false, + "read_only": true, + "label": "Inputs" + }, + "managed": { + "type": "boolean", + "required": false, + "read_only": false, + "label": "Managed" + }, + "credential_type_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Credential type id" + }, + "organization_id": { + "type": "field", + "required": false, + "read_only": true, + "label": "Organization id" + } + } + }, + "event_stream_type": { + "type": "string", + "required": false, + "read_only": false, + "label": "Event stream type", + "help_text": "The type of the event stream based on credential type" + }, + "id": { + "type": "integer", + "required": false, + "read_only": true, + "label": "ID" + }, + "owner": { + "type": "field", + "required": false, + "read_only": true, + "label": "Owner" + }, + "url": { + "type": "string", + "required": false, + "read_only": true, + "label": "Url", + "help_text": "The URL which will be used to post to the event stream" + }, + "created_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Created at" + }, + "modified_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Modified at" + }, + "test_content_type": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content type", + "help_text": "The content type of test data, when in test mode" + }, + "test_content": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test content", + "help_text": "The content recieved, when in test mode, stored as a yaml string" + }, + "test_error_message": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test error message", + "help_text": "The error message, when in test mode" + }, + "test_headers": { + "type": "string", + "required": false, + "read_only": true, + "label": "Test headers", + "help_text": "The headers recieved, when in test mode, stored as a yaml string" + }, + "events_received": { + "type": "integer", + "required": false, + "read_only": true, + "label": "Events received", + "help_text": "The total number of events received by event stream" + }, + "last_event_received_at": { + "type": "datetime", + "required": false, + "read_only": true, + "label": "Last event received at", + "help_text": "The date/time when the last event was received" + } + } + } +} diff --git a/frontend/eda/event-streams/EventStreams.cy.tsx b/frontend/eda/event-streams/EventStreams.cy.tsx index 4df7fcd772..60920c6b86 100644 --- a/frontend/eda/event-streams/EventStreams.cy.tsx +++ b/frontend/eda/event-streams/EventStreams.cy.tsx @@ -9,7 +9,12 @@ describe('EventStreams.cy.ts', () => { fixture: 'edaEventStreams.json', } ); - + cy.intercept( + { method: 'OPTIONS', url: edaAPI`/event-streams/*` }, + { + fixture: 'edaEventStreamsOptions.json', + } + ); cy.intercept( { method: 'GET', url: edaAPI`/event-streams/?page=2&page_size=10` }, { @@ -239,7 +244,16 @@ describe('EventStreams.cy.ts', () => { }); describe('Empty list', () => { - beforeEach(() => { + before(() => { + cy.intercept( + { + method: 'OPTIONS', + url: edaAPI`/event-streams/`, + }, + { + fixture: 'edaEventStreamsOptions.json', + } + ).as('getOptions'); cy.intercept( { method: 'GET', diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx index 0f9b44b4ed..02780452ff 100644 --- a/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx +++ b/frontend/eda/event-streams/components/EdaEventStreamAddTeams.cy.tsx @@ -13,7 +13,7 @@ describe('EdaEventStreamAddTeams', () => { beforeEach(() => { cy.intercept('GET', edaAPI`/event-streams/*`, { fixture: 'edaEventStream.json' }); cy.intercept('GET', edaAPI`/teams/?order_by=name*`, { fixture: 'edaTeams.json' }); - cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=event-stream*`, { + cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=eventstream*`, { fixture: 'edaEventStreamRoles.json', }); cy.mount(component, params); @@ -46,7 +46,7 @@ describe('EdaEventStreamAddTeams', () => { cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); cy.clickButton(/^Next$/); cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one role.'); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.get('[data-cy="wizard-nav-item-roles"] button').should('not.have.class', 'pf-m-current'); cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); @@ -54,7 +54,7 @@ describe('EdaEventStreamAddTeams', () => { it('should display selected team and role in the Review step', () => { cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); cy.clickButton(/^Next$/); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); cy.get('[data-cy="expandable-section-teams"]').should('contain.text', 'Teams'); @@ -62,10 +62,10 @@ describe('EdaEventStreamAddTeams', () => { cy.get('[data-cy="expandable-section-teams"]').should('contain.text', 'Demo'); cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Roles'); cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', '1'); - cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'EventStream Admin'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Event Stream Admin'); cy.get('[data-cy="expandable-section-edaRoles"]').should( 'contain.text', - 'Has all permissions to a single event-stream and its child resources - rulebook' + 'Has all permissions to a single event stream' ); }); it('should trigger bulk action dialog on submit', () => { @@ -75,7 +75,7 @@ describe('EdaEventStreamAddTeams', () => { }).as('createRoleAssignment'); cy.selectTableRowByCheckbox('name', 'Demo', { disableFilter: true }); cy.clickButton(/^Next$/); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.clickButton(/^Finish$/); cy.wait('@createRoleAssignment'); @@ -83,7 +83,7 @@ describe('EdaEventStreamAddTeams', () => { cy.get('.pf-v5-c-modal-box').within(() => { cy.get('table tbody').find('tr').should('have.length', 1); cy.get('table tbody').should('contain.text', 'Demo'); - cy.get('table tbody').should('contain.text', 'EventStream Admin'); + cy.get('table tbody').should('contain.text', 'Event Stream Admin'); cy.get('div.pf-v5-c-progress__description').should('contain.text', 'Success'); cy.get('div.pf-v5-c-progress__status').should('contain.text', '100%'); }); diff --git a/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx index e989471330..dfa88d1843 100644 --- a/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx +++ b/frontend/eda/event-streams/components/EdaEventStreamAddUsers.cy.tsx @@ -13,7 +13,7 @@ describe('EdaEventStreamAddUsers', () => { beforeEach(() => { cy.intercept('GET', edaAPI`/event-streams/*`, { fixture: 'edaEventStream.json' }); cy.intercept('GET', edaAPI`/users/*`, { fixture: 'edaNormalUsers.json' }); - cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=event-stream*`, { + cy.intercept('GET', edaAPI`/role_definitions/?content_type__model=eventstream*`, { fixture: 'edaEventStreamRoles.json', }); cy.mount(component, params); @@ -48,7 +48,7 @@ describe('EdaEventStreamAddUsers', () => { cy.get('[data-cy="wizard-nav-item-roles"] button').should('have.class', 'pf-m-current'); cy.clickButton(/^Next$/); cy.get('.pf-v5-c-alert__title').should('contain.text', 'Select at least one role.'); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.get('[data-cy="wizard-nav-item-roles"] button').should('not.have.class', 'pf-m-current'); cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); @@ -56,7 +56,7 @@ describe('EdaEventStreamAddUsers', () => { it('should display selected user and role in the Review step', () => { cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); cy.clickButton(/^Next$/); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.get('[data-cy="wizard-nav-item-review"] button').should('have.class', 'pf-m-current'); cy.get('[data-cy="expandable-section-users"]').should('contain.text', 'Users'); @@ -64,10 +64,10 @@ describe('EdaEventStreamAddUsers', () => { cy.get('[data-cy="expandable-section-users"]').should('contain.text', 'demo-user'); cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Roles'); cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', '1'); - cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'EventStream Admin'); + cy.get('[data-cy="expandable-section-edaRoles"]').should('contain.text', 'Event Stream Admin'); cy.get('[data-cy="expandable-section-edaRoles"]').should( 'contain.text', - 'Has all permissions to a single event-stream and its child resources - rulebook' + 'Has all permissions to a single event stream' ); }); it('should trigger bulk action dialog on submit', () => { @@ -77,7 +77,7 @@ describe('EdaEventStreamAddUsers', () => { }).as('createRoleAssignment'); cy.selectTableRowByCheckbox('username', 'demo-user', { disableFilter: true }); cy.clickButton(/^Next$/); - cy.selectTableRowByCheckbox('name', 'EventStream Admin', { disableFilter: true }); + cy.selectTableRowByCheckbox('name', 'Event Stream Admin', { disableFilter: true }); cy.clickButton(/^Next$/); cy.clickButton(/^Finish$/); cy.wait('@createRoleAssignment'); @@ -85,7 +85,7 @@ describe('EdaEventStreamAddUsers', () => { cy.get('.pf-v5-c-modal-box').within(() => { cy.get('table tbody').find('tr').should('have.length', 1); cy.get('table tbody').should('contain.text', 'demo-user'); - cy.get('table tbody').should('contain.text', 'EventStream Admin'); + cy.get('table tbody').should('contain.text', 'Event Stream Admin'); cy.get('div.pf-v5-c-progress__description').should('contain.text', 'Success'); cy.get('div.pf-v5-c-progress__status').should('contain.text', '100%'); }); From cba06f2afd4f1f59c8eb80fb8787329669ad01de Mon Sep 17 00:00:00 2001 From: lgalis Date: Mon, 26 Aug 2024 18:16:49 -0400 Subject: [PATCH 5/5] Update event stream component tests, add fixtures. --- cypress/fixtures/edaEventStreamOptions.json | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/cypress/fixtures/edaEventStreamOptions.json b/cypress/fixtures/edaEventStreamOptions.json index 1037c8b293..7facb06c44 100644 --- a/cypress/fixtures/edaEventStreamOptions.json +++ b/cypress/fixtures/edaEventStreamOptions.json @@ -1,15 +1,8 @@ { "name": "Event Stream Instance", "description": "", - "renders": [ - "application/json", - "text/html" - ], - "parses": [ - "application/json", - "application/x-www-form-urlencoded", - "multipart/form-data" - ], + "renders": ["application/json", "text/html"], + "parses": ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"], "actions": { "PATCH": { "name": { @@ -330,4 +323,4 @@ } } } -} \ No newline at end of file +}