mirror of
https://github.com/snowball-tools/snowballtools-base
synced 2024-11-18 02:43:05 +00:00
Integrate rollback deployment GQL mutation in frontend (#49)
* Use rollback deployment client in UI * Check if deployements domain is undefined * Fix typo * Rename variable to current deployment * Handle deployment domain relation on rollback --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
fdf06f9bd0
commit
d97794f1bf
@ -304,8 +304,21 @@ export class Database {
|
|||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
|
||||||
// TODO: Implement transactions
|
// TODO: Implement transactions
|
||||||
const oldCurrentDeploymentUpdate = await deploymentRepository.update({ project: { id: projectId }, isCurrent: true }, { isCurrent: false });
|
const oldCurrentDeployment = await deploymentRepository.findOne({
|
||||||
const newCurrentDeploymentUpdate = await deploymentRepository.update({ id: Number(deploymentId) }, { isCurrent: true });
|
relations: {
|
||||||
|
domain: true
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
project: {
|
||||||
|
id: projectId
|
||||||
|
},
|
||||||
|
isCurrent: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldCurrentDeploymentUpdate = await deploymentRepository.update({ project: { id: projectId }, isCurrent: true }, { isCurrent: false, domain: null });
|
||||||
|
|
||||||
|
const newCurrentDeploymentUpdate = await deploymentRepository.update({ id: Number(deploymentId) }, { isCurrent: true, domain: oldCurrentDeployment?.domain });
|
||||||
|
|
||||||
if (oldCurrentDeploymentUpdate.affected && newCurrentDeploymentUpdate.affected) {
|
if (oldCurrentDeploymentUpdate.affected && newCurrentDeploymentUpdate.affected) {
|
||||||
return oldCurrentDeploymentUpdate.affected > 0 && newCurrentDeploymentUpdate.affected > 0;
|
return oldCurrentDeploymentUpdate.affected > 0 && newCurrentDeploymentUpdate.affected > 0;
|
||||||
|
@ -56,8 +56,8 @@ export const createResolvers = async (db: Database): Promise<any> => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
projectsInOrganization: async (_: any, { organizationId }: {organizationId: string }, context: any) => {
|
projectsInOrganization: async (_: any, { organizationId }: {organizationId: string }, context: any) => {
|
||||||
const dbProject = await db.getProjectsInOrganization(context.userId, organizationId);
|
const dbProjects = await db.getProjectsInOrganization(context.userId, organizationId);
|
||||||
return dbProject;
|
return dbProjects;
|
||||||
},
|
},
|
||||||
|
|
||||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
|
@ -25,9 +25,8 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
|
|||||||
<Link to={`projects/${project.id}`}>
|
<Link to={`projects/${project.id}`}>
|
||||||
<Typography>{project.name}</Typography>
|
<Typography>{project.name}</Typography>
|
||||||
<Typography color="gray" variant="small">
|
<Typography color="gray" variant="small">
|
||||||
{project.deployments[0]?.domain.name
|
{project.deployments[0]?.domain?.name ??
|
||||||
? project.deployments[0]?.domain.name
|
'No Production Deployment'}
|
||||||
: ''}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import FilterForm, {
|
|||||||
} from './deployments/FilterForm';
|
} from './deployments/FilterForm';
|
||||||
import { DeploymentDetails } from '../../../types/project';
|
import { DeploymentDetails } from '../../../types/project';
|
||||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||||
|
import { COMMIT_DETAILS } from '../../../constants';
|
||||||
|
|
||||||
const DEFAULT_FILTER_VALUE: FilterValue = {
|
const DEFAULT_FILTER_VALUE: FilterValue = {
|
||||||
searchedBranch: '',
|
searchedBranch: '',
|
||||||
@ -23,25 +24,11 @@ const DeploymentsTabPanel = ({ projectId }: { projectId: string }) => {
|
|||||||
|
|
||||||
const fetchDeployments = async () => {
|
const fetchDeployments = async () => {
|
||||||
const { deployments } = await client.getDeployments(projectId);
|
const { deployments } = await client.getDeployments(projectId);
|
||||||
const updatedDeployments = deployments.map((deployment: any) => {
|
const updatedDeployments = deployments.map((deployment) => {
|
||||||
return {
|
return {
|
||||||
...deployment,
|
...deployment,
|
||||||
isProduction: deployment.environment === 'Production',
|
|
||||||
author: '',
|
author: '',
|
||||||
commit: {
|
commit: COMMIT_DETAILS,
|
||||||
hash: '',
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
domain: deployment.domain
|
|
||||||
? {
|
|
||||||
...deployment.domain,
|
|
||||||
record: {
|
|
||||||
type: '',
|
|
||||||
name: '',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setDeployments(updatedDeployments);
|
setDeployments(updatedDeployments);
|
||||||
@ -51,11 +38,11 @@ const DeploymentsTabPanel = ({ projectId }: { projectId: string }) => {
|
|||||||
fetchDeployments();
|
fetchDeployments();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const productionDeployment = useMemo(() => {
|
const currentDeployment = useMemo(() => {
|
||||||
return deployments.find((deployment) => {
|
return deployments.find((deployment) => {
|
||||||
return deployment.isProduction === true;
|
return deployment.isCurrent === true;
|
||||||
}) as DeploymentDetails;
|
});
|
||||||
}, []);
|
}, [deployments]);
|
||||||
|
|
||||||
const filteredDeployments = useMemo<DeploymentDetails[]>(() => {
|
const filteredDeployments = useMemo<DeploymentDetails[]>(() => {
|
||||||
return deployments.filter((deployment) => {
|
return deployments.filter((deployment) => {
|
||||||
@ -102,8 +89,9 @@ const DeploymentsTabPanel = ({ projectId }: { projectId: string }) => {
|
|||||||
<DeploymentDetailsCard
|
<DeploymentDetailsCard
|
||||||
deployment={deployment}
|
deployment={deployment}
|
||||||
key={key}
|
key={key}
|
||||||
productionDeployment={productionDeployment}
|
currentDeployment={currentDeployment!}
|
||||||
onUpdate={onUpdateDeploymenToProd}
|
onUpdate={onUpdateDeploymenToProd}
|
||||||
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -31,9 +31,8 @@ const OverviewTabPanel = ({ project, organizationProject }: OverviewProps) => {
|
|||||||
<div className="grow">
|
<div className="grow">
|
||||||
<Typography>{project.name}</Typography>
|
<Typography>{project.name}</Typography>
|
||||||
<Typography variant="small" color="gray">
|
<Typography variant="small" color="gray">
|
||||||
{project.deployments[0]?.domain.name
|
{project.deployments[0]?.domain?.name ??
|
||||||
? project.deployments[0]?.domain.name
|
'No Production Deployment'}
|
||||||
: ''}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
ChipProps,
|
ChipProps,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { Environment } from 'gql-client';
|
||||||
|
|
||||||
import { relativeTimeMs } from '../../../../utils/time';
|
import { relativeTimeMs } from '../../../../utils/time';
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
@ -19,8 +20,9 @@ import { useGQLClient } from '../../../../context/GQLClientContext';
|
|||||||
|
|
||||||
interface DeployDetailsCardProps {
|
interface DeployDetailsCardProps {
|
||||||
deployment: DeploymentDetails;
|
deployment: DeploymentDetails;
|
||||||
productionDeployment: DeploymentDetails;
|
currentDeployment: DeploymentDetails;
|
||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_COLORS: { [key in Status]: ChipProps['color'] } = {
|
const STATUS_COLORS: { [key in Status]: ChipProps['color'] } = {
|
||||||
@ -31,8 +33,9 @@ const STATUS_COLORS: { [key in Status]: ChipProps['color'] } = {
|
|||||||
|
|
||||||
const DeploymentDetailsCard = ({
|
const DeploymentDetailsCard = ({
|
||||||
deployment,
|
deployment,
|
||||||
productionDeployment,
|
currentDeployment,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
projectId,
|
||||||
}: DeployDetailsCardProps) => {
|
}: DeployDetailsCardProps) => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
@ -60,6 +63,19 @@ const DeploymentDetailsCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rollbackDeploymentHandler = async () => {
|
||||||
|
const isRollbacked = await client.rollbackDeployment(
|
||||||
|
projectId,
|
||||||
|
deployment.id,
|
||||||
|
);
|
||||||
|
if (isRollbacked) {
|
||||||
|
await onUpdate();
|
||||||
|
toast.success('Deployment rolled back');
|
||||||
|
} else {
|
||||||
|
toast.error('Unable to rollback deployment');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2">
|
<div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
@ -73,7 +89,7 @@ const DeploymentDetailsCard = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Typography color="gray">
|
<Typography color="gray">
|
||||||
{deployment.isProduction
|
{deployment.environment === Environment.Production
|
||||||
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
|
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
|
||||||
: 'Preview'}
|
: 'Preview'}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -81,7 +97,7 @@ const DeploymentDetailsCard = ({
|
|||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Typography color="gray">^ {deployment.branch}</Typography>
|
<Typography color="gray">^ {deployment.branch}</Typography>
|
||||||
<Typography color="gray">
|
<Typography color="gray">
|
||||||
^ {deployment.commit.hash} {deployment.commit.message}
|
^ {deployment.commitHash} {deployment.commit.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 flex items-center">
|
<div className="col-span-1 flex items-center">
|
||||||
@ -95,7 +111,7 @@ const DeploymentDetailsCard = ({
|
|||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem>^ Visit</MenuItem>
|
<MenuItem>^ Visit</MenuItem>
|
||||||
<MenuItem>^ Assign domain</MenuItem>
|
<MenuItem>^ Assign domain</MenuItem>
|
||||||
{!deployment.isProduction && (
|
{!(deployment.environment === Environment.Production) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setChangeToProduction(!changeToProduction)}
|
onClick={() => setChangeToProduction(!changeToProduction)}
|
||||||
>
|
>
|
||||||
@ -106,13 +122,19 @@ const DeploymentDetailsCard = ({
|
|||||||
<hr className="my-3" />
|
<hr className="my-3" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setRedeployToProduction(!redeployToProduction)}
|
onClick={() => setRedeployToProduction(!redeployToProduction)}
|
||||||
disabled={!(deployment.isProduction && deployment.isCurrent)}
|
disabled={
|
||||||
|
!(
|
||||||
|
deployment.environment === Environment.Production &&
|
||||||
|
deployment.isCurrent
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
^ Redeploy to production
|
^ Redeploy to production
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!deployment.isProduction && (
|
{deployment.environment === Environment.Production && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setRollbackDeployment(!rollbackDeployment)}
|
onClick={() => setRollbackDeployment(!rollbackDeployment)}
|
||||||
|
disabled={deployment.isCurrent}
|
||||||
>
|
>
|
||||||
^ Rollback to this version
|
^ Rollback to this version
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -181,7 +203,10 @@ const DeploymentDetailsCard = ({
|
|||||||
open={rollbackDeployment}
|
open={rollbackDeployment}
|
||||||
confirmButtonTitle="Rollback"
|
confirmButtonTitle="Rollback"
|
||||||
color="blue"
|
color="blue"
|
||||||
handleConfirm={() => setRollbackDeployment((preVal) => !preVal)}
|
handleConfirm={async () => {
|
||||||
|
await rollbackDeploymentHandler();
|
||||||
|
setRollbackDeployment((preVal) => !preVal);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
@ -189,7 +214,7 @@ const DeploymentDetailsCard = ({
|
|||||||
deployment
|
deployment
|
||||||
</Typography>
|
</Typography>
|
||||||
<DeploymentDialogBodyCard
|
<DeploymentDialogBodyCard
|
||||||
deployment={productionDeployment}
|
deployment={currentDeployment}
|
||||||
chip={{
|
chip={{
|
||||||
value: 'Live Deployment',
|
value: 'Live Deployment',
|
||||||
color: 'green',
|
color: 'green',
|
||||||
@ -206,10 +231,7 @@ const DeploymentDetailsCard = ({
|
|||||||
These domains will point to your new deployment:
|
These domains will point to your new deployment:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small" color="blue">
|
<Typography variant="small" color="blue">
|
||||||
^ saugatt.com
|
^ {currentDeployment.domain?.name}
|
||||||
</Typography>
|
|
||||||
<Typography variant="small" color="blue">
|
|
||||||
^ www.saugatt.com
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
@ -31,7 +31,7 @@ const DeploymentDialogBodyCard = ({
|
|||||||
{deployment.title}
|
{deployment.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
^ {deployment.branch} ^ {deployment.commit.hash}{' '}
|
^ {deployment.branch} ^ {deployment.commitHash}{' '}
|
||||||
{deployment.commit.message}
|
{deployment.commit.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
|
5
packages/frontend/src/constants.ts
Normal file
5
packages/frontend/src/constants.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const COMMIT_DETAILS = {
|
||||||
|
message: 'subscription added',
|
||||||
|
createdAt: '2023-12-11T04:20:00',
|
||||||
|
branch: 'main',
|
||||||
|
};
|
@ -6,6 +6,7 @@ import { Button, Typography, Chip } from '@material-tailwind/react';
|
|||||||
import ProjectCard from '../components/projects/ProjectCard';
|
import ProjectCard from '../components/projects/ProjectCard';
|
||||||
import { useGQLClient } from '../context/GQLClientContext';
|
import { useGQLClient } from '../context/GQLClientContext';
|
||||||
import { ProjectDetails } from '../types/project';
|
import { ProjectDetails } from '../types/project';
|
||||||
|
import { COMMIT_DETAILS } from '../constants';
|
||||||
|
|
||||||
// TODO: Implement organization switcher
|
// TODO: Implement organization switcher
|
||||||
const USER_ORGANIZATION_ID = '1';
|
const USER_ORGANIZATION_ID = '1';
|
||||||
@ -22,11 +23,7 @@ const Projects = () => {
|
|||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
// TODO: Populate from github API
|
// TODO: Populate from github API
|
||||||
latestCommit: {
|
latestCommit: COMMIT_DETAILS,
|
||||||
message: 'subscription added',
|
|
||||||
createdAt: '2023-12-11T04:20:00',
|
|
||||||
branch: 'main',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { Environment, Project } from 'gql-client';
|
import { Project, Deployment } from 'gql-client';
|
||||||
|
|
||||||
export interface ProjectDetails extends Project {
|
export interface ProjectDetails extends Project {
|
||||||
// TODO: isDomain flag
|
// TODO: isDomain flag
|
||||||
domain?: string | null;
|
domain?: string | null;
|
||||||
// TODO: Use deployment branch
|
// TODO: Use deployment branch
|
||||||
source?: string;
|
source?: string;
|
||||||
latestCommit: {
|
latestCommit: Commit;
|
||||||
message: string;
|
|
||||||
createdAt: string;
|
|
||||||
branch: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Move out of project
|
// TODO: Move out of project
|
||||||
repositories?: RepositoryDetails[];
|
repositories?: RepositoryDetails[];
|
||||||
@ -22,22 +18,9 @@ export interface ProjectMember {
|
|||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeploymentDetails {
|
export interface DeploymentDetails extends Deployment {
|
||||||
id: string;
|
commit: Commit;
|
||||||
title: string;
|
|
||||||
isProduction: boolean;
|
|
||||||
domain: DomainDetails;
|
|
||||||
status: Status;
|
|
||||||
branch: string;
|
|
||||||
environment: Environment;
|
|
||||||
isCurrent: boolean;
|
|
||||||
commit: {
|
|
||||||
hash: string;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
author: string;
|
author: string;
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Status {
|
export enum Status {
|
||||||
@ -92,3 +75,9 @@ export interface Member {
|
|||||||
export interface ProjectSearchOutletContext {
|
export interface ProjectSearchOutletContext {
|
||||||
projects: ProjectDetails[];
|
projects: ProjectDetails[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Commit {
|
||||||
|
message: string;
|
||||||
|
createdAt: string;
|
||||||
|
branch: string;
|
||||||
|
}
|
||||||
|
@ -42,7 +42,7 @@ mutation ($projectId: String!) {
|
|||||||
|
|
||||||
export const rollbackDeployment = gql`
|
export const rollbackDeployment = gql`
|
||||||
mutation ($projectId: String! ,$deploymentId: String!) {
|
mutation ($projectId: String! ,$deploymentId: String!) {
|
||||||
rollbackDeployment(proejctId: $projectId, deploymentId: $deploymentId)
|
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user