Skip to content
2 changes: 1 addition & 1 deletion apps/backend/src/datasources/StatusActionsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface StatusActionsDataSource {
getConnectionStatusAction(
workflowConnectionId: number,
statusActionId: number
): Promise<ConnectionHasStatusAction>;
): Promise<ConnectionHasStatusAction | null>;
hasEmailTemplateIdConnectionStatusAction(
emailTemplateId: number
): Promise<boolean>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default class PostgresStatusActionsDataSource
async getConnectionStatusAction(
workflowConnectionId: number,
statusActionId: number
): Promise<ConnectionHasStatusAction> {
): Promise<ConnectionHasStatusAction | null> {
const statusActionRecord: StatusActionRecord &
WorkflowConnectionHasActionsRecord & {
config: typeof StatusActionConfig;
Expand All @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions apps/backend/src/mutations/StatusActionsLogsMutations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<StatusActionsDataSource>(
Tokens.StatusActionsDataSource
);
const getConnectionStatusActionSpy = jest
.spyOn(statusActionsDataSource, 'getConnectionStatusAction')
.mockResolvedValueOnce(null);

const result = await statusActionsLogsMutations.replayStatusActionsLog(
dummyUserOfficerWithRole,
dummyStatusActionsLog.statusActionsLogId
);

expect(result).toBeInstanceOf(Rejection);

getConnectionStatusActionSpy.mockRestore();
});
});
28 changes: 28 additions & 0 deletions apps/e2e/cypress/e2e/statusActions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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('/');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ const StatusActionsLogsTable = ({
const [selectedCallName, setSelectedCallName] = useState<string | undefined>(
undefined
);
const ReplayIcon = (): JSX.Element => (
<Replay data-cy="replay_status_action_icon" />
);
const ReplayAllIcon = (): JSX.Element => (
<ReplayCircleFilledIcon data-cy="replay_all_status_action_icon" />
);
Expand Down Expand Up @@ -342,11 +339,31 @@ const StatusActionsLogsTable = ({
},
}}
actions={[
{
icon: ReplayIcon,
tooltip: 'Replay status action',
(rowData: StatusActionsLog) => ({
icon: () => (
<Replay
data-cy="replay_status_action_icon"
sx={(theme) => ({
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(
() =>
Expand Down Expand Up @@ -387,7 +404,7 @@ const StatusActionsLogsTable = ({
)();
}
},
},
}),
{
icon: RefreshIcon,
tooltip: 'Refresh status actions log data',
Expand Down
Loading