Skip to content
Merged
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
6 changes: 5 additions & 1 deletion apps/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ WORKDIR /app
COPY package.json ./

# Install bun and deps
# Note: workspace:* dependencies will be skipped and copied manually below
RUN curl -fsSL https://bun.sh/install | bash \
&& export PATH="/root/.bun/bin:$PATH" \
&& bun install --production --ignore-scripts
&& bun install --production --ignore-scripts || true

# Copy pre-built workspace packages (must be before copying app contents)
COPY node_modules/@trycompai ./node_modules/@trycompai

# Now copy the pre-built app contents (dist/, prisma/, etc.)
COPY . .
Expand Down
16 changes: 16 additions & 0 deletions apps/api/buildspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ phases:
- echo "Installing API dependencies only..."
- bun install --filter=@comp/api --frozen-lockfile || bun install --filter=@comp/api --ignore-scripts || bun install --ignore-scripts

# Build email package (required dependency for API)
- echo "Building @trycompai/email package..."
- cd packages/email
- bun run build
- cd ../..

# Build NestJS application (prebuild automatically handles Prisma)
- echo "Building NestJS application..."
- echo "APP_NAME is set to $APP_NAME"
Expand Down Expand Up @@ -69,6 +75,16 @@ phases:
# Copy entire node_modules for runtime (includes @trycompai/db from npm)
- echo "Skipping host node_modules copy; Dockerfile installs prod deps inside image"

# Copy built workspace packages (needed for runtime)
# Only copy packages that are actually imported at runtime
# Email package is already bundled, so only need its dist
- echo "Copying built workspace packages..."
- mkdir -p ../docker-build/node_modules/@trycompai
# Email package - copy dist and package.json (needed at runtime)
- mkdir -p ../docker-build/node_modules/@trycompai/email
- cp -r ../../packages/email/dist ../docker-build/node_modules/@trycompai/email/
- cp ../../packages/email/package.json ../docker-build/node_modules/@trycompai/email/

