From 4f5a12e96cc7c447a228de12752294004e5b8e27 Mon Sep 17 00:00:00 2001 From: psiddharthdesign <107192927+psiddharthdesign@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:53:02 +0530 Subject: [PATCH] fix : projects performance fix test --- .../projects/OrganizationProjectsTable.tsx | 29 +-- .../projects/ProjectsWithPagination.tsx | 17 +- .../TeamProjectsTableWithPagination.tsx | 4 +- src/data/user/projects.tsx | 220 ++++++++++++++---- 4 files changed, 199 insertions(+), 71 deletions(-) diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/OrganizationProjectsTable.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/OrganizationProjectsTable.tsx index 0e19b9f3..e43948f3 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/OrganizationProjectsTable.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/OrganizationProjectsTable.tsx @@ -9,7 +9,6 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; -import { getRepoDetails } from '@/data/user/repos'; import type { Tables } from '@/lib/database.types'; import { flexRender, @@ -24,32 +23,18 @@ import { import { GitBranch } from 'lucide-react'; import moment from 'moment'; import Link from 'next/link'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; +import { ProjectListType } from './ProjectsWithPagination'; type ProjectWithRepo = Tables<'projects'> & { repoFullName: string | null }; type Props = { - projects: Tables<'projects'>[]; + projects: ProjectListType[]; }; export function OrganizationProjectsTable({ projects }: Props) { - const [projectsWithRepos, setProjectsWithRepos] = useState([]); - useEffect(() => { - const fetchRepoDetails = async () => { - const updatedProjects = await Promise.all( - projects.map(async (project) => { - const repoDetails = await getRepoDetails(project.repo_id); - return { ...project, repoFullName: repoDetails?.repo_full_name ?? null }; - }) - ); - setProjectsWithRepos(updatedProjects); - }; - - fetchRepoDetails(); - }, [projects]); - - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { accessorKey: 'name', header: 'Name', @@ -79,8 +64,8 @@ export function OrganizationProjectsTable({ projects }: Props) {
- {row.original.repoFullName ? `${row.original.repoFullName.toLowerCase().replace(/\s+/g, '-').replace(/[A-Z]/g, (letter) => letter.toLowerCase())}` : ''} - {row.original.repoFullName && ((row.getValue('terraform_working_dir') as string) || '') ? '/' : ''} + {row.original.repo_full_name ? `${row.original.repo_full_name.toLowerCase().replace(/\s+/g, '-').replace(/[A-Z]/g, (letter) => letter.toLowerCase())}` : ''} + {row.original.repo_full_name && ((row.getValue('terraform_working_dir') as string) || '') ? '/' : ''} {((row.getValue('terraform_working_dir') as string) || '').replace(/\s+/g, '-').replace(/[A-Z]/g, (letter) => letter.toLowerCase())}
@@ -90,7 +75,7 @@ export function OrganizationProjectsTable({ projects }: Props) { const [sorting, setSorting] = useState([]); const table = useReactTable({ - data: projectsWithRepos, + data: projects, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), 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 891e63d8..ea82b2c4 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 @@ -1,10 +1,18 @@ import { Pagination } from "@/components/Pagination"; import { getLoggedInUserOrganizationRole } from "@/data/user/organizations"; -import { getAllProjectsInOrganization, getProjects, getProjectsCountForUser, getProjectsForUser, getProjectsTotalCount } from "@/data/user/projects"; +import { getAllProjectsListInOrganization, getProjectsCountForUser, getProjectsList, getProjectsListForUser, getProjectsTotalCount } from "@/data/user/projects"; import { serverGetLoggedInUser } from "@/utils/server/serverGetLoggedInUser"; import { projectsfilterSchema } from "@/utils/zod-schemas/params"; import { OrganizationProjectsTable } from "./OrganizationProjectsTable"; +export type ProjectListType = { + name: string; + latest_action_on: string | null; + created_at: string; + repo_full_name: string | null; + slug: string; +} + export async function UserProjectsWithPagination({ organizationId, searchParams, @@ -15,7 +23,7 @@ export async function UserProjectsWithPagination({ getLoggedInUserOrganizationRole(organizationId) ]); const [projects, totalPages] = await Promise.all([ - getProjectsForUser({ ...filters, organizationId, userRole, userId }), + getProjectsListForUser({ ...filters, organizationId, userRole, userId }), getProjectsCountForUser({ ...filters, organizationId, userId }), ]); @@ -33,10 +41,9 @@ export async function AllProjectsTableWithPagination({ }: { organizationId: string; searchParams: unknown }) { const filters = projectsfilterSchema.parse(searchParams); const [projects, totalPages] = await Promise.all([ - getAllProjectsInOrganization({ ...filters, organizationId }), + getAllProjectsListInOrganization({ ...filters, organizationId }), getProjectsTotalCount({ ...filters, organizationId }), ]); - return ( <> @@ -53,7 +60,7 @@ export async function ProjectsTableWithPagination({ }: { organizationId: string; teamId: number | null; searchParams: unknown }) { const filters = projectsfilterSchema.parse(searchParams); const [projects, totalPages] = await Promise.all([ - getProjects({ ...filters, organizationId, teamId }), + getProjectsList({ ...filters, organizationId, teamId }), getProjectsTotalCount({ ...filters, organizationId }), ]); diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/TeamProjectsTableWithPagination.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/TeamProjectsTableWithPagination.tsx index f13c7b4e..d417eac5 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/TeamProjectsTableWithPagination.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/TeamProjectsTableWithPagination.tsx @@ -1,5 +1,5 @@ import { Pagination } from "@/components/Pagination"; -import { getProjects, getProjectsTotalCount } from "@/data/user/projects"; +import { getProjectsList, getProjectsTotalCount } from "@/data/user/projects"; import { projectsfilterSchema } from "@/utils/zod-schemas/params"; import { OrganizationProjectsTable } from "../../../(specific-organization-pages)/projects/OrganizationProjectsTable"; @@ -10,7 +10,7 @@ export async function TeamProjectsWithPagination({ }: { organizationId: string; teamId: number | null; searchParams: unknown }) { const filters = projectsfilterSchema.parse(searchParams); const [projects, totalPages] = await Promise.all([ - getProjects({ ...filters, organizationId, teamId }), + getProjectsList({ ...filters, organizationId, teamId }), getProjectsTotalCount({ ...filters, organizationId }), ]); diff --git a/src/data/user/projects.tsx b/src/data/user/projects.tsx index 8f68a780..bd8abc36 100644 --- a/src/data/user/projects.tsx +++ b/src/data/user/projects.tsx @@ -1,4 +1,5 @@ "use server"; +import { ProjectListType } from "@/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/projects/ProjectsWithPagination"; import { CommentList } from "@/components/Projects/CommentList"; import type { Tables } from "@/lib/database.types"; import { supabaseAdminClient } from "@/supabase-clients/admin/supabaseAdminClient"; @@ -9,6 +10,7 @@ import { normalizeComment } from "@/utils/comments"; import { serverGetLoggedInUser } from "@/utils/server/serverGetLoggedInUser"; import { revalidatePath } from "next/cache"; import { Suspense } from "react"; +import { getRepoDetails } from "./repos"; export async function getSlimProjectById(projectId: string) { const supabaseClient = createSupabaseUserServerComponentClient(); @@ -242,48 +244,6 @@ export const markProjectAsCompletedAction = async (projectId: string): Promise[]> { -// const supabase = createSupabaseUserServerComponentClient(); - -// // First, get the user's role in the organization -// const { data: userRole } = await supabase -// .from('organization_members') -// .select('member_role') -// .eq('organization_id', organizationId) -// .eq('member_id', userId) -// .single(); - -// if (!userRole) { -// throw new Error('User not found in organization'); -// } - -// let query = supabase -// .from('projects') -// .select('*') -// .eq('organization_id', organizationId); - -// if (userRole.member_role === 'admin') { -// // Admins can see all projects -// const { data, error } = await query; -// if (error) throw error; -// return data; -// } else { -// // Regular members can see organization-level projects and projects from their teams -// const { data: userTeams } = await supabase -// .from('team_members') -// .select('team_id') -// .eq('user_id', userId); - -// const teamIds = userTeams?.map(team => team.team_id) || []; - -// const { data, error } = await query -// .or(`team_id.is.null,team_id.in.(${teamIds.join(',')})`); - -// if (error) throw error; -// return data; -// } -// } - export async function getProjectsForUser({ userId, userRole, @@ -329,6 +289,71 @@ export async function getProjectsForUser({ return data || []; } +export async function getProjectsListForUser({ + userId, + userRole, + organizationId, + query = '', + teamIds = [], +}: { + userId: string; + userRole: Enum<'organization_member_role'>; + organizationId: string; + query?: string; + teamIds?: number[]; +}): Promise { + const supabase = createSupabaseUserServerComponentClient(); + + let supabaseQuery = supabase + .from('projects') + .select('name, slug, latest_action_on, created_at, repo_id') + .eq('organization_id', organizationId) + .ilike('name', `%${query}%`); + + 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 getProjectsCountForUser({ userId, organizationId, @@ -423,6 +448,9 @@ export const getAllProjectsInOrganization = async ({ return data || []; }; + + + export const getOrganizationLevelProjects = async ({ organizationId, query = "", @@ -459,6 +487,7 @@ export const getOrganizationLevelProjects = async ({ return data || []; }; + export const getProjects = async ({ organizationId, teamId, @@ -505,6 +534,113 @@ export const getProjects = async ({ return data || []; }; +export const getAllProjectsListInOrganization = async ({ + organizationId, + query = "", + page = 1, + limit = 20, +}: { + query?: string; + page?: number; + organizationId: string; + limit?: number; +}) => { + const zeroIndexedPage = page - 1; + const supabase = createSupabaseUserServerComponentClient(); + let supabaseQuery = supabase + .from("projects") + .select("name, slug, latest_action_on, created_at, repo_id") + .eq("organization_id", organizationId) + .range(zeroIndexedPage * limit, (zeroIndexedPage + 1) * limit - 1); + + if (query) { + supabaseQuery = supabaseQuery.ilike("name", `%${query}%`); + } + + 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 const getProjectsList = async ({ + organizationId, + teamId, + query = "", + page = 1, + limit = 5, +}: { + query?: string; + page?: number; + organizationId: string; + teamId: number | null; + limit?: number; +}) => { + const zeroIndexedPage = page - 1; + const supabase = createSupabaseUserServerComponentClient(); + let supabaseQuery = supabase + .from("projects") + .select("name, slug, latest_action_on, created_at, repo_id") + .eq("organization_id", organizationId) + .range(zeroIndexedPage * limit, (zeroIndexedPage + 1) * limit - 1); + + // Add team filter + if (teamId !== null) { + supabaseQuery = supabaseQuery.eq('team_id', teamId); + } else { + supabaseQuery = supabaseQuery.is('team_id', null); + } + + if (query) { + supabaseQuery = supabaseQuery.ilike("name", `%${query}%`); + } + + 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: ProjectListType[] = 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 const getProjectsTotalCount = async ({ organizationId, query = "",