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

View File

@ -11,13 +11,13 @@ import {
import SearchBar from '../SearchBar';
import { ProjectDetails } from '../../types/project';
import projectsData from '../../assets/projects.json';
interface ProjectsSearchProps {
projects: ProjectDetails[];
onChange?: (data: ProjectDetails) => void;
}
const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
const ProjectSearchBar = ({ projects, onChange }: ProjectsSearchProps) => {
const [items, setItems] = useState<ProjectDetails[]>([]);
const [selectedItem, setSelectedItem] = useState<ProjectDetails | null>(null);
@ -32,7 +32,7 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
onInputValueChange({ inputValue }) {
setItems(
inputValue
? projectsData.filter((project) =>
? projects.filter((project) =>
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';
@ -11,67 +11,79 @@ interface OverviewProps {
project: ProjectDetails;
}
const OverviewTabPanel = ({ project }: OverviewProps) => (
<div className="grid grid-cols-5">
<div className="col-span-3 p-2">
<div className="flex items-center gap-2 p-2 ">
<div>^</div>
<div className="grow">
<Typography>{project.name}</Typography>
<Typography variant="small" color="gray">
{project.url}
</Typography>
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="col-span-3 p-2">
<div className="flex items-center gap-2 p-2 ">
<div>^</div>
<div className="grow">
<Typography>{project.name}</Typography>
<Typography variant="small" color="gray">
{project.url}
</Typography>
</div>
</div>
</div>
<div className="flex justify-between p-2 text-sm items-center">
<div>
^ Domain
{!project.domain && (
<Chip
className="normal-case ml-6 bg-[#FED7AA] text-[#EA580C] inline font-normal"
size="sm"
value="Not connected"
icon="^"
/>
<div className="flex justify-between p-2 text-sm items-center">
<div>
^ Domain
{!project.domain && (
<Chip
className="normal-case ml-6 bg-[#FED7AA] text-[#EA580C] inline font-normal"
size="sm"
value="Not connected"
icon="^"
/>
)}
</div>
{project.domain ? (
<p>{project.domain}</p>
) : (
<Button className="normal-case rounded-full" color="blue" size="sm">
Setup
</Button>
)}
</div>
{project.domain ? (
<p>{project.domain}</p>
) : (
<Button className="normal-case rounded-full" color="blue" size="sm">
Setup
</Button>
)}
<div className="flex justify-between p-2 text-sm">
<p>^ Source</p>
<p>{project.source}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Deployment</p>
<p className="text-blue-600">{currentDeploymentTitle}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Created</p>
<p>
{/* TODO: Use following time conversion wherever required */}
{relativeTime(new Date(Number(project.createdAt)).toISOString())} by
^ {project.createdBy}
</p>
</div>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Source</p>
<p>{project.source}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Deployment</p>
<p className="text-blue-600">{project.deployment}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Created</p>
<p>
{relativeTime(project.createdAt)} by ^ {project.createdBy}
</p>
<div className="col-span-2 p-2">
<div className="flex justify-between">
<Typography variant="h6">Activity</Typography>
<button className="text-xs bg-gray-300 rounded-full p-2">
See all
</button>
</div>
<div className="p-2">
{activityDetails.map((activity, key) => {
return <ActivityCard activity={activity} key={key} />;
})}
</div>
</div>
</div>
<div className="col-span-2 p-2">
<div className="flex justify-between">
<Typography variant="h6">Activity</Typography>
<button className="text-xs bg-gray-300 rounded-full p-2">
See all
</button>
</div>
<div className="p-2">
{activityDetails.map((activity, key) => {
return <ActivityCard activity={activity} key={key} />;
})}
</div>
</div>
</div>
);
);
};
export default OverviewTabPanel;

View File

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

View File

@ -1,17 +1,61 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import HorizontalLine from '../components/HorizontalLine';
import { IconButton, Typography } from '@material-tailwind/react';
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
import { useGQLClient } from '../context/GQLClientContext';
import { ProjectDetails } from '../types/project';
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 (
<div>
<div className="sticky top-0 bg-white z-30">
<div className="flex p-5">
<div className="grow mr-2">
<ProjectSearchBar onChange={() => {}} />
<ProjectSearchBar onChange={() => {}} projects={projects} />
</div>
<IconButton color="blue" className="rounded-full mr-2">
<Typography variant="h5">+</Typography>
@ -26,7 +70,7 @@ const ProjectSearch = () => {
<HorizontalLine />
</div>
<div className="z-0">
<Outlet />
<Outlet context={{ projects }} />
</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 { Button, Typography, Chip } from '@material-tailwind/react';
import ProjectCard from '../components/projects/ProjectCard';
import projectsDetail from '../assets/projects.json';
import { useGQLClient } from '../context/GQLClientContext';
const Projects = () => {
const client = useGQLClient();
const [projects, setProjects] = useState([]);
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]);
// @ts-expect-error create context type for projects
const { projects } = useOutletContext();
return (
<div>
@ -52,7 +18,7 @@ const Projects = () => {
<Typography variant="h4">Projects</Typography>
<Chip
className="bg-gray-300 rounded-full static"
value={projectsDetail.length}
value={projects.length}
size="sm"
/>
</div>
@ -67,7 +33,7 @@ const Projects = () => {
</div>
<div className="grid grid-cols-3 gap-5 p-5">
{projects.length !== 0 &&
projects.map((project, key) => {
projects.map((project: any, key: number) => {
return <ProjectCard project={project} key={key} />;
})}
</div>

View File

@ -1,22 +1,28 @@
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 HorizontalLine from '../../components/HorizontalLine';
import projects from '../../assets/projects.json';
import ProjectTabs from '../../components/projects/project/ProjectTabs';
const getProject = (id: number) => {
return projects.find((project) => {
return project.id === id;
const getProject = (projects: any, id: number) => {
return projects.find((project: any) => {
return Number(project.id) === id;
});
};
const Project = () => {
const { id } = useParams();
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 (
<div className="h-full">

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { getUser, getOrganizations } from './gql-queries';
import { getUser, getOrganizations, getDeployments } from './gql-queries';
export interface GraphQLConfig {
gqlEndpoint: string;
@ -31,4 +31,15 @@ export class GQLClient {
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`
query {
organizations {
id
name
projects {
id
owner {
id
name
}
deployments {
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
}
}
`;