# Copy Dockerfile
- echo "Copying Dockerfile..."
- cp Dockerfile ../docker-build/
Expand Down
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@nestjs/swagger": "^11.2.0",
"@prisma/client": "^6.13.0",
"@trycompai/db": "^1.3.17",
"@trycompai/email": "workspace:*",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.3.27",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ControlRequirementsTableColumns: ColumnDef<RequirementTableData>[]
const isCompleted = requirement.policy
? requirement.policy?.status === 'published'
: requirement.task
? requirement.task?.status === 'done'
? requirement.task?.status === 'done' || requirement.task?.status === 'not_relevant'
: false;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const getOrganizationControlProgress = async (controlId: string) => {

progress.byType[taskTypeKey].total++;

const isCompleted = task.status === 'done';
const isCompleted = task.status === 'done' || task.status === 'not_relevant';

if (isCompleted) {
progress.completed++;
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/app/(app)/[orgId]/controls/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function getControlStatus(control: ControlWithRelations): StatusType {
const tasks = control.tasks || [];

const allPoliciesArePublished = policies.every((policy) => policy.status === 'published');
const allTasksAreCompleted = tasks.every((task) => task.status === 'done');
const allTasksAreCompleted = tasks.every((task) => task.status === 'done' || task.status === 'not_relevant');

if (!allPoliciesArePublished && !allTasksAreCompleted) {
return 'not_started';
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/app/(app)/[orgId]/frameworks/lib/compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function computeFrameworkStats(
for (const t of frameworkTasks) uniqueTaskMap.set(t.id, t);
const uniqueTasks = Array.from(uniqueTaskMap.values());
const totalTasks = uniqueTasks.length;
const doneTasks = uniqueTasks.filter((t) => t.status === 'done').length;
const doneTasks = uniqueTasks.filter((t) => t.status === 'done' || t.status === 'not_relevant').length;
const taskRatio = totalTasks > 0 ? doneTasks / totalTasks : 1;

const complianceScore = Math.round(((policyRatio + taskRatio) / 2) * 100);
Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/app/(app)/[orgId]/frameworks/lib/getTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export const getDoneTasks = cache(async (organizationId: string) => {
},
});

const doneTasks = tasks.filter((t) => t.status === 'done');
const doneTasks = tasks.filter((t) => t.status === 'done' || t.status === 'not_relevant');
const incompleteTasks = tasks.filter(
(t) => t.status === 'todo' || t.status === 'in_progress' || t.status === 'not_relevant',
(t) => t.status === 'todo' || t.status === 'in_progress',
);

return {
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/app/(app)/[orgId]/frameworks/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function getControlStatus(
(policy) => policy.status === 'published', // Simplified from artifact.policy.status
);
const allTasksDone =
controlTasks.length > 0 && controlTasks.every((task) => task.status === 'done');
controlTasks.length > 0 && controlTasks.every((task) => task.status === 'done' || task.status === 'not_relevant');

if (allPoliciesPublished && (controlTasks.length === 0 || allTasksDone)) return 'completed';
if (allPoliciesDraft && allTasksTodo) return 'not_started';
Expand Down
20 changes: 17 additions & 3 deletions apps/app/src/app/(app)/[orgId]/tasks/components/ModernTaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,24 @@ export function ModernTaskList({ tasks, members, statusFilter }: ModernTaskListP
<div className="divide-y divide-slate-100 overflow-hidden rounded-lg border border-slate-200/60 bg-white">
{statusTasks.map((task, index) => {
const member = assignedMember(task);
const isNotRelevant = task.status === 'not_relevant';
return (
<div
key={task.id}
className="group flex items-center gap-4 p-4 transition-colors hover:bg-slate-50/50 cursor-pointer"
className={`group relative flex items-center gap-4 p-4 transition-colors cursor-pointer ${
isNotRelevant
? 'opacity-50 bg-slate-100/50 backdrop-blur-md hover:bg-slate-100/60'
: 'hover:bg-slate-50/50'
}`}
onClick={() => handleTaskClick(task.id)}
>
{isNotRelevant && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<span className="text-sm font-bold uppercase tracking-[0.15em] text-slate-600">
NOT RELEVANT
</span>
</div>
)}
<div
className="flex shrink-0 items-center"
onClick={(e) => e.stopPropagation()}
Expand All @@ -128,14 +140,16 @@ export function ModernTaskList({ tasks, members, statusFilter }: ModernTaskListP
<div className="flex min-w-0 flex-1 items-center gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<div className="text-sm font-semibold text-slate-900">{task.title}</div>
<div className={`text-sm font-semibold ${isNotRelevant ? 'text-slate-500' : 'text-slate-900'}`}>
{task.title}
</div>
<AutomationIndicator
automations={task.evidenceAutomations}
variant="inline"
/>
</div>
{task.description && (
<div className="text-slate-500 mt-0.5 line-clamp-1 text-xs">
<div className={`mt-0.5 line-clamp-1 text-xs ${isNotRelevant ? 'text-slate-400' : 'text-slate-500'}`}>
{task.description}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function TaskList({
// Calculate overall stats from all tasks (not filtered)
const overallStats = useMemo(() => {
const total = initialTasks.length;
const done = initialTasks.filter((t) => t.status === 'done').length;
const done = initialTasks.filter((t) => t.status === 'done' || t.status === 'not_relevant').length;
const inProgress = initialTasks.filter((t) => t.status === 'in_progress').length;
const todo = initialTasks.filter((t) => t.status === 'todo').length;
const completionRate = total > 0 ? Math.round((done / total) * 100) : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
// Calculate stats for a category
const getCategoryStats = (categoryTasks: Task[]) => {
const total = categoryTasks.length;
const done = categoryTasks.filter((t) => t.status === 'done').length;
const done = categoryTasks.filter((t) => t.status === 'done' || t.status === 'not_relevant').length;
const inProgress = categoryTasks.filter((t) => t.status === 'in_progress').length;
const completionRate = total > 0 ? Math.round((done / total) * 100) : 0;
return { total, done, inProgress, completionRate };
Expand Down Expand Up @@ -262,20 +262,34 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
const member = assignedMember(task);
const statusStyle =
statusPalette[task.status as keyof typeof statusPalette] ?? statusPalette.todo;
const isNotRelevant = task.status === 'not_relevant';
return (
<div
key={task.id}
className="group relative flex cursor-pointer flex-col gap-3 rounded-sm border border-border/60 bg-card/50 p-4 transition-colors hover:bg-muted/20"
className={`group relative flex cursor-pointer flex-col gap-3 rounded-sm border border-border/60 p-4 transition-colors ${
isNotRelevant
? 'opacity-50 bg-slate-100/50 backdrop-blur-md hover:bg-slate-100/60'
: 'bg-card/50 hover:bg-muted/20'
}`}
onClick={() => handleTaskClick(task.id)}
>
<span
className={`absolute left-0 top-0 h-full w-[2px] ${statusStyle.indicator}`}
/>
{isNotRelevant && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<span className="text-sm font-bold uppercase tracking-[0.15em] text-slate-600">
NOT RELEVANT
</span>
</div>
)}

<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1 space-y-2">
<div className="flex items-start gap-2">
<h4 className="flex-1 text-sm font-semibold text-foreground line-clamp-2">
<h4 className={`flex-1 text-sm font-semibold line-clamp-2 ${
isNotRelevant ? 'text-slate-500' : 'text-foreground'
}`}>
{task.title}
</h4>
<AutomationIndicator
Expand All @@ -284,7 +298,9 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
/>
</div>
{task.description && (
<p className="text-xs text-muted-foreground/80 line-clamp-2 leading-relaxed">
<p className={`text-xs line-clamp-2 leading-relaxed ${
isNotRelevant ? 'text-slate-400' : 'text-muted-foreground/80'
}`}>
{task.description}
</p>
)}
Expand Down
Loading
Loading