Skip to content

Commit 8305166

Browse files
CS-345 [Bug] - When adding a new framework to and org, new policies pulled in, aren't regenerated to replace placeholders (#2807)
* fix(api): regenerate newly created policies when adding a framework * fix(api): add error handling for failed policy trigger --------- Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
1 parent c413a9a commit 8305166

3 files changed

Lines changed: 113 additions & 3 deletions

File tree

apps/api/src/frameworks/frameworks-upsert.helper.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export async function upsertOrgFrameworkStructure({
144144
}
145145

146146
// Upsert policy instances
147+
const createdPolicyIds: string[] = [];
147148
const existingPolicies = await tx.policy.findMany({
148149
where: {
149150
organizationId,
@@ -182,6 +183,7 @@ export async function upsertOrgFrameworkStructure({
182183
});
183184

184185
if (newPolicies.length > 0) {
186+
createdPolicyIds.push(...newPolicies.map((p) => p.id));
185187
await tx.policyVersion.createMany({
186188
data: newPolicies.map((p) => ({
187189
policyId: p.id,
@@ -360,5 +362,6 @@ export async function upsertOrgFrameworkStructure({
360362
controlTemplates,
361363
policyTemplates,
362364
taskTemplates,
365+
createdPolicyIds,
363366
};
364367
}

apps/api/src/frameworks/frameworks.controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,12 @@ export class FrameworksController {
125125
async addFrameworks(
126126
@OrganizationId() organizationId: string,
127127
@Body() dto: AddFrameworksDto,
128+
@AuthContext() authContext: AuthContextType,
128129
) {
129130
return this.frameworksService.addFrameworks(
130131
organizationId,
131132
dto.frameworkIds,
133+
authContext.memberId,
132134
);
133135
}
134136

apps/api/src/frameworks/frameworks.service.ts

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
NotFoundException,
66
} from '@nestjs/common';
77
import { db, type EvidenceFormType } from '@db';
8+
9+
import { tasks } from '@trigger.dev/sdk';
810
import {
911
getOverviewScores,
1012
getCurrentMember,
@@ -15,6 +17,7 @@ import { createTimelinesForFrameworks } from './frameworks-timeline.helper';
1517
import { TimelinesService } from '../timelines/timelines.service';
1618
import type { FrameworkManifest } from './framework-versioning/manifest.types';
1719
import { buildUpdatePreview } from './framework-versioning/framework-update-preview';
20+
import type { updatePolicy } from '../trigger/policies/update-policy';
1821

1922
type RequirementDef = {
2023
id: string;
@@ -543,7 +546,11 @@ export class FrameworksService {
543546
return { ...scores, currentMember };
544547
}
545548

546-
async addFrameworks(organizationId: string, frameworkIds: string[]) {
549+
async addFrameworks(
550+
organizationId: string,
551+
frameworkIds: string[],
552+
memberId?: string,
553+
) {
547554
const result = await db.$transaction(async (tx) => {
548555
const frameworks = await tx.frameworkEditorFramework.findMany({
549556
where: { id: { in: frameworkIds }, visible: true },
@@ -558,14 +565,19 @@ export class FrameworksService {
558565

559566
const finalIds = frameworks.map((f) => f.id);
560567

561-
await upsertOrgFrameworkStructure({
568+
const upsertResult = await upsertOrgFrameworkStructure({
562569
organizationId,
563570
targetFrameworkEditorIds: finalIds,
564571
frameworkEditorFrameworks: frameworks,
565572
tx,
566573
});
567574

568-
return { success: true, frameworksAdded: finalIds.length, finalIds };
575+
return {
576+
success: true,
577+
frameworksAdded: finalIds.length,
578+
finalIds,
579+
createdPolicyIds: upsertResult.createdPolicyIds,
580+
};
569581
});
570582

571583
// Auto-create timeline instances from templates for newly added
@@ -584,9 +596,102 @@ export class FrameworksService {
584596
);
585597
});
586598

599+
// Regenerate only newly created policies so placeholder replacement runs
600+
// without touching customer-edited existing policies.
601+
if (result.createdPolicyIds.length > 0) {
602+
this.enqueuePolicyGenerationForNewPolicies({
603+
organizationId,
604+
policyIds: result.createdPolicyIds,
605+
memberId,
606+
}).catch((err) => {
607+
this.logger.warn(
608+
'enqueuePolicyGenerationForNewPolicies failed after framework add',
609+
err,
610+
);
611+
});
612+
}
613+
587614
return { success: result.success, frameworksAdded: result.frameworksAdded };
588615
}
589616

617+
private async enqueuePolicyGenerationForNewPolicies({
618+
organizationId,
619+
policyIds,
620+
memberId,
621+
}: {
622+
organizationId: string;
623+
policyIds: string[];
624+
memberId?: string;
625+
}) {
626+
const [instances, contextEntries] = await Promise.all([
627+
db.frameworkInstance.findMany({
628+
where: { organizationId },
629+
include: { framework: true, customFramework: true },
630+
}),
631+
db.context.findMany({
632+
where: { organizationId },
633+
orderBy: { createdAt: 'asc' },
634+
}),
635+
]);
636+
637+
const normalized = instances.map((fi) => {
638+
if (fi.framework) {
639+
return {
640+
id: fi.framework.id,
641+
name: fi.framework.name,
642+
version: fi.framework.version,
643+
description: fi.framework.description,
644+
visible: fi.framework.visible,
645+
createdAt: fi.framework.createdAt,
646+
updatedAt: fi.framework.updatedAt,
647+
};
648+
}
649+
if (fi.customFramework) {
650+
return {
651+
id: fi.customFramework.id,
652+
name: fi.customFramework.name,
653+
version: fi.customFramework.version,
654+
description: fi.customFramework.description,
655+
visible: true,
656+
createdAt: fi.customFramework.createdAt,
657+
updatedAt: fi.customFramework.updatedAt,
658+
};
659+
}
660+
return null;
661+
});
662+
const uniqueFrameworks = Array.from(
663+
new Map(
664+
normalized
665+
.filter((f): f is NonNullable<typeof f> => f !== null)
666+
.map((f) => [f.id, f]),
667+
).values(),
668+
);
669+
670+
const contextHub = contextEntries
671+
.map((c) => `${c.question}\n${c.answer}`)
672+
.join('\n');
673+
674+
const triggerResults = await Promise.allSettled(
675+
policyIds.map((policyId) =>
676+
tasks.trigger<typeof updatePolicy>('update-policy', {
677+
organizationId,
678+
policyId,
679+
contextHub,
680+
frameworks: uniqueFrameworks,
681+
memberId,
682+
}),
683+
),
684+
);
685+
686+
const failedTrigger = triggerResults.find(
687+
(result): result is PromiseRejectedResult => result.status === 'rejected',
688+
);
689+
if (failedTrigger) {
690+
this.logger.error('Failed to trigger policy update', failedTrigger.reason);
691+
throw new Error('Failed to trigger policy update');
692+
}
693+
}
694+
590695
async findRequirement(
591696
frameworkInstanceId: string,
592697
requirementKey: string,

0 commit comments

Comments
 (0)