diff --git a/apps/backend/src/datasources/StatusActionsDataSource.ts b/apps/backend/src/datasources/StatusActionsDataSource.ts index 0d15a383c2..d77b64adda 100644 --- a/apps/backend/src/datasources/StatusActionsDataSource.ts +++ b/apps/backend/src/datasources/StatusActionsDataSource.ts @@ -11,7 +11,7 @@ export interface StatusActionsDataSource { getConnectionStatusAction( workflowConnectionId: number, statusActionId: number - ): Promise; + ): Promise; hasEmailTemplateIdConnectionStatusAction( emailTemplateId: number ): Promise; diff --git a/apps/backend/src/datasources/postgres/StatusActionsDataSource.ts b/apps/backend/src/datasources/postgres/StatusActionsDataSource.ts index e00d464553..98eabdfb75 100644 --- a/apps/backend/src/datasources/postgres/StatusActionsDataSource.ts +++ b/apps/backend/src/datasources/postgres/StatusActionsDataSource.ts @@ -104,7 +104,7 @@ export default class PostgresStatusActionsDataSource async getConnectionStatusAction( workflowConnectionId: number, statusActionId: number - ): Promise { + ): Promise { const statusActionRecord: StatusActionRecord & WorkflowConnectionHasActionsRecord & { config: typeof StatusActionConfig; @@ -119,9 +119,7 @@ export default class PostgresStatusActionsDataSource .first(); if (!statusActionRecord) { - throw new GraphQLError( - `Status action not found ActionId: ${statusActionId} connectionId: ${workflowConnectionId}` - ); + return null; } return this.createConnectionStatusActionObject(statusActionRecord); diff --git a/apps/backend/src/mutations/StatusActionsLogsMutations.spec.ts b/apps/backend/src/mutations/StatusActionsLogsMutations.spec.ts index 5f42ce4461..22e5bf0a6d 100644 --- a/apps/backend/src/mutations/StatusActionsLogsMutations.spec.ts +++ b/apps/backend/src/mutations/StatusActionsLogsMutations.spec.ts @@ -2,8 +2,11 @@ import 'reflect-metadata'; import { container } from 'tsyringe'; import StatusActionsLogsMutations from './StatusActionsLogsMutations'; +import { Tokens } from '../config/Tokens'; import { dummyStatusActionsLog } from '../datasources/mockups/StatusActionsLogsDataSource'; import { dummyUserOfficerWithRole } from '../datasources/mockups/UserDataSource'; +import { StatusActionsDataSource } from '../datasources/StatusActionsDataSource'; +import { Rejection } from '../models/Rejection'; const statusActionsLogsMutations = container.resolve( StatusActionsLogsMutations @@ -18,4 +21,22 @@ describe('Test Status Actions Logs Mutations', () => { ) ).resolves.toBeTruthy(); }); + + test('Replaying a status actions log whose connection/action no longer exists should be rejected, not throw', async () => { + const statusActionsDataSource = container.resolve( + Tokens.StatusActionsDataSource + ); + const getConnectionStatusActionSpy = jest + .spyOn(statusActionsDataSource, 'getConnectionStatusAction') + .mockResolvedValueOnce(null); + + const result = await statusActionsLogsMutations.replayStatusActionsLog( + dummyUserOfficerWithRole, + dummyStatusActionsLog.statusActionsLogId + ); + + expect(result).toBeInstanceOf(Rejection); + + getConnectionStatusActionSpy.mockRestore(); + }); }); diff --git a/apps/e2e/cypress/e2e/statusActions.cy.ts b/apps/e2e/cypress/e2e/statusActions.cy.ts index 8b2465bce6..c006b86ef9 100644 --- a/apps/e2e/cypress/e2e/statusActions.cy.ts +++ b/apps/e2e/cypress/e2e/statusActions.cy.ts @@ -44,6 +44,7 @@ let proposal1Id: string; let proposal2Id: string; let testEmailTemplate1Id: string; let testEmailTemplate2Id: string; +let statusActionsConnectionId: number; context('Status actions tests', () => { beforeEach(function () { @@ -783,6 +784,7 @@ context('Status actions tests', () => { }).then((result) => { const connection = result.createWorkflowConnection; if (connection) { + statusActionsConnectionId = connection.id; cy.setStatusChangingEventsOnConnection({ workflowConnectionId: connection.id, statusChangingEvents: [PROPOSAL_EVENTS.PROPOSAL_SUBMITTED], @@ -878,6 +880,32 @@ context('Status actions tests', () => { }); }); + it('User Officer should see status actions logs whose connection configuration was later removed, with replay disabled', () => { + cy.addConnectionStatusActions({ + actions: [], + connectionId: statusActionsConnectionId, + workflowId: initialDBData.workflows.defaultWorkflow.id, + }); + + cy.login('officer'); + cy.visit('/'); + + cy.navigateToStatusActionLogsSubmenu('Email'); + + cy.finishedLoading(); + + cy.get('[data-cy="status-actions-logs-table"]') + .find('tbody td') + .filter(':contains("SUCCESSFUL")') + .should('have.length.greaterThan', 0); + + cy.get('[data-cy="replay_status_action_icon"]') + .first() + .click({ force: true }); + + cy.get('[data-cy="confirm-ok"]').should('not.exist'); + }); + it('User Officer should be able to replay all email status actions in a call', () => { cy.login('officer'); cy.visit('/'); diff --git a/apps/frontend/src/components/statusActionsLogs/StatusActionsLogsTable.tsx b/apps/frontend/src/components/statusActionsLogs/StatusActionsLogsTable.tsx index 1be5365ca0..370c8ebb0d 100644 --- a/apps/frontend/src/components/statusActionsLogs/StatusActionsLogsTable.tsx +++ b/apps/frontend/src/components/statusActionsLogs/StatusActionsLogsTable.tsx @@ -48,9 +48,6 @@ const StatusActionsLogsTable = ({ const [selectedCallName, setSelectedCallName] = useState( undefined ); - const ReplayIcon = (): JSX.Element => ( - - ); const ReplayAllIcon = (): JSX.Element => ( ); @@ -342,11 +339,31 @@ const StatusActionsLogsTable = ({ }, }} actions={[ - { - icon: ReplayIcon, - tooltip: 'Replay status action', + (rowData: StatusActionsLog) => ({ + icon: () => ( + ({ + opacity: rowData.connectionStatusAction + ? 1 + : theme.palette.action.disabledOpacity, + color: rowData.connectionStatusAction + ? undefined + : theme.palette.action.disabled, + cursor: rowData.connectionStatusAction + ? 'pointer' + : 'not-allowed', + })} + /> + ), + tooltip: rowData.connectionStatusAction + ? 'Replay status action' + : 'This status action can no longer be replayed because its connection/action configuration no longer exists', onClick: (_event: unknown, rowData: unknown): void => { const statusActionsLog = rowData as StatusActionsLog; + if (!statusActionsLog.connectionStatusAction) { + return; + } if (statusActionsLog.statusActionsLogId) { confirm( () => @@ -387,7 +404,7 @@ const StatusActionsLogsTable = ({ )(); } }, - }, + }), { icon: RefreshIcon, tooltip: 'Refresh status actions log data',