From 6f1860609b6b35b047f268afe46e31cdac822e04 Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Fri, 16 May 2025 14:44:32 +0100 Subject: [PATCH 1/3] refactor(create-organization-action): remove webhook call to Zapier for organization creation - Eliminated the code that sends organization details to the Zapier HubSpot webhook upon organization creation. - This change simplifies the action and removes unnecessary dependencies on external services. --- .../organization/create-organization-action.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/apps/app/src/actions/organization/create-organization-action.ts b/apps/app/src/actions/organization/create-organization-action.ts index f229e33c8d..3479222423 100644 --- a/apps/app/src/actions/organization/create-organization-action.ts +++ b/apps/app/src/actions/organization/create-organization-action.ts @@ -59,19 +59,6 @@ export const createOrganizationAction = authActionClient headers: await headers(), }); - if (env.ZAPIER_HUBSPOT_WEBHOOK_URL) { - await ky.post(env.ZAPIER_HUBSPOT_WEBHOOK_URL, { - json: { - email: session?.user.email, - website: website, - organization: name, - frameworks: frameworks, - first_name: session?.user.name?.split(" ")[0] || "", - last_name: session?.user.name?.split(" ")[1] || "", - }, - }); - } - timings.getAuthSession = (performance.now() - start) / 1000; if (!session?.session.activeOrganizationId) { From b53b704059248824df2d7a56b9198bf45358cd71 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Fri, 16 May 2025 11:14:17 -0400 Subject: [PATCH 2/3] refactor(task): remove entityId and entityType from Task model and related components - Eliminated entityId and entityType from the Task model to simplify task management and improve data handling. - Updated various components and utility functions to reflect this change, ensuring consistency across the application. - Removed the getRelatedTasks function as it became redundant with the new many-to-many relationship structure. --- .../app/src/actions/organization/lib/utils.ts | 1 - .../data/getOrganizationControlProgress.ts | 12 ++----- .../[controlId]/data/getRelatedTasks.ts | 31 ------------------- .../[orgId]/controls/[controlId]/page.tsx | 1 - .../frameworks/[frameworkInstanceId]/page.tsx | 8 +++-- .../requirements/[requirementKey]/page.tsx | 7 +++-- .../(dashboard)/[orgId]/frameworks/page.tsx | 7 +++-- .../[orgId]/tasks/components/StatusGroup.tsx | 2 +- .../[orgId]/tasks/components/TaskCard.tsx | 28 +++++------------ apps/app/src/app/api/v1/risks/[id]/route.ts | 7 +++-- .../migration.sql | 16 ++++++++++ .../migration.sql | 2 ++ packages/db/prisma/schema/task.prisma | 12 ------- 13 files changed, 50 insertions(+), 84 deletions(-) delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getRelatedTasks.ts create mode 100644 packages/db/prisma/migrations/20250516145149_drop_entity_and_entity_type/migration.sql create mode 100644 packages/db/prisma/migrations/20250516150558_drop_task_type/migration.sql diff --git a/apps/app/src/actions/organization/lib/utils.ts b/apps/app/src/actions/organization/lib/utils.ts index e36534c321..360df6d019 100644 --- a/apps/app/src/actions/organization/lib/utils.ts +++ b/apps/app/src/actions/organization/lib/utils.ts @@ -6,7 +6,6 @@ import { type PolicyStatus, RequirementId, TaskStatus, - TaskEntityType, TaskFrequency, Departments, ArtifactType, diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getOrganizationControlProgress.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getOrganizationControlProgress.ts index c7b11eea1f..de75dd52a8 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getOrganizationControlProgress.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getOrganizationControlProgress.ts @@ -2,7 +2,6 @@ import { auth } from "@/utils/auth"; import { db } from "@comp/db"; -import { TaskEntityType } from "@comp/db/types"; import { ArtifactType } from "@prisma/client"; import { headers } from "next/headers"; @@ -110,15 +109,8 @@ export const getOrganizationControlProgress = async (controlId: string) => { progress.byType["control"].total++; - // Check completion based on task type - let isCompleted = false; - switch (task.entityType) { - case TaskEntityType.control: - isCompleted = task.status === "done"; - break; - default: - isCompleted = false; - } + // Check completion based on task status (all tasks here are implicitly control-related) + const isCompleted = task.status === "done"; if (isCompleted) { progress.completed++; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getRelatedTasks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getRelatedTasks.ts deleted file mode 100644 index 6b4bbedccc..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/data/getRelatedTasks.ts +++ /dev/null @@ -1,31 +0,0 @@ -"use server"; - -import { auth } from "@/utils/auth"; -import { db } from "@comp/db"; -import { headers } from "next/headers"; - -export const getRelatedTasks = async ({ - controlId, -}: { - controlId: string; -}) => { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - const orgId = session?.session.activeOrganizationId; - - if (!orgId) { - return []; - } - - const tasks = await db.task.findMany({ - where: { - organizationId: orgId, - entityId: controlId, - entityType: "control", - }, - }); - - return tasks; -}; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/page.tsx index c2ec6c2e8e..e174473bcb 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/page.tsx @@ -6,7 +6,6 @@ import { SingleControl } from "./components/SingleControl"; import { getControl } from "./data/getControl"; import { getOrganizationControlProgress } from "./data/getOrganizationControlProgress"; import type { ControlProgressResponse } from "./data/getOrganizationControlProgress"; -import { getRelatedTasks } from "./data/getRelatedTasks"; import { getRelatedArtifacts } from "./data/getRelatedArtifacts"; interface ControlPageProps { diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx index ee95b4f4d1..e8c673c61f 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx @@ -7,7 +7,7 @@ import { getFrameworkDetails } from "../lib/getFrameworkDetails"; import { FrameworkOverview } from "./components/FrameworkOverview"; import { FrameworkRequirements } from "./components/FrameworkRequirements"; import { db } from "@comp/db"; -import { TaskEntityType } from "@comp/db/types"; + interface PageProps { params: Promise<{ frameworkInstanceId: string; @@ -48,7 +48,11 @@ export default async function FrameworkPage({ params }: PageProps) { const tasks = await db.task.findMany({ where: { organizationId, - entityType: TaskEntityType.control, + controls: { + some: { + id: frameworkInstanceWithControls.id, + }, + }, }, }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx index 7795735075..2b5117963a 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx @@ -7,7 +7,6 @@ import { getFrameworkDetails } from "../../../lib/getFrameworkDetails"; import { getFrameworkRequirements } from "../../../lib/getFrameworkRequirements"; import { RequirementControls } from "./components/RequirementControls"; import { db } from "@comp/db"; -import { TaskEntityType } from "@comp/db/types"; interface PageProps { params: Promise<{ @@ -70,7 +69,11 @@ export default async function RequirementPage({ params }: PageProps) { (await db.task.findMany({ where: { organizationId, - entityType: TaskEntityType.control, + controls: { + some: { + id: frameworkInstanceWithControls.id, + }, + }, }, })) || []; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx index 0ac11d0a83..8c846f4541 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx @@ -6,7 +6,6 @@ import { redirect } from "next/navigation"; import { FrameworksOverview } from "./components/FrameworksOverview"; import { getAllFrameworkInstancesWithControls } from "./data/getAllFrameworkInstancesWithControls"; import { db } from "@comp/db"; -import { TaskEntityType } from "@comp/db/types"; export async function generateMetadata() { const t = await getI18n(); @@ -58,7 +57,11 @@ const getControlTasks = async () => { const tasks = await db.task.findMany({ where: { organizationId, - entityType: TaskEntityType.control, + controls: { + some: { + organizationId + }, + }, }, }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/StatusGroup.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/StatusGroup.tsx index 3009f47192..374fdddcc5 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/StatusGroup.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/StatusGroup.tsx @@ -1,6 +1,6 @@ "use client"; -import { Member, Task, User, TaskEntityType } from "@comp/db/types"; +import { Member, Task, User } from "@comp/db/types"; import { useDrop } from "react-dnd"; import { useRef } from "react"; import { TaskCard, DragItem, ItemTypes, StatusId } from "./TaskCard"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskCard.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskCard.tsx index 4236777380..8031983f4d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskCard.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskCard.tsx @@ -1,12 +1,11 @@ "use client"; -import { Member, Task, User, TaskEntityType, TaskStatus } from "@comp/db/types"; +import { Member, Task, TaskStatus, User } from "@comp/db/types"; import { Badge } from "@comp/ui/badge"; -import { Check } from "lucide-react"; import Image from "next/image"; -import { useRouter, usePathname } from "next/navigation"; -import { useDrag, useDrop, XYCoord } from "react-dnd"; -import { useRef, useMemo, useState, useEffect, useCallback } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useDrag, useDrop } from "react-dnd"; import { TaskStatusIndicator } from "./TaskStatusIndicator"; // DnD Item Type identifier for tasks. @@ -163,13 +162,13 @@ export function TaskCard({ }, [task.assigneeId, members]); // Helper to get Tailwind class for the entity type indicator dot. - const getEntityTypeDotClass = (entityType: TaskEntityType): string => { + const getEntityTypeDotClass = (entityType: "control" | "risk" | "vendor"): string => { switch (entityType) { - case TaskEntityType.control: + case "control": return "bg-blue-500"; - case TaskEntityType.risk: + case "risk": return "bg-red-500"; - case TaskEntityType.vendor: + case "vendor": return "bg-green-500"; default: return "bg-gray-500"; @@ -224,17 +223,6 @@ export function TaskCard({ {task.title}
- {task.entityType && ( - - - {task.entityType} - - )} Apr 15 diff --git a/apps/app/src/app/api/v1/risks/[id]/route.ts b/apps/app/src/app/api/v1/risks/[id]/route.ts index ccfe8475f5..d590908011 100644 --- a/apps/app/src/app/api/v1/risks/[id]/route.ts +++ b/apps/app/src/app/api/v1/risks/[id]/route.ts @@ -96,8 +96,11 @@ export async function GET( // Fetch tasks for this risk const tasks = await db.task.findMany({ where: { - entityId: riskId, - entityType: "risk", + risks: { + some: { + id: riskId, + }, + }, organizationId: organizationId!, }, select: { diff --git a/packages/db/prisma/migrations/20250516145149_drop_entity_and_entity_type/migration.sql b/packages/db/prisma/migrations/20250516145149_drop_entity_and_entity_type/migration.sql new file mode 100644 index 0000000000..cbf721f15c --- /dev/null +++ b/packages/db/prisma/migrations/20250516145149_drop_entity_and_entity_type/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - You are about to drop the column `entityId` on the `Task` table. All the data in the column will be lost. + - You are about to drop the column `entityType` on the `Task` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "Task_entityId_idx"; + +-- DropIndex +DROP INDEX "Task_entityId_organizationId_idx"; + +-- AlterTable +ALTER TABLE "Task" DROP COLUMN "entityId", +DROP COLUMN "entityType"; diff --git a/packages/db/prisma/migrations/20250516150558_drop_task_type/migration.sql b/packages/db/prisma/migrations/20250516150558_drop_task_type/migration.sql new file mode 100644 index 0000000000..ef8a4f9580 --- /dev/null +++ b/packages/db/prisma/migrations/20250516150558_drop_task_type/migration.sql @@ -0,0 +1,2 @@ +-- DropEnum +DROP TYPE "TaskEntityType"; diff --git a/packages/db/prisma/schema/task.prisma b/packages/db/prisma/schema/task.prisma index 0b5fb3169a..8d9a6faa18 100644 --- a/packages/db/prisma/schema/task.prisma +++ b/packages/db/prisma/schema/task.prisma @@ -5,9 +5,6 @@ model Task { description String status TaskStatus @default(todo) - entityId String? - entityType TaskEntityType? - controls Control[] vendors Vendor[] risks Risk[] @@ -26,9 +23,6 @@ model Task { assignee Member? @relation(fields: [assigneeId], references: [id]) organizationId String organization Organization @relation(fields: [organizationId], references: [id]) - - @@index([entityId]) - @@index([entityId, organizationId]) } enum TaskStatus { @@ -44,9 +38,3 @@ enum TaskFrequency { quarterly yearly } - -enum TaskEntityType { - control - vendor - risk -} From bfbad293ade92a8e165c390759f7ab0eab4dfae0 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Fri, 16 May 2025 11:25:58 -0400 Subject: [PATCH 3/3] feat(frameworks): include controls in task retrieval across various components - Added an `include` clause to task retrieval functions to fetch associated controls, enhancing the data structure for tasks. - Updated multiple components to accommodate the new task structure, ensuring they handle tasks with associated controls correctly. - Adjusted type definitions to reflect the inclusion of controls in task data, improving type safety and consistency. --- .../components/FrameworkOverview.tsx | 4 +- .../frameworks/[frameworkInstanceId]/page.tsx | 3 + .../components/RequirementControls.tsx | 4 +- .../table/RequirementControlsTable.tsx | 2 +- .../table/RequirementControlsTableColumns.tsx | 2 +- .../requirements/[requirementKey]/page.tsx | 3 + .../frameworks/components/FrameworkCard.tsx | 8 +- .../frameworks/components/FrameworkList.tsx | 4 +- .../components/FrameworksOverview.tsx | 4 +- .../data/getFrameworkWithComplianceScores.ts | 13 +- .../[orgId]/frameworks/lib/utils.ts | 8 +- .../(dashboard)/[orgId]/frameworks/page.tsx | 3 + .../risk/[riskId]/tasks/[taskId]/page.tsx | 2 - .../[orgId]/tasks/actions/updateTaskOrder.ts | 2 +- .../tasks/components/TaskFilterHeader.tsx | 92 +------------- .../(app)/(dashboard)/[orgId]/tasks/page.tsx | 114 +++++++----------- .../vendors/[vendorId]/actions/schema.ts | 3 + .../actions/task/create-task-action.ts | 7 +- .../actions/task/update-task-action.ts | 11 +- 19 files changed, 96 insertions(+), 193 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkOverview.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkOverview.tsx index 473c691fd5..96c79e1a1c 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkOverview.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkOverview.tsx @@ -6,11 +6,11 @@ import { Progress } from "@comp/ui/progress"; import { getFrameworkDetails } from "../../lib/getFrameworkDetails"; import { getControlStatus } from "../../lib/utils"; import { FrameworkInstanceWithControls } from "../../types"; -import { Task } from "@comp/db/types"; +import { Control, Task } from "@comp/db/types"; interface FrameworkOverviewProps { frameworkInstanceWithControls: FrameworkInstanceWithControls; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; } export function FrameworkOverview({ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx index e8c673c61f..ed12101dbe 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/page.tsx @@ -54,6 +54,9 @@ export default async function FrameworkPage({ params }: PageProps) { }, }, }, + include: { + controls: true, + }, }); return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/RequirementControls.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/RequirementControls.tsx index fbd84c7a04..f2b6fd4038 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/RequirementControls.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/RequirementControls.tsx @@ -6,13 +6,13 @@ import { FrameworkId } from "@comp/db/types"; import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card"; import { FrameworkInstanceWithControls } from "../../../../types"; import { RequirementControlsTable } from "./table/RequirementControlsTable"; -import type { Task } from "@comp/db/types"; +import type { Control, Task } from "@comp/db/types"; interface RequirementControlsProps { requirement: Requirement; requirementKey: string; frameworkInstanceWithControls: FrameworkInstanceWithControls; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; } export function RequirementControls({ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx index a0b4c88681..1cd45ebf2a 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx @@ -27,7 +27,7 @@ interface RequirementControlsTableProps { policy: Policy | null; })[]; })[]; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; } export function RequirementControlsTable({ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTableColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTableColumns.tsx index 5e736c87c9..2d8c4d205c 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTableColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTableColumns.tsx @@ -26,7 +26,7 @@ type OrganizationControlType = Control & { export function RequirementControlsTableColumns({ tasks, }: { - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; }): ColumnDef[] { const t = useI18n(); const { orgId } = useParams<{ orgId: string }>(); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx index 2b5117963a..a5bdc0ccd8 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/page.tsx @@ -75,6 +75,9 @@ export default async function RequirementPage({ params }: PageProps) { }, }, }, + include: { + controls: true, + }, })) || []; return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx index 04c034d1c3..de9acd4fed 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx @@ -1,7 +1,7 @@ "use client"; import { useI18n } from "@/locales/client"; -import { Task } from "@comp/db/types"; +import { Task, Control } from "@comp/db/types"; import { Badge } from "@comp/ui/badge"; import { Card, @@ -21,7 +21,7 @@ import { FrameworkInstanceWithControls } from "../types"; interface FrameworkCardProps { frameworkInstance: FrameworkInstanceWithControls; complianceScore: number; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; } export function FrameworkCard({ @@ -75,8 +75,8 @@ export function FrameworkCard({ const notStartedControlsCount = frameworkInstance.controls?.filter((control) => { // If a control has no artifacts and no tasks, it's not started. - const controlTasks = tasks.filter( - (task) => task.entityId === control.id, + const controlTasks = tasks.filter((task) => + task.controls.some((c) => c.id === control.id), ); if ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkList.tsx index 10b2a881ec..07fa0a7181 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkList.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkList.tsx @@ -1,6 +1,6 @@ "use client"; -import { Task } from "@comp/db/types"; +import { Control, Task } from "@comp/db/types"; import { FrameworkCard } from "./FrameworkCard"; import type { FrameworkInstanceWithComplianceScore } from "./types"; @@ -9,7 +9,7 @@ export function FrameworkList({ tasks, }: { frameworksWithControlsAndComplianceScores: FrameworkInstanceWithComplianceScore[]; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; }) { if (!frameworksWithControlsAndComplianceScores.length) return null; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworksOverview.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworksOverview.tsx index fb3742dbec..c79ba75c89 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworksOverview.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworksOverview.tsx @@ -1,11 +1,11 @@ -import { Task } from "@comp/db/types"; +import { Control, Task } from "@comp/db/types"; import { getFrameworkWithComplianceScores } from "../data/getFrameworkWithComplianceScores"; import type { FrameworkInstanceWithControls } from "../types"; import { FrameworkList } from "./FrameworkList"; export interface FrameworksOverviewProps { frameworksWithControls: FrameworkInstanceWithControls[]; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; } export async function FrameworksOverview({ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/data/getFrameworkWithComplianceScores.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/data/getFrameworkWithComplianceScores.ts index 5f810f3e78..645708ce44 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/data/getFrameworkWithComplianceScores.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/data/getFrameworkWithComplianceScores.ts @@ -1,16 +1,13 @@ "use server"; import { + Control, type Artifact, - type Task, type Policy, - TaskEntityType, + type Task, } from "@comp/db/types"; import { FrameworkInstanceWithComplianceScore } from "../components/types"; import { FrameworkInstanceWithControls } from "../types"; -import { db } from "@comp/db"; -import { headers } from "next/headers"; -import { auth } from "@/utils/auth"; /** * Checks if a control is compliant based on its artifacts and tasks @@ -62,7 +59,7 @@ export async function getFrameworkWithComplianceScores({ tasks, }: { frameworksWithControls: FrameworkInstanceWithControls[]; - tasks: Task[]; + tasks: (Task & { controls: Control[] })[]; }): Promise { // Calculate compliance for each framework const frameworksWithComplianceScores = frameworksWithControls.map( @@ -73,8 +70,8 @@ export async function getFrameworkWithComplianceScores({ // Calculate compliance percentage const totalControls = controls.length; const compliantControls = controls.filter((control) => { - const controlTasks = tasks.filter( - (task) => task.entityId === control.id, + const controlTasks = tasks.filter((task) => + task.controls.some((c) => c.id === control.id), ); return isControlCompliant(control.artifacts, controlTasks); }).length; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/utils.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/utils.ts index 65bbce7d7e..7401f33a78 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/utils.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/utils.ts @@ -1,6 +1,6 @@ import { StatusType } from "@/components/status-indicator"; // Import base types explicitly -import type { Artifact, Policy, PolicyStatus } from "@comp/db/types"; +import type { Artifact, Control, Policy, PolicyStatus } from "@comp/db/types"; import { Task } from "@comp/db/types"; // Define the expected artifact structure explicitly, allowing null status @@ -11,10 +11,12 @@ type ArtifactWithRelations = Artifact & { // Function to determine control status based on artifacts export function getControlStatus( artifacts: ArtifactWithRelations[], // Use the explicit type - tasks: Task[], + tasks: (Task & { controls: Control[] })[], controlId: string, ): StatusType { - const controlTasks = tasks.filter((task) => task.entityId === controlId); + const controlTasks = tasks.filter((task) => + task.controls.some((c) => c.id === controlId), + ); // All artifacts are draft or none const allArtifactsDraft = diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx index 8c846f4541..14a19def7d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx @@ -63,6 +63,9 @@ const getControlTasks = async () => { }, }, }, + include: { + controls: true, + }, }); return tasks; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx index 83257d9982..6dc986300b 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx @@ -39,8 +39,6 @@ const getTask = cache(async (riskId: string, taskId: string) => { const task = await db.task.findUnique({ where: { - entityId: riskId, - entityType: "risk", id: taskId, organizationId: session.session.activeOrganizationId, }, diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/actions/updateTaskOrder.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/actions/updateTaskOrder.ts index 071f76ba0d..da40ab896d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/actions/updateTaskOrder.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/actions/updateTaskOrder.ts @@ -2,7 +2,7 @@ import { db } from "@comp/db"; import { z } from "zod"; -import { TaskStatus, TaskEntityType } from "@comp/db/types"; +import { TaskStatus } from "@comp/db/types"; import { revalidatePath } from "next/cache"; import type { ActionResponse } from "@/types/actions"; import { auth } from "@/utils/auth"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskFilterHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskFilterHeader.tsx index 86812e8e4c..0ff79462eb 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskFilterHeader.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/components/TaskFilterHeader.tsx @@ -1,17 +1,8 @@ "use client"; import { Button } from "@comp/ui/button"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@comp/ui/dropdown-menu"; -import { TaskEntityType, TaskStatus } from "@comp/db/types"; -import { Check, Circle, Filter, List, Loader2 } from "lucide-react"; -import { parseAsArrayOf, parseAsStringLiteral, useQueryState } from "nuqs"; +import { Check, Circle, List, Loader2 } from "lucide-react"; +import { useQueryState } from "nuqs"; // Configuration for task statuses and their display order. const statuses = [ @@ -21,23 +12,8 @@ const statuses = [ ] as const; type StatusId = (typeof statuses)[number]["id"]; -// Allowed entity types for tasks. -const possibleEntityTypes = [ - TaskEntityType.control, - TaskEntityType.risk, - TaskEntityType.vendor, -] as const; - -// Parser for handling comma-separated entity types in the URL query string. -const entityTypesParser = parseAsArrayOf( - parseAsStringLiteral(possibleEntityTypes), - ",", -) - .withDefault([] as TaskEntityType[]) - .withOptions({ shallow: false }); - /** - * Renders the header section for filtering tasks by status and entity type. + * Renders the header section for filtering tasks by status. * Uses `nuqs` to manage filter state in the URL search parameters. */ export function TaskFilterHeader() { @@ -46,12 +22,6 @@ export function TaskFilterHeader() { shallow: false, // Ensures full page reload on change to refetch server data. }); - // State for the entity type filter, synced with the 'entityTypes' URL query parameter. - const [entityTypes, setEntityTypes] = useQueryState( - "entityTypes", - entityTypesParser, - ); - // Mapping of status IDs (and 'all') to their corresponding icons. const statusIcons: Record = { all: List, @@ -68,27 +38,12 @@ export function TaskFilterHeader() { return `${baseClasses} ${isActive ? "" : inactiveClasses}`.trim(); }; - // Handler for updating the entity type filter based on checkbox changes. - const handleEntityTypeChange = (type: TaskEntityType, checked: boolean) => { - setEntityTypes((prev) => { - const currentTypes = prev ?? []; - if (checked) { - // Add the type if checked, ensuring uniqueness. - return Array.from(new Set([...currentTypes, type])); - } - // Remove the type if unchecked. - return currentTypes.filter((t) => t !== type); - }); - }; - - // Check if any status or entity type filters are currently active. - const filtersActive = - statusFilter !== null || (entityTypes?.length ?? 0) > 0; + // Check if any status filters are currently active. + const filtersActive = statusFilter !== null; // Handler to clear all active filters. const clearFilters = () => { setStatusFilter(null); - setEntityTypes([]); // Clear entity types using the setter with an empty array. }; return ( @@ -122,43 +77,8 @@ export function TaskFilterHeader() { })}
- {/* Action Filters (Entity Type and Clear) */} + {/* Action Filters (Clear) */}
- - - - - - Filter by Type - - {possibleEntityTypes.map((type) => ( - - handleEntityTypeChange(type, !!checked) - } - className="capitalize" - > - {type} - - ))} - - - {/* Conditionally render the 'Clear filters' button only when filters are active. */} {filtersActive && (
); } -// Wrap getTasks logic in React.cache -const getCachedTasks = cache( - async (statusParam?: string, entityTypesParam?: string) => { - console.log("Fetching tasks with cache...", { - statusParam, - entityTypesParam, - }); - const session = await auth.api.getSession({ - headers: await headers(), - }); - - const orgId = session?.session.activeOrganizationId; +const getTasks = async (statusParam?: string) => { + console.log("Fetching tasks...", { + statusParam, + }); + const session = await auth.api.getSession({ + headers: await headers(), + }); - if (!orgId) { - return []; - } + const orgId = session?.session.activeOrganizationId; - const whereClause: { - organizationId: string; - status?: TaskStatus; - entityType?: { in: TaskEntityType[] }; - } = { organizationId: orgId }; + if (!orgId) { + return []; + } - // Filter by Status (using passed argument) - if (typeof statusParam === "string" && statusParam in TaskStatus) { - whereClause.status = statusParam as TaskStatus; - console.log(`Filtering by status: ${whereClause.status}`); - } + const whereClause: { + organizationId: string; + status?: TaskStatus; + } = { organizationId: orgId }; - // Filter by Entity Type(s) (using passed argument) - if ( - typeof entityTypesParam === "string" && - entityTypesParam.length > 0 - ) { - const types = entityTypesParam.split(","); - const validEntityTypes = types.filter( - (type): type is TaskEntityType => type in TaskEntityType, - ); - if (validEntityTypes.length > 0) { - whereClause.entityType = { in: validEntityTypes }; - console.log( - `Filtering by entity types: ${validEntityTypes.join(", ")}`, - ); - } - } + // Filter by Status (using passed argument) + if (typeof statusParam === "string" && statusParam in TaskStatus) { + whereClause.status = statusParam as TaskStatus; + console.log(`Filtering by status: ${whereClause.status}`); + } - const tasks = await db.task.findMany({ - where: whereClause, - orderBy: [ - { status: "asc" }, - { entityType: "asc" }, - { order: "asc" }, - { createdAt: "asc" }, - ], - }); - return tasks; - }, -); + const tasks = await db.task.findMany({ + where: whereClause, + orderBy: [{ status: "asc" }, { order: "asc" }, { createdAt: "asc" }], + }); + return tasks; +}; -// Wrap getMembersWithMetadata logic in React.cache -const getCachedMembersWithMetadata = cache(async () => { - console.log("Fetching members with cache..."); +const getMembersWithMetadata = async () => { + console.log("Fetching members..."); const session = await auth.api.getSession({ headers: await headers(), }); @@ -132,4 +100,4 @@ const getCachedMembersWithMetadata = cache(async () => { }); return members; -}); +}; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/schema.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/schema.ts index c6725f2536..0e37c12e7d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/schema.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/schema.ts @@ -70,6 +70,9 @@ export const updateVendorTaskSchema = z.object({ id: z.string().min(1, { message: "Task ID is required", }), + vendorId: z.string().min(1, { + message: "Vendor ID is required", + }), title: z.string().min(1, { message: "Title is required", }), diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/create-task-action.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/create-task-action.ts index 1b3514c682..f5d0df1fba 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/create-task-action.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/create-task-action.ts @@ -35,8 +35,11 @@ export const createVendorTaskAction = authActionClient description, assigneeId, organizationId: activeOrganizationId, - entityId: vendorId, - entityType: "vendor", + vendors: { + connect: { + id: vendorId, + }, + }, }, }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/update-task-action.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/update-task-action.ts index ee3c37862d..4848978117 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/update-task-action.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/actions/task/update-task-action.ts @@ -36,10 +36,13 @@ export const updateVendorTaskAction = authActionClient id: id, }, select: { - entityId: true, + vendors: { + select: { + id: true, + }, + }, }, }); - if (!task) { throw new Error("Task not found"); } @@ -58,10 +61,10 @@ export const updateVendorTaskAction = authActionClient }); revalidatePath( - `/${session.activeOrganizationId}/vendors/${task.entityId}`, + `/${session.activeOrganizationId}/vendors/${task.vendors[0].id}`, ); revalidatePath( - `/${session.activeOrganizationId}/vendors/${task.entityId}/tasks/${id}`, + `/${session.activeOrganizationId}/vendors/${task.vendors[0].id}/tasks/${id}`, ); revalidateTag(`vendor_${session.activeOrganizationId}`);