Skip to content

Commit 0b9fd59

Browse files
authored
Merge pull request #2741 from trycompai/main
[comp] Production Deploy
2 parents cf8cfe3 + 4b5c527 commit 0b9fd59

81 files changed

Lines changed: 6950 additions & 6370 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/api/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"@aws-sdk/s3-request-presigner": "3.1013.0",
6262
"@browserbasehq/sdk": "2.6.0",
6363
"@browserbasehq/stagehand": "^3.2.1",
64-
"@maced/api-client": "^0.9.1",
64+
"@maced/api-client": "^0.9.2",
6565
"@mendable/firecrawl-js": "^4.9.3",
6666
"@nestjs/common": "^11.0.1",
6767
"@nestjs/config": "^4.0.2",

‎apps/api/src/controls/controls.service.ts‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ export class ControlsService {
9999
const formTypes = (control.controlDocumentTypes ?? []).map(
100100
(d) => d.formType,
101101
);
102+
const notRelevantSettings =
103+
formTypes.length > 0
104+
? await db.evidenceFormSetting.findMany({
105+
where: {
106+
organizationId,
107+
formType: { in: formTypes },
108+
isNotRelevant: true,
109+
},
110+
select: { formType: true },
111+
})
112+
: [];
113+
const notRelevantFormTypes = new Set(
114+
notRelevantSettings.map((setting) => setting.formType),
115+
);
102116
const submissionCountsByFormType: Record<string, number> = {};
103117
if (formTypes.length > 0) {
104118
const grouped = await db.evidenceSubmission.groupBy({
@@ -133,6 +147,12 @@ export class ControlsService {
133147

134148
return {
135149
...control,
150+
controlDocumentTypes: (control.controlDocumentTypes ?? []).map(
151+
(documentType) => ({
152+
...documentType,
153+
isNotRelevant: notRelevantFormTypes.has(documentType.formType),
154+
}),
155+
),
136156
submissionCountsByFormType,
137157
progress: {
138158
total: totalItems,
@@ -309,7 +329,12 @@ export class ControlsService {
309329
// policies. Checking only archivedAt would let user-archived policies
310330
// get re-linked to a control and surface back through the UI.
311331
const policies = await db.policy.findMany({
312-
where: { id: { in: uniqueIds }, organizationId, archivedAt: null, isArchived: false },
332+
where: {
333+
id: { in: uniqueIds },
334+
organizationId,
335+
archivedAt: null,
336+
isArchived: false,
337+
},
313338
select: { id: true },
314339
});
315340
if (policies.length !== uniqueIds.length) {

‎apps/api/src/evidence-forms/evidence-forms.controller.spec.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ jest.mock('../auth/auth.server', () => ({
99
auth: { api: { getSession: jest.fn() } },
1010
}));
1111

12+
jest.mock('./evidence-forms.service', () => ({
13+
EvidenceFormsService: class EvidenceFormsService {},
14+
}));
15+
16+
jest.mock('../auth/hybrid-auth.guard', () => ({
17+
HybridAuthGuard: class HybridAuthGuard {},
18+
}));
19+
20+
jest.mock('../auth/permission.guard', () => ({
21+
PermissionGuard: class PermissionGuard {},
22+
}));
23+
1224
jest.mock('@trycompai/auth', () => ({
1325
statement: {
1426
evidence: ['create', 'read', 'update', 'delete'],
@@ -23,6 +35,8 @@ describe('EvidenceFormsController', () => {
2335
const mockService = {
2436
listForms: jest.fn(),
2537
getFormStatuses: jest.fn(),
38+
getFormSettings: jest.fn(),
39+
updateFormSetting: jest.fn(),
2640
getMySubmissions: jest.fn(),
2741
getPendingSubmissionCount: jest.fn(),
2842
getFormWithSubmissions: jest.fn(),
@@ -88,6 +102,39 @@ describe('EvidenceFormsController', () => {
88102
});
89103
});
90104

105+
describe('getFormSettings', () => {
106+
it('should call service.getFormSettings with organizationId', async () => {
107+
const mockSettings = [{ formType: 'meeting', isNotRelevant: false }];
108+
mockService.getFormSettings.mockResolvedValue(mockSettings);
109+
110+
const result = await controller.getFormSettings('org_1');
111+
112+
expect(result).toEqual(mockSettings);
113+
expect(service.getFormSettings).toHaveBeenCalledWith('org_1');
114+
});
115+
});
116+
117+
describe('updateFormSetting', () => {
118+
it('should call service.updateFormSetting with correct params', async () => {
119+
const mockSetting = { formType: 'meeting', isNotRelevant: true };
120+
const payload = { isNotRelevant: true };
121+
mockService.updateFormSetting.mockResolvedValue(mockSetting);
122+
123+
const result = await controller.updateFormSetting(
124+
'org_1',
125+
'meeting',
126+
payload,
127+
);
128+
129+
expect(result).toEqual(mockSetting);
130+
expect(service.updateFormSetting).toHaveBeenCalledWith({
131+
organizationId: 'org_1',
132+
formType: 'meeting',
133+
payload,
134+
});
135+
});
136+
});
137+
91138
describe('getMySubmissions', () => {
92139
it('should call service.getMySubmissions with correct params', async () => {
93140
const mockSubmissions = [{ id: 'sub_1' }];

‎apps/api/src/evidence-forms/evidence-forms.controller.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,36 @@ export class EvidenceFormsController {
5555
return this.evidenceFormsService.getFormStatuses(organizationId);
5656
}
5757

58+
@Get('settings')
59+
@RequirePermission('evidence', 'read')
60+
@ApiOperation({
61+
summary: 'Get document relevance settings',
62+
description:
63+
'Returns organization-scoped relevance settings for evidence forms',
64+
})
65+
async getFormSettings(@OrganizationId() organizationId: string) {
66+
return this.evidenceFormsService.getFormSettings(organizationId);
67+
}
68+
69+
@Patch(':formType/settings')
70+
@RequirePermission('evidence', 'update')
71+
@ApiOperation({
72+
summary: 'Update document relevance setting',
73+
description:
74+
'Marks an evidence form as relevant or not relevant for the active organization',
75+
})
76+
async updateFormSetting(
77+
@OrganizationId() organizationId: string,
78+
@Param('formType') formType: string,
79+
@Body() body: unknown,
80+
) {
81+
return this.evidenceFormsService.updateFormSetting({
82+
organizationId,
83+
formType,
84+
payload: body,
85+
});
86+
}
87+
5888
@Get('my-submissions')
5989
@RequirePermission('evidence', 'read')
6090
@ApiOperation({

‎apps/api/src/evidence-forms/evidence-forms.service.spec.ts‎

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ jest.mock(
1111
{ virtual: true },
1212
);
1313

14+
jest.mock('../frameworks/frameworks-timeline.helper', () => ({
15+
checkAutoCompletePhases: jest.fn(),
16+
}));
17+
18+
jest.mock('../timelines/timelines.service', () => ({
19+
TimelinesService: class TimelinesService {},
20+
}));
21+
1422
jest.mock('@db', () => {
1523
const evidenceFormTypeEnum = {
1624
board_meeting: 'board_meeting',
@@ -23,24 +31,36 @@ jest.mock('@db', () => {
2331
rbac_matrix: 'rbac_matrix',
2432
infrastructure_inventory: 'infrastructure_inventory',
2533
employee_performance_evaluation: 'employee_performance_evaluation',
34+
network_diagram: 'network_diagram',
35+
tabletop_exercise: 'tabletop_exercise',
2636
};
2737

2838
return {
2939
EvidenceFormType: evidenceFormTypeEnum,
3040
db: {
3141
evidenceSubmission: {
3242
findFirst: jest.fn(),
43+
groupBy: jest.fn(),
3344
update: jest.fn(),
3445
},
46+
evidenceFormSetting: {
47+
findMany: jest.fn(),
48+
upsert: jest.fn(),
49+
},
3550
},
3651
};
3752
});
3853

3954
type MockDb = {
4055
evidenceSubmission: {
4156
findFirst: jest.Mock;
57+
groupBy: jest.Mock;
4258
update: jest.Mock;
4359
};
60+
evidenceFormSetting: {
61+
findMany: jest.Mock;
62+
upsert: jest.Mock;
63+
};
4464
};
4565

4666
describe('EvidenceFormsService', () => {
@@ -59,15 +79,114 @@ describe('EvidenceFormsService', () => {
5979
getPresignedDownloadUrl: jest.fn(),
6080
} as unknown as AttachmentsService;
6181

62-
const timelinesServiceMock = {} as unknown as import('../timelines/timelines.service').TimelinesService;
82+
const timelinesServiceMock =
83+
{} as unknown as import('../timelines/timelines.service').TimelinesService;
6384

64-
const service = new EvidenceFormsService(attachmentsServiceMock, timelinesServiceMock);
85+
const service = new EvidenceFormsService(
86+
attachmentsServiceMock,
87+
timelinesServiceMock,
88+
);
6589
const mockedDb = db as unknown as MockDb;
6690

6791
beforeEach(() => {
6892
jest.clearAllMocks();
6993
});
7094

95+
describe('getFormStatuses', () => {
96+
it('includes relevance settings alongside latest submission dates', async () => {
97+
mockedDb.evidenceSubmission.groupBy.mockResolvedValue([
98+
{
99+
formType: 'meeting',
100+
_max: { submittedAt: new Date('2026-01-01T00:00:00.000Z') },
101+
},
102+
]);
103+
mockedDb.evidenceFormSetting.findMany.mockResolvedValue([
104+
{ formType: 'meeting', isNotRelevant: true },
105+
]);
106+
107+
const result = await service.getFormStatuses('org_123');
108+
109+
expect(mockedDb.evidenceFormSetting.findMany).toHaveBeenCalledWith({
110+
where: { organizationId: 'org_123' },
111+
select: { formType: true, isNotRelevant: true },
112+
});
113+
expect(result.meeting).toEqual({
114+
lastSubmittedAt: '2026-01-01T00:00:00.000Z',
115+
isNotRelevant: true,
116+
});
117+
expect(result['access-request']).toEqual({
118+
lastSubmittedAt: null,
119+
isNotRelevant: false,
120+
});
121+
});
122+
});
123+
124+
describe('getFormSettings', () => {
125+
it('returns one setting entry for every document form', async () => {
126+
mockedDb.evidenceFormSetting.findMany.mockResolvedValue([
127+
{
128+
formType: 'access_request',
129+
isNotRelevant: true,
130+
updatedAt: new Date('2026-01-02T00:00:00.000Z'),
131+
},
132+
]);
133+
134+
const result = await service.getFormSettings('org_123');
135+
136+
expect(result).toContainEqual({
137+
formType: 'access-request',
138+
isNotRelevant: true,
139+
updatedAt: '2026-01-02T00:00:00.000Z',
140+
});
141+
expect(result).toContainEqual({
142+
formType: 'meeting',
143+
isNotRelevant: false,
144+
updatedAt: null,
145+
});
146+
});
147+
});
148+
149+
describe('updateFormSetting', () => {
150+
it('upserts a document relevance setting', async () => {
151+
mockedDb.evidenceFormSetting.upsert.mockResolvedValue({
152+
formType: 'meeting',
153+
isNotRelevant: true,
154+
updatedAt: new Date('2026-01-03T00:00:00.000Z'),
155+
});
156+
157+
const result = await service.updateFormSetting({
158+
organizationId: 'org_123',
159+
formType: 'meeting',
160+
payload: { isNotRelevant: true },
161+
});
162+
163+
expect(mockedDb.evidenceFormSetting.upsert).toHaveBeenCalledWith({
164+
where: {
165+
organizationId_formType: {
166+
organizationId: 'org_123',
167+
formType: 'meeting',
168+
},
169+
},
170+
create: {
171+
organizationId: 'org_123',
172+
formType: 'meeting',
173+
isNotRelevant: true,
174+
},
175+
update: { isNotRelevant: true },
176+
select: {
177+
formType: true,
178+
isNotRelevant: true,
179+
updatedAt: true,
180+
},
181+
});
182+
expect(result).toEqual({
183+
formType: 'meeting',
184+
isNotRelevant: true,
185+
updatedAt: '2026-01-03T00:00:00.000Z',
186+
});
187+
});
188+
});
189+
71190
describe('reviewSubmission', () => {
72191
it('includes submittedBy and reviewedBy relations on review update', async () => {
73192
mockedDb.evidenceSubmission.findFirst.mockResolvedValue({

0 commit comments

Comments
 (0)