Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 42 additions & 33 deletions apps/backend/src/mutations/ProposalMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,51 +546,60 @@ export default class ProposalMutations {
agent: UserWithRole | null,
args: ChangeProposalsStatusInput
): Promise<Proposals | Rejection> {
const { workflowStatusId: statusId, proposalPks } = args;
const {
workflowStatusId: statusId,
proposalPks,
workflowConnectionId,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it redundant to have both? workflowConnectionId and workflowStatusId?

} = args;

const result = await this.proposalDataSource.changeProposalsWorkflowStatus(
statusId,
proposalPks.map((proposalPk) => proposalPk)
);

if (result.proposals.length === proposalPks.length) {
const fullProposals = await Promise.all(
proposalPks.map(async (proposalPk) => {
const fullProposal = result.proposals.find(
(updatedProposal) => updatedProposal.primaryKey === proposalPk
);

if (!fullProposal) {
return null;
}

const proposalWorkflow =
await this.callDataSource.getProposalWorkflowByCall(
fullProposal.callId
// Only run status actions if a specific workflow connection was provided
if (workflowConnectionId) {
const fullProposals = await Promise.all(
proposalPks.map(async (proposalPk) => {
const fullProposal = result.proposals.find(
(updatedProposal) => updatedProposal.primaryKey === proposalPk
);

if (!proposalWorkflow) {
return rejection(
`No propsal workflow found for the specific proposal call with id: ${fullProposal.callId}`,
{
agent,
args,
}
);
}
if (!fullProposal) {
return null;
}

return {
...fullProposal,
};
})
);
const proposalWorkflow =
await this.callDataSource.getProposalWorkflowByCall(
fullProposal.callId
);

if (!proposalWorkflow) {
return rejection(
`No propsal workflow found for the specific proposal call with id: ${fullProposal.callId}`,
{
agent,
args,
}
);
}

const statusEngineReadyProposals = fullProposals.filter(
(item): item is WorkflowEngineProposalType => !!item
);
return {
...fullProposal,
prevStatusId: fullProposal.workflowStatusId,
workflowStatusConnectionId: workflowConnectionId,
};
})
);

// NOTE: After proposal status change we need to run the status engine and execute the actions on the selected status.
proposalStatusActionEngine(statusEngineReadyProposals);
const statusEngineReadyProposals = fullProposals.filter(
(item): item is WorkflowEngineProposalType => !!item
);

// NOTE: After proposal status change we need to run the status engine and execute the actions on the selected status.
proposalStatusActionEngine(statusEngineReadyProposals);
}
} else {
rejection('Could not change statuses to all of the selected proposals', {
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export class ChangeProposalsStatusInput {

@Field(() => [Int])
public proposalPks: number[];

@Field(() => Int, { nullable: true })
public workflowConnectionId?: number;
}

@Resolver()
Expand Down
77 changes: 77 additions & 0 deletions apps/e2e/cypress/e2e/proposals.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { faker } from '@faker-js/faker';
import {
AllocationTimeUnits,
DataType,
EmailStatusActionRecipients,
FeatureId,
ProposalEndStatus,
SettingsId,
StatusActionType,
TemplateCategoryId,
TemplateGroupId,
WorkflowType,
Expand Down Expand Up @@ -126,6 +128,7 @@ context('Proposal tests', () => {
cy.addStatusToWorkflow({
statusId: initialDBData.proposalStatuses.fapMeeting.id,
workflowId: initialDBData.workflows.defaultWorkflow.id,
posY: 100,
}).then((workflowStatusResult) => {
if (workflowStatusResult.addStatusToWorkflow) {
createdFapMeetingWorkflowStatusId =
Expand All @@ -141,6 +144,7 @@ context('Proposal tests', () => {
cy.addStatusToWorkflow({
statusId: initialDBData.proposalStatuses.feasibilityReview.id,
workflowId: result.createWorkflow.id,
posY: 200,
});
createdWorkflowId = result.createWorkflow.id;
}
Expand Down Expand Up @@ -880,6 +884,79 @@ context('Proposal tests', () => {
);
});

it('User officer should be able to opt-in to run status actions when changing status', () => {
const statusActionConfig = {
recipientsWithEmailTemplate: [
{
recipient: {
name: EmailStatusActionRecipients.PI,
description: '',
},
emailTemplate: {
id: initialDBData.emailTemplates.template1.id,
name: initialDBData.emailTemplates.template1.name,
},
combineEmails: true,
},
],
};

// Add a status with a connection from DRAFT and attach a status action
cy.addStatusToWorkflow({
statusId: initialDBData.proposalStatuses.feasibilityReview.id,
workflowId: initialDBData.workflows.defaultWorkflow.id,
prevId:
initialDBData.workflows.defaultWorkflow.workflowStatuses.draft.id,
posX: 0,
posY: 200,
}).then((result) => {
cy.addConnectionStatusActions({
actions: [
{
actionId: 1,
actionType: StatusActionType.EMAIL,
config: JSON.stringify(statusActionConfig),
},
],
connectionId: result.createWorkflowConnection.id,
workflowId: initialDBData.workflows.defaultWorkflow.id,
});
});

cy.login('officer');
cy.visit('/');

cy.contains(newProposalTitle).parent().find('[type="checkbox"]').check();

cy.get('[data-cy="change-proposal-status"]').click();

cy.finishedLoading();

cy.get('[role="presentation"] .MuiDialogContent-root').as('dialog');

// Select the status that has a connection with actions
cy.get('@dialog').find('#selectedWorkflowStatusId-input').click();
cy.get('[role="listbox"]')
.contains(initialDBData.proposalStatuses.feasibilityReview.name)
.click();

// The run status actions checkbox should appear and be unchecked
cy.get('[data-cy="run-status-actions-checkbox"] input')
.should('exist')
.should('not.be.checked');

// Check it to opt-in to running status actions
cy.get('[data-cy="run-status-actions-checkbox"] input').check();

// Should be able to submit the status change
cy.get('[data-cy="submit-proposal-status-change"]').click();

cy.notification({
variant: 'success',
text: 'status changed successfully',
});
});

it('Should be able to delete proposal', () => {
cy.login('user1', initialDBData.roles.user);
cy.visit('/');
Expand Down
Loading
Loading