Refactor to fetch organization and deployment details in the parent component (#23)

* Refactor to fetch organization details in the parent component

* Fetch and use deployment details for a project

* Remove deployment field from ProjectDetails type

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
prathamesh0 2024-01-18 17:50:50 +05:30 committed by Ashwin Phatak
parent 62c969d1ff
commit af021d3357
11 changed files with 199 additions and 121 deletions

View File

@ -4,6 +4,7 @@
"title": "nextjs-boilerplate-9t44zbky4dg-bygideon-projects", "title": "nextjs-boilerplate-9t44zbky4dg-bygideon-projects",
"status": "Building", "status": "Building",
"isProduction": true, "isProduction": true,
"isCurrent": false,
"branch": "prod", "branch": "prod",
"commit": { "commit": {
"hash": "9haif19", "hash": "9haif19",
@ -17,6 +18,7 @@
"title": "nextjs-boilerplate-9232dwky4dg-bygideon-projects", "title": "nextjs-boilerplate-9232dwky4dg-bygideon-projects",
"status": "Ready", "status": "Ready",
"isProduction": false, "isProduction": false,
"isCurrent": false,
"branch": "prod", "branch": "prod",
"commit": { "commit": {
"hash": "43de569", "hash": "43de569",
@ -30,6 +32,7 @@
"title": "nextjs-boilerplate-9saa22y4dg-bygideon-projects", "title": "nextjs-boilerplate-9saa22y4dg-bygideon-projects",
"status": "Error", "status": "Error",
"isProduction": false, "isProduction": false,
"isCurrent": false,
"branch": "main", "branch": "main",
"commit": { "commit": {
"hash": "4hdsf19", "hash": "4hdsf19",

View File

@ -11,13 +11,13 @@ import {
import SearchBar from '../SearchBar'; import SearchBar from '../SearchBar';
import { ProjectDetails } from '../../types/project'; import { ProjectDetails } from '../../types/project';
import projectsData from '../../assets/projects.json';
interface ProjectsSearchProps { interface ProjectsSearchProps {
projects: ProjectDetails[];
onChange?: (data: ProjectDetails) => void; onChange?: (data: ProjectDetails) => void;
} }
const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { const ProjectSearchBar = ({ projects, onChange }: ProjectsSearchProps) => {
const [items, setItems] = useState<ProjectDetails[]>([]); const [items, setItems] = useState<ProjectDetails[]>([]);
const [selectedItem, setSelectedItem] = useState<ProjectDetails | null>(null); const [selectedItem, setSelectedItem] = useState<ProjectDetails | null>(null);
@ -32,7 +32,7 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
onInputValueChange({ inputValue }) { onInputValueChange({ inputValue }) {
setItems( setItems(
inputValue inputValue
? projectsData.filter((project) => ? projects.filter((project) =>
project.title.toLowerCase().includes(inputValue.toLowerCase()), project.title.toLowerCase().includes(inputValue.toLowerCase()),
) )
: [], : [],

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo } from 'react';
import { Typography, Button, Chip } from '@material-tailwind/react'; import { Typography, Button, Chip } from '@material-tailwind/react';
@ -11,7 +11,16 @@ interface OverviewProps {
project: ProjectDetails; project: ProjectDetails;
} }
const OverviewTabPanel = ({ project }: OverviewProps) => ( const OverviewTabPanel = ({ project }: OverviewProps) => {
const currentDeploymentTitle = useMemo(() => {
const deployment = project.deployments.find((deployment) => {
return deployment.isCurrent === true;
});
return deployment?.title;
}, []);
return (
<div className="grid grid-cols-5"> <div className="grid grid-cols-5">
<div className="col-span-3 p-2"> <div className="col-span-3 p-2">
<div className="flex items-center gap-2 p-2 "> <div className="flex items-center gap-2 p-2 ">
@ -49,12 +58,14 @@ const OverviewTabPanel = ({ project }: OverviewProps) => (
</div> </div>
<div className="flex justify-between p-2 text-sm"> <div className="flex justify-between p-2 text-sm">
<p>^ Deployment</p> <p>^ Deployment</p>
<p className="text-blue-600">{project.deployment}</p> <p className="text-blue-600">{currentDeploymentTitle}</p>
</div> </div>
<div className="flex justify-between p-2 text-sm"> <div className="flex justify-between p-2 text-sm">
<p>^ Created</p> <p>^ Created</p>
<p> <p>
{relativeTime(project.createdAt)} by ^ {project.createdBy} {/* TODO: Use following time conversion wherever required */}
{relativeTime(new Date(Number(project.createdAt)).toISOString())} by
^ {project.createdBy}
</p> </p>
</div> </div>
</div> </div>
@ -73,5 +84,6 @@ const OverviewTabPanel = ({ project }: OverviewProps) => (
</div> </div>
</div> </div>
); );
};
export default OverviewTabPanel; export default OverviewTabPanel;

View File

@ -1,5 +1,5 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link, useOutletContext } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react'; import { Button, Typography } from '@material-tailwind/react';
@ -7,17 +7,21 @@ import DomainCard from './DomainCard';
import { DomainDetails } from '../../../../types/project'; import { DomainDetails } from '../../../../types/project';
import domainsData from '../../../../assets/domains.json'; import domainsData from '../../../../assets/domains.json';
import repositories from '../../../../assets/repositories.json'; import repositories from '../../../../assets/repositories.json';
import projectData from '../../../../assets/projects.json';
const Domains = () => { const Domains = () => {
const { id } = useParams(); const { id } = useParams();
// @ts-expect-error create context type for projects
const { projects } = useOutletContext();
const currProject = useMemo(() => { const currProject = useMemo(() => {
return projectData.find((data) => data.id === Number(id)); return projects.find((data: any) => Number(data.id) === Number(id));
}, [id]); }, [id]);
const linkedRepo = useMemo(() => { const linkedRepo = useMemo(() => {
return repositories.find((repo) => repo.id === currProject?.repositoryId); return repositories.find(
(repo) => repo.id === Number(currProject?.repositoryId),
);
}, [currProject]); }, [currProject]);
return ( return (

View File

@ -1,17 +1,61 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import HorizontalLine from '../components/HorizontalLine'; import HorizontalLine from '../components/HorizontalLine';
import { IconButton, Typography } from '@material-tailwind/react'; import { IconButton, Typography } from '@material-tailwind/react';
import ProjectSearchBar from '../components/projects/ProjectSearchBar'; import ProjectSearchBar from '../components/projects/ProjectSearchBar';
import { useGQLClient } from '../context/GQLClientContext';
import { ProjectDetails } from '../types/project';
const ProjectSearch = () => { const ProjectSearch = () => {
const client = useGQLClient();
const [projects, setProjects] = useState<ProjectDetails[]>([]);
useEffect(() => {
const fetch = async () => {
const res = await client.getOrganizations();
// Note: select first organization as organization switching not yet implemented
const projects = res.organizations[0].projects;
const orgName = res.organizations[0].name;
const updatedProjectsPromises = projects.map(async (project: any) => {
const { deployments } = await client.getDeployments(String(project.id));
return {
...project,
// TODO: populate empty fields
icon: '',
title: project.name,
organization: orgName,
deployments,
url: '',
domain: null,
createdBy: project.owner.name,
source: '',
repositoryId: project.repository,
// TODO: populate from github API
latestCommit: {
message: '',
createdAt: '',
branch: '',
},
};
});
const updatedProjects = await Promise.all(updatedProjectsPromises);
setProjects(updatedProjects);
};
fetch();
}, [client]);
return ( return (
<div> <div>
<div className="sticky top-0 bg-white z-30"> <div className="sticky top-0 bg-white z-30">
<div className="flex p-5"> <div className="flex p-5">
<div className="grow mr-2"> <div className="grow mr-2">
<ProjectSearchBar onChange={() => {}} /> <ProjectSearchBar onChange={() => {}} projects={projects} />
</div> </div>
<IconButton color="blue" className="rounded-full mr-2"> <IconButton color="blue" className="rounded-full mr-2">
<Typography variant="h5">+</Typography> <Typography variant="h5">+</Typography>
@ -26,7 +70,7 @@ const ProjectSearch = () => {
<HorizontalLine /> <HorizontalLine />
</div> </div>
<div className="z-0"> <div className="z-0">
<Outlet /> <Outlet context={{ projects }} />
</div> </div>
</div> </div>
); );

View File

@ -1,48 +1,14 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { useOutletContext } from 'react-router-dom';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button, Typography, Chip } from '@material-tailwind/react'; import { Button, Typography, Chip } from '@material-tailwind/react';
import ProjectCard from '../components/projects/ProjectCard'; import ProjectCard from '../components/projects/ProjectCard';
import projectsDetail from '../assets/projects.json';
import { useGQLClient } from '../context/GQLClientContext';
const Projects = () => { const Projects = () => {
const client = useGQLClient(); // @ts-expect-error create context type for projects
const [projects, setProjects] = useState([]); const { projects } = useOutletContext();
useEffect(() => {
const fetchOrganization = async () => {
const res = await client.getOrganizations();
// Note: select first organization as organization switching not yet implemented
const projects = res.organizations[0].projects;
const updatedProjects = projects.map((project: any) => {
return {
...project,
// TODO: populate empty fields
icon: '',
title: '',
organization: '',
url: '',
domain: null,
createdBy: '',
source: '',
// TODO: populate from github API
latestCommit: {
message: '',
createdAt: '',
branch: '',
},
};
});
setProjects(updatedProjects);
};
fetchOrganization();
}, [client]);
return ( return (
<div> <div>
@ -52,7 +18,7 @@ const Projects = () => {
<Typography variant="h4">Projects</Typography> <Typography variant="h4">Projects</Typography>
<Chip <Chip
className="bg-gray-300 rounded-full static" className="bg-gray-300 rounded-full static"
value={projectsDetail.length} value={projects.length}
size="sm" size="sm"
/> />
</div> </div>
@ -67,7 +33,7 @@ const Projects = () => {
</div> </div>
<div className="grid grid-cols-3 gap-5 p-5"> <div className="grid grid-cols-3 gap-5 p-5">
{projects.length !== 0 && {projects.length !== 0 &&
projects.map((project, key) => { projects.map((project: any, key: number) => {
return <ProjectCard project={project} key={key} />; return <ProjectCard project={project} key={key} />;
})} })}
</div> </div>

View File

@ -1,22 +1,28 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useOutletContext, useParams } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react'; import { Button, Typography } from '@material-tailwind/react';
import HorizontalLine from '../../components/HorizontalLine'; import HorizontalLine from '../../components/HorizontalLine';
import projects from '../../assets/projects.json';
import ProjectTabs from '../../components/projects/project/ProjectTabs'; import ProjectTabs from '../../components/projects/project/ProjectTabs';
const getProject = (id: number) => { const getProject = (projects: any, id: number) => {
return projects.find((project) => { return projects.find((project: any) => {
return project.id === id; return Number(project.id) === id;
}); });
}; };
const Project = () => { const Project = () => {
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const project = useMemo(() => getProject(Number(id)), [id]);
// @ts-expect-error create context type for projects
const { projects } = useOutletContext();
const project = useMemo(
() => getProject(projects, Number(id)),
[id, projects],
);
return ( return (
<div className="h-full"> <div className="h-full">

View File

@ -1,12 +1,14 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { Button, Typography, Chip } from '@material-tailwind/react'; import { Button, Typography, Chip } from '@material-tailwind/react';
import ProjectCard from '../../components/projects/ProjectCard'; import ProjectCard from '../../components/projects/ProjectCard';
import projectsDetail from '../../assets/projects.json';
const Projects = () => { const Projects = () => {
// @ts-expect-error create context type for projects
const { projects } = useOutletContext();
return ( return (
<div> <div>
<div className="flex p-5"> <div className="flex p-5">
@ -15,7 +17,7 @@ const Projects = () => {
<Typography variant="h4">Projects</Typography> <Typography variant="h4">Projects</Typography>
<Chip <Chip
className="bg-gray-300 rounded-full static" className="bg-gray-300 rounded-full static"
value={projectsDetail.length} value={projects.length}
size="sm" size="sm"
/> />
</div> </div>
@ -29,7 +31,8 @@ const Projects = () => {
</div> </div>
</div> </div>
<div className="grid grid-cols-3 gap-5 p-5"> <div className="grid grid-cols-3 gap-5 p-5">
{projectsDetail.map((project, key) => { {projects.length !== 0 &&
projects.map((project: any, key: number) => {
return <ProjectCard project={project} key={key} />; return <ProjectCard project={project} key={key} />;
})} })}
</div> </div>

View File

@ -8,7 +8,7 @@ export interface ProjectDetails {
id: number; id: number;
createdAt: string; createdAt: string;
createdBy: string; createdBy: string;
deployment: string; deployments: DeploymentDetails[];
source: string; source: string;
latestCommit: { latestCommit: {
message: string; message: string;
@ -30,6 +30,7 @@ export interface DeploymentDetails {
isProduction: boolean; isProduction: boolean;
status: Status; status: Status;
branch: string; branch: string;
isCurrent: boolean;
commit: { commit: {
hash: string; hash: string;
message: string; message: string;

View File

@ -1,6 +1,6 @@
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { getUser, getOrganizations } from './gql-queries'; import { getUser, getOrganizations, getDeployments } from './gql-queries';
export interface GraphQLConfig { export interface GraphQLConfig {
gqlEndpoint: string; gqlEndpoint: string;
@ -31,4 +31,15 @@ export class GQLClient {
return data; return data;
} }
async getDeployments (projectId: string) : Promise<any> {
const { data } = await this.client.query({
query: getDeployments,
variables: {
projectId
}
});
return data;
}
} }

View File

@ -15,10 +15,13 @@ query {
export const getOrganizations = gql` export const getOrganizations = gql`
query { query {
organizations { organizations {
id
name
projects { projects {
id id
owner { owner {
id id
name
} }
deployments { deployments {
id id
@ -42,3 +45,28 @@ query {
} }
} }
`; `;
export const getDeployments = gql`
query ($projectId: String!) {
deployments(projectId: $projectId) {
id
domain{
branch
createdAt
isRedirected
id
name
status
updatedAt
}
branch
commitHash
title
environment
isCurrent
status
createdAt
updatedAt
}
}
`;