Skip to content

Commit aebe81e

Browse files
github-actions[bot]carhartlewisMarfuen
authored
[dev] [carhartlewis] lewis/comp-document-tweaks (#2150)
* feat(meeting): add meeting minutes form and improve validation * feat(findings): add evidence form type support in findings retrieval and creation * feat(forms): add meetingSubTypeValues for improved form handling * feat(meeting): add AI analysis for meeting minutes submission * fix(meeting): change some to every in outstanding document check * fix(meeting): change some to every in meeting todo check * feat(evidence-forms): add mapping functions for evidence form type conversion * fix(CompanyFormPageClient): remove period from empty title text * feat(evidence-forms): add reviewSubmission method with user relations --------- Co-authored-by: Lewis Carhart <lewis@trycomp.ai> Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
1 parent 7dc610d commit aebe81e

36 files changed

Lines changed: 1651 additions & 346 deletions

apps/api/src/devices/dto/device-responses.dto.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,12 @@ export class DeviceResponseDto {
8080
software_updated_at: string;
8181

8282
@ApiProperty({
83-
description: 'Device ID (numeric for Fleet devices, string UUID for device-agent devices)',
84-
oneOf: [{ type: 'number', example: 123 }, { type: 'string', example: 'clx1abc123' }],
83+
description:
84+
'Device ID (numeric for Fleet devices, string UUID for device-agent devices)',
85+
oneOf: [
86+
{ type: 'number', example: 123 },
87+
{ type: 'string', example: 'clx1abc123' },
88+
],
8589
})
8690
id: number | string;
8791

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { EvidenceFormType as DbEvidenceFormType } from '@trycompai/db';
2+
import { type EvidenceFormType } from './evidence-forms.definitions';
3+
4+
const EXTERNAL_TO_DB: Record<EvidenceFormType, DbEvidenceFormType> = {
5+
'board-meeting': DbEvidenceFormType.board_meeting,
6+
'it-leadership-meeting': DbEvidenceFormType.it_leadership_meeting,
7+
'risk-committee-meeting': DbEvidenceFormType.risk_committee_meeting,
8+
meeting: DbEvidenceFormType.meeting,
9+
'access-request': DbEvidenceFormType.access_request,
10+
'whistleblower-report': DbEvidenceFormType.whistleblower_report,
11+
'penetration-test': DbEvidenceFormType.penetration_test,
12+
'rbac-matrix': DbEvidenceFormType.rbac_matrix,
13+
'infrastructure-inventory': DbEvidenceFormType.infrastructure_inventory,
14+
'employee-performance-evaluation':
15+
DbEvidenceFormType.employee_performance_evaluation,
16+
};
17+
18+
const DB_TO_EXTERNAL: Record<DbEvidenceFormType, EvidenceFormType> = {
19+
[DbEvidenceFormType.board_meeting]: 'board-meeting',
20+
[DbEvidenceFormType.it_leadership_meeting]: 'it-leadership-meeting',
21+
[DbEvidenceFormType.risk_committee_meeting]: 'risk-committee-meeting',
22+
[DbEvidenceFormType.meeting]: 'meeting',
23+
[DbEvidenceFormType.access_request]: 'access-request',
24+
[DbEvidenceFormType.whistleblower_report]: 'whistleblower-report',
25+
[DbEvidenceFormType.penetration_test]: 'penetration-test',
26+
[DbEvidenceFormType.rbac_matrix]: 'rbac-matrix',
27+
[DbEvidenceFormType.infrastructure_inventory]: 'infrastructure-inventory',
28+
[DbEvidenceFormType.employee_performance_evaluation]:
29+
'employee-performance-evaluation',
30+
};
31+
32+
export function toDbEvidenceFormType(
33+
formType: EvidenceFormType,
34+
): DbEvidenceFormType {
35+
return EXTERNAL_TO_DB[formType];
36+
}
37+
38+
export function toExternalEvidenceFormType(
39+
formType: DbEvidenceFormType | null | undefined,
40+
): EvidenceFormType | null {
41+
if (!formType) return null;
42+
return DB_TO_EXTERNAL[formType];
43+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import type { AuthContext } from '@/auth/types';
2+
import { EvidenceFormsService } from './evidence-forms.service';
3+
import type { AttachmentsService } from '@/attachments/attachments.service';
4+
import { db } from '@trycompai/db';
5+
6+
jest.mock(
7+
'@/attachments/attachments.service',
8+
() => ({
9+
AttachmentsService: class AttachmentsService {},
10+
}),
11+
{ virtual: true },
12+
);
13+
14+
jest.mock('@trycompai/db', () => {
15+
const evidenceFormTypeEnum = {
16+
board_meeting: 'board_meeting',
17+
it_leadership_meeting: 'it_leadership_meeting',
18+
risk_committee_meeting: 'risk_committee_meeting',
19+
meeting: 'meeting',
20+
access_request: 'access_request',
21+
whistleblower_report: 'whistleblower_report',
22+
penetration_test: 'penetration_test',
23+
rbac_matrix: 'rbac_matrix',
24+
infrastructure_inventory: 'infrastructure_inventory',
25+
employee_performance_evaluation: 'employee_performance_evaluation',
26+
};
27+
28+
return {
29+
EvidenceFormType: evidenceFormTypeEnum,
30+
db: {
31+
evidenceSubmission: {
32+
findFirst: jest.fn(),
33+
update: jest.fn(),
34+
},
35+
},
36+
};
37+
});
38+
39+
type MockDb = {
40+
evidenceSubmission: {
41+
findFirst: jest.Mock;
42+
update: jest.Mock;
43+
};
44+
};
45+
46+
describe('EvidenceFormsService', () => {
47+
const authContext: AuthContext = {
48+
organizationId: 'org_123',
49+
authType: 'jwt',
50+
isApiKey: false,
51+
userRoles: ['admin'],
52+
userId: 'usr_reviewer',
53+
userEmail: 'reviewer@example.com',
54+
};
55+
56+
const attachmentsServiceMock = {
57+
uploadToS3: jest.fn(),
58+
getPresignedDownloadUrl: jest.fn(),
59+
} as unknown as AttachmentsService;
60+
61+
const service = new EvidenceFormsService(attachmentsServiceMock);
62+
const mockedDb = db as unknown as MockDb;
63+
64+
beforeEach(() => {
65+
jest.clearAllMocks();
66+
});
67+
68+
describe('reviewSubmission', () => {
69+
it('includes submittedBy and reviewedBy relations on review update', async () => {
70+
mockedDb.evidenceSubmission.findFirst.mockResolvedValue({
71+
id: 'sub_123',
72+
status: 'pending',
73+
});
74+
mockedDb.evidenceSubmission.update.mockResolvedValue({
75+
id: 'sub_123',
76+
formType: 'meeting',
77+
submittedBy: {
78+
id: 'usr_submitter',
79+
name: 'Submitter',
80+
email: 'submitter@example.com',
81+
},
82+
reviewedBy: {
83+
id: 'usr_reviewer',
84+
name: 'Reviewer',
85+
email: 'reviewer@example.com',
86+
},
87+
});
88+
89+
const result = await service.reviewSubmission({
90+
organizationId: 'org_123',
91+
formType: 'meeting',
92+
submissionId: 'sub_123',
93+
payload: {
94+
action: 'approved',
95+
},
96+
authContext,
97+
});
98+
99+
expect(mockedDb.evidenceSubmission.update).toHaveBeenCalledWith(
100+
expect.objectContaining({
101+
where: { id: 'sub_123' },
102+
include: {
103+
submittedBy: {
104+
select: {
105+
id: true,
106+
name: true,
107+
email: true,
108+
},
109+
},
110+
reviewedBy: {
111+
select: {
112+
id: true,
113+
name: true,
114+
email: true,
115+
},
116+
},
117+
},
118+
}),
119+
);
120+
expect(result).toMatchObject({
121+
submittedBy: {
122+
id: 'usr_submitter',
123+
name: 'Submitter',
124+
email: 'submitter@example.com',
125+
},
126+
reviewedBy: {
127+
id: 'usr_reviewer',
128+
name: 'Reviewer',
129+
email: 'reviewer@example.com',
130+
},
131+
formType: 'meeting',
132+
});
133+
});
134+
});
135+
});

0 commit comments

Comments
 (0)