Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TTAHUB-3046] AR flags on merged goals need to be set #2377

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
43 changes: 43 additions & 0 deletions src/models/hooks/activityReportGoal.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { Op } = require('sequelize');
const { REPORT_STATUSES } = require('@ttahub/common');
const { GOAL_COLLABORATORS } = require('../../constants');
const {
Expand Down Expand Up @@ -152,6 +153,46 @@ const destroyLinkedSimilarityGroups = async (sequelize, instance, options) => {
});
};

const updateOnARAndOnApprovedARForMergedGoals = async (sequelize, instance) => {
const changed = instance.changed();

// Check if both originalGoalId and goalId have been changed and originalGoalId is not null
if (Array.isArray(changed)
&& changed.includes('originalGoalId')
&& changed.includes('goalId')
&& instance.originalGoalId !== null) {
const { goalId } = instance;

// Check if the ActivityReport linked to this ActivityReportGoal has a
// calculatedStatus of 'approved'
const approvedActivityReports = await sequelize.models.ActivityReport.count({
where: {
calculatedStatus: 'approved',
id: instance.activityReportId, // Use the activityReportId from the instance
},
});

const onApprovedAR = approvedActivityReports > 0;

// Update only if the current values differ
await sequelize.models.Goal.update(
{ onAR: true, onApprovedAR },
{
where: {
id: goalId,
[Op.or]: [
// Update if onAR is not already true
{ onAR: { [Op.ne]: true } },
// Update if onApprovedAR differs
{ onApprovedAR: { [Op.ne]: onApprovedAR } },
],
},
individualHooks: true, // Ensure individual hooks are triggered
},
);
}
};

const afterCreate = async (sequelize, instance, options) => {
await processForEmbeddedResources(sequelize, instance, options);
await autoPopulateLinker(sequelize, instance, options);
Expand All @@ -177,6 +218,7 @@ const afterDestroy = async (sequelize, instance, options) => {
const afterUpdate = async (sequelize, instance, options) => {
await processForEmbeddedResources(sequelize, instance, options);
await destroyLinkedSimilarityGroups(sequelize, instance, options);
await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance);
};

export {
Expand All @@ -185,6 +227,7 @@ export {
recalculateOnAR,
propagateDestroyToMetadata,
destroyLinkedSimilarityGroups,
updateOnARAndOnApprovedARForMergedGoals,
afterCreate,
beforeDestroy,
afterDestroy,
Expand Down
177 changes: 176 additions & 1 deletion src/models/hooks/activityReportGoal.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const { Op } = require('sequelize'); // Import Sequelize operators
const { REPORT_STATUSES } = require('@ttahub/common');
const { destroyLinkedSimilarityGroups } = require('./activityReportGoal');
const {
destroyLinkedSimilarityGroups,
updateOnARAndOnApprovedARForMergedGoals,
} = require('./activityReportGoal');

describe('destroyLinkedSimilarityGroups', () => {
afterEach(() => {
Expand Down Expand Up @@ -205,3 +209,174 @@ describe('destroyLinkedSimilarityGroups', () => {
expect(sequelize.models.GoalSimilarityGroup.destroy).not.toHaveBeenCalled();
});
});

describe('updateOnARAndOnApprovedARForMergedGoals', () => {
afterEach(() => {
jest.clearAllMocks();
});

const sequelize = {
models: {
Goal: {
update: jest.fn(),
},
ActivityReport: {
count: jest.fn(),
},
},
};

it('should update onAR and onApprovedAR for merged goals when originalGoalId and goalId are changed', async () => {
const instance = {
goalId: 1,
originalGoalId: 2,
activityReportId: 1,
changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed
};

const options = {
transaction: 'mockTransaction',
};

// Mock the necessary Sequelize methods
// Simulate approved ActivityReport exists
sequelize.models.ActivityReport.count.mockResolvedValue(1);

await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options);

expect(sequelize.models.ActivityReport.count).toHaveBeenCalledWith({
where: {
calculatedStatus: 'approved',
id: instance.activityReportId,
},
});

expect(sequelize.models.Goal.update).toHaveBeenCalledWith(
{ onAR: true, onApprovedAR: true },
{
where: {
id: instance.goalId,
[Op.or]: [
// Ensure onAR condition is in the where clause
{ onAR: { [Op.ne]: true } },
// Ensure onApprovedAR condition is in the where clause
{ onApprovedAR: { [Op.ne]: true } },
],
},
individualHooks: true,
},
);
});

it('should update onAR and onApprovedAR with false when there are no approved activity reports', async () => {
const instance = {
goalId: 1,
originalGoalId: 2,
activityReportId: 1,
changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed
};

const options = {
transaction: 'mockTransaction',
};

// Mock the necessary Sequelize methods
sequelize.models.ActivityReport.count.mockResolvedValue(0); // No approved ActivityReports

await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options);

expect(sequelize.models.ActivityReport.count).toHaveBeenCalledWith({
where: {
calculatedStatus: 'approved',
id: instance.activityReportId,
},
});

expect(sequelize.models.Goal.update).toHaveBeenCalledWith(
{ onAR: true, onApprovedAR: false }, // onApprovedAR is false since no approved reports
{
where: {
id: instance.goalId,
[Op.or]: [
// Ensure onAR condition is in the where clause
{ onAR: { [Op.ne]: true } },
// Ensure onApprovedAR condition is in the where clause
{ onApprovedAR: { [Op.ne]: false } },
],
},
individualHooks: true,
},
);
});

it('should not update if originalGoalId or goalId is not changed', async () => {
const instance = {
goalId: 1,
originalGoalId: 2,
activityReportId: 1,
changed: () => [], // Simulate no changes
};

const options = {
transaction: 'mockTransaction',
};

await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options);

expect(sequelize.models.ActivityReport.count).not.toHaveBeenCalled();
expect(sequelize.models.Goal.update).not.toHaveBeenCalled();
});

it('should not update if originalGoalId is null', async () => {
const instance = {
goalId: 1,
originalGoalId: null,
activityReportId: 1,
changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed
};

const options = {
transaction: 'mockTransaction',
};

await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options);

expect(sequelize.models.ActivityReport.count).not.toHaveBeenCalled();
expect(sequelize.models.Goal.update).not.toHaveBeenCalled();
});

it('should not update if onAR and onApprovedAR are already set to the correct values', async () => {
const instance = {
goalId: 1,
originalGoalId: 2,
activityReportId: 1,
changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed
};

const options = {
transaction: 'mockTransaction',
};

// Mock the necessary Sequelize methods
// Simulate approved ActivityReport exists
sequelize.models.ActivityReport.count.mockResolvedValue(1);
// Simulate that the update doesn't happen
sequelize.models.Goal.update.mockResolvedValue(0);

await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options);

expect(sequelize.models.Goal.update).toHaveBeenCalledWith(
{ onAR: true, onApprovedAR: true },
{
where: {
id: instance.goalId,
[Op.or]: [
{ onAR: { [Op.ne]: true } }, // Check if onAR is already true
{ onApprovedAR: { [Op.ne]: true } }, // Check if onApprovedAR is already true
],
},
individualHooks: true,
},
);
});
});