From 3de8c96c0bd95d6fda211110db88554bf17d6a87 Mon Sep 17 00:00:00 2001 From: psiddharthdesign <107192927+psiddharthdesign@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:57:31 +0530 Subject: [PATCH] feat / add dummy data and banner for drifted project --- .../@sidebar/OrganizationSidebar.tsx | 7 +- .../drifts/DriftedProjectsWithPagination.tsx | 90 ++++++++++ .../drifts/drift-alerts.ts | 30 ++++ .../drifts/loading.tsx | 91 ++++++++++ .../drifts/page.tsx | 46 +++++ .../projects/ProjectsWithPagination.tsx | 1 + .../@sidebar/TeamSidebar.tsx | 7 +- .../(specific-project-pages)/dummyData.ts | 162 ------------------ .../(specific-project-pages)/layout.tsx | 19 ++ .../[projectSlug]/@sidebar/ProjectSidebar.tsx | 7 +- src/components/Pagination/Pagination.tsx | 6 +- src/data/user/projects.tsx | 104 ++++++++++- src/lib/utils.ts | 3 + 13 files changed, 402 insertions(+), 171 deletions(-) create mode 100644 src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/DriftedProjectsWithPagination.tsx create mode 100644 src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/drift-alerts.ts create mode 100644 src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/loading.tsx create mode 100644 src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/page.tsx delete mode 100644 src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/dummyData.ts diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx index ddf96576..393c2d0d 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx @@ -12,7 +12,7 @@ import { FreeTrialComponent } from '@/components/SubscriptionCards'; import { Skeleton } from '@/components/ui/skeleton'; import { getIsStripeTestMode } from '@/utils/server/stripe-utils'; import { differenceInDays } from 'date-fns'; -import { Activity, FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; +import { Activity, FileText, FlagIcon, Home, Layers, MessageCircle, Users } from 'lucide-react'; const isStripeTestMode = getIsStripeTestMode() @@ -86,6 +86,11 @@ async function OrganizationSidebarInternal({ href={`/org/${organizationId}/projects`} icon={} /> + } + /> alert.project_id))); + + const [{ id: userId }, userRole] = await Promise.all([ + serverGetLoggedInUser(), + getLoggedInUserOrganizationRole(organizationId), + ]); + + // const [projects, totalPages] = await Promise.all([ + // getProjectsListForUser({ ...filters, organizationId, userRole, userId }), + // getProjectsCountForUser({ ...filters, organizationId, userId }), + // ]); + + const [projects, totalPages] = await Promise.all([ + getSlimProjectsForUser({ projectIds: driftedProjectIds, userRole, userId }), + getProjectsCountForUser({ ...filters, organizationId, userId }), + ]); + + + + return ( + <> + + + + ); +} + +export async function AllProjectsTableWithPagination({ + organizationId, + searchParams, +}: { organizationId: string; searchParams: unknown }) { + const filters = projectsfilterSchema.parse(searchParams); + const [projects, totalPages] = await Promise.all([ + getAllProjectsListInOrganization({ ...filters, organizationId }), + getProjectsTotalCount({ ...filters, organizationId }), + ]); + return ( + <> + + + + ); +} + + +export async function ProjectsTableWithPagination({ + organizationId, + teamId, + searchParams, +}: { organizationId: string; teamId: number | null; searchParams: unknown }) { + const filters = projectsfilterSchema.parse(searchParams); + const [projects, totalPages] = await Promise.all([ + getProjectsList({ ...filters, organizationId, teamId }), + getProjectsTotalCount({ ...filters, organizationId }), + ]); + + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/drift-alerts.ts b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/drift-alerts.ts new file mode 100644 index 00000000..1402d7a1 --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/drift-alerts.ts @@ -0,0 +1,30 @@ +// types/driftTypes.ts +// clearly dummy data is being generated here as no drift_alerts table is there yet + +import { randomUUID } from 'crypto'; + +export type DriftAlert = { + id: string; + project_id: string; + job_id: string; + timestamp: string; + is_resolved: boolean; +}; + +export const generateDummyDriftAlerts = (projectIds: string[]) => { + const alerts: DriftAlert[] = []; + const numAlerts = 10; // You can adjust this number as needed + + for (let i = 0; i < numAlerts; i++) { + const alert: DriftAlert = { + id: randomUUID(), + project_id: projectIds[Math.floor(Math.random() * projectIds.length)], + job_id: randomUUID(), + timestamp: new Date().toISOString(), + is_resolved: Math.random() < 0.5, // 50% chance of being resolved + }; + alerts.push(alert); + } + + return alerts; +}; diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/loading.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/loading.tsx new file mode 100644 index 00000000..f0ea22c3 --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/loading.tsx @@ -0,0 +1,91 @@ +import { Card } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; + +export default function ProjectsTableLoadingFallback() { + return
+
+ + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+} diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/page.tsx new file mode 100644 index 00000000..3a5299b6 --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/drifts/page.tsx @@ -0,0 +1,46 @@ +import { PageHeading } from "@/components/PageHeading"; +import { T } from "@/components/ui/Typography"; +import { + projectsfilterSchema, + teamParamSchema +} from "@/utils/zod-schemas/params"; +import type { Metadata } from "next"; +import { Suspense } from "react"; +import type { DashboardProps } from "../page"; +import { UserDriftedProjectsWithPagination } from "./DriftedProjectsWithPagination"; + +export const metadata: Metadata = { + title: "Projects", + description: "You can create projects within teams, or within your organization.", +}; + +export default async function DriftsPage({ + params, + searchParams, +}: DashboardProps) { + const { organizationId, teamId } = teamParamSchema.parse(params); + const filters = projectsfilterSchema.parse(searchParams); + + return ( +
+ + { + + Loading drifts... + + } + > + + + } +
+ ); +} diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/ProjectsWithPagination.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/ProjectsWithPagination.tsx index ea82b2c4..768e2e7d 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/ProjectsWithPagination.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/ProjectsWithPagination.tsx @@ -6,6 +6,7 @@ import { projectsfilterSchema } from "@/utils/zod-schemas/params"; import { OrganizationProjectsTable } from "./OrganizationProjectsTable"; export type ProjectListType = { + id: string; name: string; latest_action_on: string | null; created_at: string; diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx index 66caa77a..8ee56ba6 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx @@ -4,7 +4,7 @@ import { SidebarLink } from '@/components/SidebarLink'; import { fetchSlimOrganizations } from '@/data/user/organizations'; import { getOrganizationOfTeam } from '@/data/user/teams'; import { cn } from '@/utils/cn'; -import { Activity, FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; +import { Activity, FileText, FlagIcon, Home, Layers, MessageCircle, Users } from 'lucide-react'; import { Suspense } from 'react'; async function TeamSidebarInternal({ organizationId }: { organizationId: string }) { @@ -33,6 +33,11 @@ async function TeamSidebarInternal({ organizationId }: { organizationId: string href={`/org/${organizationId}/projects`} icon={} /> + } + /> ); } + export default async function ProjectPagesLayout({ params, children }: { params: unknown, children: React.ReactNode }) { const { projectSlug } = projectSlugParamSchema.parse(params); const project = await getSlimProjectBySlug(projectSlug); + + + // fetch the drift alerts const tabs = [ { label: 'Runs', @@ -34,6 +41,18 @@ export default async function ProjectPagesLayout({ params, children }: { params: ]; return <>
+ {isLocalEnvironment && ( + + + + Drift Detected + + + Changes have been detected in your infrastructure that differ from your Terraform configuration. Please review and reconcile these differences. + + + )} + diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx index 50609dcf..f792a511 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx @@ -5,7 +5,7 @@ import { fetchSlimOrganizations } from '@/data/user/organizations'; import { getSlimProjectById, getSlimProjectBySlug } from '@/data/user/projects'; import { cn } from '@/utils/cn'; import { projectSlugParamSchema } from '@/utils/zod-schemas/params'; -import { Activity, ArrowLeft, FileText, Layers, MessageCircle, Users } from 'lucide-react'; +import { Activity, ArrowLeft, FileText, FlagIcon, Layers, MessageCircle, Users } from 'lucide-react'; import { Suspense } from 'react'; async function ProjectSidebarInternal({ projectId, projectSlug }: { projectId: string; projectSlug: string }) { @@ -37,6 +37,11 @@ async function ProjectSidebarInternal({ projectId, projectSlug }: { projectId: s href={`/org/${organizationId}/projects`} icon={} /> + } + /> ; + projectIds: string[]; + teamIds?: number[]; +}): Promise { + const supabase = createSupabaseUserServerComponentClient(); + + let supabaseQuery = supabase + .from('projects') + .select('id,name, slug, latest_action_on, created_at, repo_id') + .in('id', projectIds); + + if (userRole !== 'admin' || userId !== 'owner') { + // For non-admin users, get their team memberships + const { data: userTeams } = await supabase + .from('team_members') + .select('team_id') + .eq('user_id', userId); + + const userTeamIds = userTeams?.map(team => team.team_id) || []; + + // Filter by user's teams and organization-level projects + supabaseQuery = supabaseQuery.or(`team_id.is.null,team_id.in.(${userTeamIds.join(',')})`); + } + + // Apply team filter if provided + if (teamIds.length > 0) { + supabaseQuery = supabaseQuery.in('team_id', teamIds); + } + + const { data, error } = await supabaseQuery.order('latest_action_on', { + ascending: false, + }); + + if (error) { + console.error("Error fetching projects:", error); + return []; + } + + if (!data) return []; + + // Fetch repo details for each project + const projectsWithRepoDetails = await Promise.all( + data.map(async (project) => { + const repoDetails = await getRepoDetails(project.repo_id); + const { repo_id, ...projectWithoutRepoId } = project; + return { + ...projectWithoutRepoId, + repo_full_name: repoDetails?.repo_full_name || null, + }; + }) + ); + + return projectsWithRepoDetails; +} export async function getProjectsIdsListForUser({ userId, userRole, @@ -518,7 +580,43 @@ export const getAllProjectsInOrganization = async ({ return data || []; }; +export const getAllProjectIdsInOrganization = async (organizationId: string) => { + const supabase = createSupabaseUserServerComponentClient(); + const supabaseQuery = supabase + .from("projects") + .select("id") + .eq("organization_id", organizationId) + .order("created_at", { ascending: false }); + + const { data, error } = await supabaseQuery; + + if (error) { + console.error("Error fetching project IDs:", error); + return []; + } + + return data?.map(project => project.id) || []; +}; + +export const getProjectIdsInOrganization = async (organizationId: string, count: number) => { + const supabase = createSupabaseUserServerComponentClient(); + const supabaseQuery = supabase + .from("projects") + .select("id") + .eq("organization_id", organizationId); + + const { data, error } = await supabaseQuery; + + if (error) { + console.error("Error fetching project IDs:", error); + return []; + } + const projectIds = data?.map(project => project.id) || []; + + // Shuffle the array and slice to get random project IDs + return projectIds.sort(() => Math.random() - 0.5).slice(0, count); +}; export const getOrganizationLevelProjects = async ({ @@ -619,7 +717,7 @@ export const getAllProjectsListInOrganization = async ({ const supabase = createSupabaseUserServerComponentClient(); let supabaseQuery = supabase .from("projects") - .select("name, slug, latest_action_on, created_at, repo_id") + .select("id,name, slug, latest_action_on, created_at, repo_id") .eq("organization_id", organizationId) .range(zeroIndexedPage * limit, (zeroIndexedPage + 1) * limit - 1); @@ -670,7 +768,7 @@ export const getProjectsList = async ({ const supabase = createSupabaseUserServerComponentClient(); let supabaseQuery = supabase .from("projects") - .select("name, slug, latest_action_on, created_at, repo_id") + .select("id,name, slug, latest_action_on, created_at, repo_id") .eq("organization_id", organizationId) .range(zeroIndexedPage * limit, (zeroIndexedPage + 1) * limit - 1); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 5e8a35ec..d0de0cf2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -34,3 +34,6 @@ export const ToSnakeCase = (str: string) => { .map((word) => word.toLowerCase()) .join('_'); }; + +export const isLocalEnvironment = + process.env.NEXT_PUBLIC_SITE_URL?.includes('localhost');