Skip to content

Commit 8dbb944

Browse files
github-actions[bot]MarfuenDhanus3133dependabot[bot]golamrabbiazad
authored
[comp] Production Deploy (#1428)
* refactor: enhance EmployeeCompletionChart with profile links - Added external profile links for employees in the EmployeeCompletionChart, allowing users to view detailed profiles. - Integrated organization ID retrieval using useParams for dynamic linking. - Improved layout of employee details for better visual presentation. * fix: Enforce role-based access control in app * fix: Prisma seed command in `packages/db` * fix: Move role checks on org level * fix: Allow access to auditor role * chore: Just restrict access to employee role * chore(deps): bump @tiptap/extension-highlight from 2.22.3 to 3.3.0 (#1404) Bumps [@tiptap/extension-highlight](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-highlight) from 2.22.3 to 3.3.0. - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/develop/packages/extension-highlight/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.3.0/packages/extension-highlight) --- updated-dependencies: - dependency-name: "@tiptap/extension-highlight" dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump dub from 0.63.7 to 0.66.1 (#1399) Bumps [dub](https://github.com/dubinc/dub-ts) from 0.63.7 to 0.66.1. - [Release notes](https://github.com/dubinc/dub-ts/releases) - [Changelog](https://github.com/dubinc/dub-ts/blob/main/RELEASES.md) - [Commits](dubinc/dub-ts@v0.63.7...v0.66.1) --- updated-dependencies: - dependency-name: dub dependency-version: 0.66.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mariano Fuentes <[email protected]> * chore(deps): bump @dub/embed-react from 0.0.15 to 0.0.16 (#1337) Bumps [@dub/embed-react](https://github.com/dubinc/dub) from 0.0.15 to 0.0.16. - [Commits](https://github.com/dubinc/dub/commits) --- updated-dependencies: - dependency-name: "@dub/embed-react" dependency-version: 0.0.16 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mariano Fuentes <[email protected]> * chore: update Header component and enhance NoAccess page layout (#1425) - Added Header component to NoAccess page for better organization context. - Adjusted layout of NoAccess page to improve user experience and visual structure. - Updated Header component to conditionally render AssistantButton based on hideChat prop. Dependencies updated in bun.lock for @dub/embed-react, dub, and @tiptap/extension-highlight. Co-authored-by: Mariano Fuentes <[email protected]> * fix: remove duplicate dependsOn key (#1426) Signed-off-by: Golamrabbi Azad <[email protected]> * [dev] [Marfuen] mariano/videos (#1427) * chore: update Header component and enhance NoAccess page layout - Added Header component to NoAccess page for better organization context. - Adjusted layout of NoAccess page to improve user experience and visual structure. - Updated Header component to conditionally render AssistantButton based on hideChat prop. Dependencies updated in bun.lock for @dub/embed-react, dub, and @tiptap/extension-highlight. * feat: implement training video backfill functionality - Added scripts and server actions to trigger training video completion backfill for all organizations or specific organizations. - Created jobs to handle backfilling of training video records for existing members in organizations. - Enhanced employee onboarding processes by ensuring new members have training video completion entries created upon invitation or organization creation. - Updated README with usage instructions and details on the backfill jobs. This implementation improves the tracking of training video completions for all members, ensuring accurate data representation in the system. --------- Co-authored-by: Mariano Fuentes <[email protected]> * [dev] [Marfuen] mariano/fix-bug (#1429) * chore: update Header component and enhance NoAccess page layout - Added Header component to NoAccess page for better organization context. - Adjusted layout of NoAccess page to improve user experience and visual structure. - Updated Header component to conditionally render AssistantButton based on hideChat prop. Dependencies updated in bun.lock for @dub/embed-react, dub, and @tiptap/extension-highlight. * feat: implement training video backfill functionality - Added scripts and server actions to trigger training video completion backfill for all organizations or specific organizations. - Created jobs to handle backfilling of training video records for existing members in organizations. - Enhanced employee onboarding processes by ensuring new members have training video completion entries created upon invitation or organization creation. - Updated README with usage instructions and details on the backfill jobs. This implementation improves the tracking of training video completions for all members, ensuring accurate data representation in the system. * fix: update database access in training video backfill action - Replaced direct database access reference with a centralized db import for improved consistency and maintainability in the triggerTrainingVideoBackfill action. * chore: remove backfill training videos script - Deleted the backfill-training-videos.ts script as it is no longer needed for triggering training video completion backfill jobs. This change simplifies the codebase and removes redundant functionality. * chore: remove training video backfill action and documentation - Deleted the trigger-training-video-backfill action and its associated README documentation as they are no longer needed. This change streamlines the codebase and eliminates outdated references to backfill functionality. --------- Co-authored-by: Mariano Fuentes <[email protected]> * Mariano/updated employees (#1430) * chore: update Header component and enhance NoAccess page layout - Added Header component to NoAccess page for better organization context. - Adjusted layout of NoAccess page to improve user experience and visual structure. - Updated Header component to conditionally render AssistantButton based on hideChat prop. Dependencies updated in bun.lock for @dub/embed-react, dub, and @tiptap/extension-highlight. * feat: implement training video backfill functionality - Added scripts and server actions to trigger training video completion backfill for all organizations or specific organizations. - Created jobs to handle backfilling of training video records for existing members in organizations. - Enhanced employee onboarding processes by ensuring new members have training video completion entries created upon invitation or organization creation. - Updated README with usage instructions and details on the backfill jobs. This implementation improves the tracking of training video completions for all members, ensuring accurate data representation in the system. * fix: update database access in training video backfill action - Replaced direct database access reference with a centralized db import for improved consistency and maintainability in the triggerTrainingVideoBackfill action. * chore: remove backfill training videos script - Deleted the backfill-training-videos.ts script as it is no longer needed for triggering training video completion backfill jobs. This change simplifies the codebase and removes redundant functionality. * chore: remove training video backfill action and documentation - Deleted the trigger-training-video-backfill action and its associated README documentation as they are no longer needed. This change streamlines the codebase and eliminates outdated references to backfill functionality. * chore: remove training video backfill action and related documentation (#1431) - Deleted the trigger-training-video-backfill action and its associated README documentation as they are no longer needed. This change streamlines the codebase and eliminates outdated references to backfill functionality. Co-authored-by: Mariano Fuentes <[email protected]> * [dev] [Marfuen] mariano/batch (#1432) * chore: remove training video backfill action and related documentation - Deleted the trigger-training-video-backfill action and its associated README documentation as they are no longer needed. This change streamlines the codebase and eliminates outdated references to backfill functionality. * feat: enhance training video backfill process with batch processing - Updated the backfillTrainingVideosForAllOrgs function to process organizations in batches of 500, improving efficiency and handling of large datasets. - Added logging for batch processing to track the number of organizations processed and any errors encountered during triggering. - Adjusted return values to reflect the total number of organizations processed and the number of batches created. --------- Co-authored-by: Mariano Fuentes <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: Golamrabbi Azad <[email protected]> Co-authored-by: Mariano Fuentes <[email protected]> Co-authored-by: Dhanus <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Golamrabbi Azad <[email protected]>
1 parent cdb71c8 commit 8dbb944

File tree

10 files changed

+323
-14
lines changed

10 files changed

+323
-14
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env tsx
2+
3+
/**
4+
* Script to trigger the training video completion backfill job.
5+
*
6+
* Usage:
7+
* # Backfill all organizations
8+
* bun run scripts/backfill-training-videos.ts
9+
*
10+
* # Backfill specific organization
11+
* bun run scripts/backfill-training-videos.ts --org <organizationId>
12+
*
13+
* This script is useful for:
14+
* - Running the backfill manually
15+
* - Testing the backfill process
16+
* - Running on-demand backfills for specific organizations
17+
*/
18+
19+
import { backfillTrainingVideosForAllOrgs } from '@/jobs/tasks/onboarding/backfill-training-videos-for-all-orgs';
20+
import { backfillTrainingVideosForOrg } from '@/jobs/tasks/onboarding/backfill-training-videos-for-org';
21+
22+
async function main() {
23+
const args = process.argv.slice(2);
24+
const orgIndex = args.indexOf('--org');
25+
const organizationId = orgIndex !== -1 ? args[orgIndex + 1] : null;
26+
27+
try {
28+
if (organizationId) {
29+
console.log(`🚀 Triggering training video backfill for organization: ${organizationId}`);
30+
31+
const handle = await backfillTrainingVideosForOrg.trigger({
32+
organizationId: organizationId,
33+
});
34+
35+
console.log(`✅ Successfully triggered job with ID: ${handle.id}`);
36+
console.log(`📊 You can monitor the progress in the Trigger.dev dashboard`);
37+
} else {
38+
console.log('🚀 Triggering training video backfill for ALL organizations');
39+
40+
const handle = await backfillTrainingVideosForAllOrgs.trigger();
41+
42+
console.log(`✅ Successfully triggered batch job with ID: ${handle.id}`);
43+
console.log(`📊 You can monitor the progress in the Trigger.dev dashboard`);
44+
console.log(`⚠️ This will process ALL organizations and their members`);
45+
}
46+
} catch (error) {
47+
console.error('❌ Error triggering backfill job:', error);
48+
process.exit(1);
49+
}
50+
}
51+
52+
// Only run if this script is executed directly
53+
if (require.main === module) {
54+
main().catch((error) => {
55+
console.error('❌ Script failed:', error);
56+
process.exit(1);
57+
});
58+
}

apps/app/src/actions/organization/accept-invitation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use server';
22

3+
import { createTrainingVideoEntries } from '@/lib/db/employee';
34
import { db } from '@db';
45
import { revalidatePath, revalidateTag } from 'next/cache';
56
import { redirect } from 'next/navigation';
@@ -96,7 +97,7 @@ export const completeInvitation = authActionClientWithoutOrg
9697
throw new Error('Invitation role is required');
9798
}
9899

99-
await db.member.create({
100+
const newMember = await db.member.create({
100101
data: {
101102
userId: user.id,
102103
organizationId: invitation.organizationId,
@@ -105,6 +106,9 @@ export const completeInvitation = authActionClientWithoutOrg
105106
},
106107
});
107108

109+
// Create training video completion entries for the new member
110+
await createTrainingVideoEntries(newMember.id);
111+
108112
await db.invitation.update({
109113
where: {
110114
id: invitation.id,

apps/app/src/app/(app)/[orgId]/people/all/actions/addEmployeeWithoutInvite.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use server';
22

3+
import { createTrainingVideoEntries } from '@/lib/db/employee';
34
import { auth } from '@/utils/auth';
45
import type { Role } from '@db';
56
import { db } from '@db';
@@ -61,6 +62,11 @@ export const addEmployeeWithoutInvite = async ({
6162
},
6263
});
6364

65+
// Create training video completion entries for the new member
66+
if (member?.id) {
67+
await createTrainingVideoEntries(member.id);
68+
}
69+
6470
return { success: true, data: member };
6571
} catch (error) {
6672
console.error('Error adding employee:', error);

apps/app/src/app/(app)/[orgId]/people/dashboard/components/EmployeeCompletionChart.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,15 @@ export function EmployeeCompletionChart({
228228
</div>
229229
<p className="text-muted-foreground text-xs">{stat.email}</p>
230230
</div>
231-
<span className="text-muted-foreground">
232-
{stat.policiesCompleted + stat.trainingsCompleted} / {stat.totalTasks}{' '}
233-
{'tasks'}
234-
</span>
231+
<div className="text-muted-foreground text-right text-xs">
232+
<div>
233+
{stat.policiesCompleted + stat.trainingsCompleted} / {stat.totalTasks} tasks
234+
</div>
235+
<div className="text-xs">
236+
{stat.policiesCompleted}/{stat.policiesTotal} policies •{' '}
237+
{stat.trainingsCompleted}/{stat.trainingsTotal} training
238+
</div>
239+
</div>
235240
</div>
236241

237242
<TaskBarChart stat={stat} />

apps/app/src/app/(app)/setup/actions/create-organization-minimal.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { initializeOrganization } from '@/actions/organization/lib/initialize-organization';
44
import { authActionClientWithoutOrg } from '@/actions/safe-action';
5+
import { createTrainingVideoEntries } from '@/lib/db/employee';
56
import { auth } from '@/utils/auth';
67
import { db } from '@db';
78
import { revalidatePath } from 'next/cache';
@@ -64,6 +65,19 @@ export const createOrganizationMinimal = authActionClientWithoutOrg
6465

6566
const orgId = newOrg.id;
6667

68+
// Get the member that was created with the organization (the owner)
69+
const ownerMember = await db.member.findFirst({
70+
where: {
71+
userId: session.user.id,
72+
organizationId: orgId,
73+
},
74+
});
75+
76+
// Create training video completion entries for the owner
77+
if (ownerMember) {
78+
await createTrainingVideoEntries(ownerMember.id);
79+
}
80+
6781
// Create onboarding record for new org
6882
await db.onboarding.create({
6983
data: {

apps/app/src/app/(app)/setup/actions/create-organization.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { initializeOrganization } from '@/actions/organization/lib/initialize-or
44
import { authActionClientWithoutOrg } from '@/actions/safe-action';
55
import { createFleetLabelForOrg } from '@/jobs/tasks/device/create-fleet-label-for-org';
66
import { onboardOrganization as onboardOrganizationTask } from '@/jobs/tasks/onboarding/onboard-organization';
7+
import { createTrainingVideoEntries } from '@/lib/db/employee';
78
import { auth } from '@/utils/auth';
89
import { db } from '@db';
910
import { tasks } from '@trigger.dev/sdk';
@@ -63,6 +64,19 @@ export const createOrganization = authActionClientWithoutOrg
6364

6465
const orgId = newOrg.id;
6566

67+
// Get the member that was created with the organization (the owner)
68+
const ownerMember = await db.member.findFirst({
69+
where: {
70+
userId: session.user.id,
71+
organizationId: orgId,
72+
},
73+
});
74+
75+
// Create training video completion entries for the owner
76+
if (ownerMember) {
77+
await createTrainingVideoEntries(ownerMember.id);
78+
}
79+
6680
// Create onboarding record for new org
6781
await db.onboarding.create({
6882
data: {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { db } from '@db';
2+
import { logger, task } from '@trigger.dev/sdk';
3+
import { backfillTrainingVideosForOrg } from './backfill-training-videos-for-org';
4+
5+
export const backfillTrainingVideosForAllOrgs = task({
6+
id: 'backfill-training-videos-for-all-orgs',
7+
run: async () => {
8+
logger.info('Starting training video completion backfill for all organizations');
9+
10+
try {
11+
// Get all organizations
12+
const organizations = await db.organization.findMany({
13+
select: {
14+
id: true,
15+
name: true,
16+
_count: {
17+
select: {
18+
members: true,
19+
},
20+
},
21+
},
22+
orderBy: {
23+
createdAt: 'asc', // Process older organizations first
24+
},
25+
});
26+
27+
logger.info(`Found ${organizations.length} organizations to process`);
28+
29+
if (organizations.length === 0) {
30+
logger.info('No organizations found, nothing to backfill');
31+
return {
32+
success: true,
33+
organizationsProcessed: 0,
34+
};
35+
}
36+
37+
// Log some stats about what we're about to process
38+
const totalMembers = organizations.reduce((sum, org) => sum + org._count.members, 0);
39+
logger.info(
40+
`About to process ${organizations.length} organizations with a total of ${totalMembers} members`,
41+
);
42+
43+
// Create batch items for processing
44+
const allBatchItems = organizations.map((organization) => ({
45+
payload: {
46+
organizationId: organization.id,
47+
},
48+
}));
49+
50+
// Split into chunks of 500 (Trigger.dev batch size limit)
51+
const BATCH_SIZE = 500;
52+
const batches: (typeof allBatchItems)[] = [];
53+
54+
for (let i = 0; i < allBatchItems.length; i += BATCH_SIZE) {
55+
batches.push(allBatchItems.slice(i, i + BATCH_SIZE));
56+
}
57+
58+
logger.info(
59+
`Splitting ${allBatchItems.length} organizations into ${batches.length} batches of max ${BATCH_SIZE} each`,
60+
);
61+
62+
// Process each batch
63+
let totalTriggered = 0;
64+
for (let i = 0; i < batches.length; i++) {
65+
const batch = batches[i];
66+
logger.info(
67+
`Triggering batch ${i + 1}/${batches.length} with ${batch.length} organizations`,
68+
);
69+
70+
try {
71+
await backfillTrainingVideosForOrg.batchTrigger(batch);
72+
totalTriggered += batch.length;
73+
logger.info(`Successfully triggered batch ${i + 1}/${batches.length}`);
74+
} catch (error) {
75+
logger.error(`Failed to trigger batch ${i + 1}/${batches.length}: ${error}`);
76+
throw error;
77+
}
78+
}
79+
80+
logger.info(
81+
`Successfully triggered training video backfill jobs for ${totalTriggered} organizations across ${batches.length} batches`,
82+
);
83+
84+
return {
85+
success: true,
86+
organizationsProcessed: totalTriggered,
87+
totalBatches: batches.length,
88+
totalMembers,
89+
};
90+
} catch (error) {
91+
logger.error(`Error during training video backfill batch trigger: ${error}`);
92+
throw error;
93+
}
94+
},
95+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { trainingVideos } from '@/lib/data/training-videos';
2+
import { db } from '@db';
3+
import { logger, task } from '@trigger.dev/sdk';
4+
5+
export const backfillTrainingVideosForOrg = task({
6+
id: 'backfill-training-videos-for-org',
7+
retry: {
8+
maxAttempts: 3,
9+
},
10+
run: async (payload: { organizationId: string }) => {
11+
logger.info(`Starting training video backfill for organization ${payload.organizationId}`);
12+
13+
try {
14+
// Get all members for this organization
15+
const members = await db.member.findMany({
16+
where: {
17+
organizationId: payload.organizationId,
18+
},
19+
select: {
20+
id: true,
21+
user: {
22+
select: {
23+
email: true,
24+
},
25+
},
26+
},
27+
});
28+
29+
logger.info(`Found ${members.length} members in organization ${payload.organizationId}`);
30+
31+
if (members.length === 0) {
32+
logger.info(`No members found for organization ${payload.organizationId}, skipping`);
33+
return {
34+
success: true,
35+
organizationId: payload.organizationId,
36+
membersProcessed: 0,
37+
recordsCreated: 0,
38+
};
39+
}
40+
41+
let totalRecordsCreated = 0;
42+
let membersProcessed = 0;
43+
let membersSkipped = 0;
44+
45+
// Process each member
46+
for (const member of members) {
47+
try {
48+
logger.info(`Processing member ${member.id} (${member.user.email})`);
49+
50+
// Check if this member already has any training video completion records
51+
// (including old video IDs like sat-1, sat-2, etc.)
52+
const existingRecords = await db.employeeTrainingVideoCompletion.findMany({
53+
where: {
54+
memberId: member.id,
55+
},
56+
select: {
57+
videoId: true,
58+
},
59+
});
60+
61+
if (existingRecords.length > 0) {
62+
const videoIds = existingRecords.map((r) => r.videoId).join(', ');
63+
logger.info(
64+
`Member ${member.id} already has ${existingRecords.length} training video completion records (${videoIds}), skipping`,
65+
);
66+
membersSkipped++;
67+
continue;
68+
}
69+
70+
// Create training video completion entries for this member
71+
// Using skipDuplicates to prevent duplicate records
72+
const result = await db.employeeTrainingVideoCompletion.createMany({
73+
data: trainingVideos.map((video) => ({
74+
memberId: member.id,
75+
videoId: video.id,
76+
})),
77+
skipDuplicates: true,
78+
});
79+
80+
totalRecordsCreated += result.count;
81+
membersProcessed++;
82+
83+
logger.info(
84+
`Created ${result.count} training video completion records for member ${member.id}`,
85+
);
86+
} catch (memberError) {
87+
logger.error(
88+
`Failed to process member ${member.id} (${member.user.email}): ${memberError}`,
89+
);
90+
// Continue processing other members even if one fails
91+
}
92+
}
93+
94+
logger.info(
95+
`Completed training video backfill for organization ${payload.organizationId}. ` +
96+
`Total members: ${members.length}, New records created: ${membersProcessed}, ` +
97+
`Skipped (already had records): ${membersSkipped}, Records created: ${totalRecordsCreated}`,
98+
);
99+
100+
return {
101+
success: true,
102+
organizationId: payload.organizationId,
103+
membersProcessed,
104+
membersSkipped,
105+
totalMembers: members.length,
106+
recordsCreated: totalRecordsCreated,
107+
};
108+
} catch (error) {
109+
logger.error(
110+
`Error during training video backfill for organization ${payload.organizationId}: ${error}`,
111+
);
112+
throw error;
113+
}
114+
},
115+
});

0 commit comments

Comments
 (0)