Add GQL mutation for updating domain (#50)

* Pass data for all domains in edit domain dialog box

* Add mutation to update domain by id

* implement front end and gql client method to edit domain

* Rename arguments of resolver function to update domain and project

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-01-30 14:01:09 +05:30 committed by Ashwin Phatak
parent a34e2286a6
commit fdf06f9bd0
9 changed files with 111 additions and 28 deletions

View File

@ -365,4 +365,16 @@ export class Database {
return domains; return domains;
} }
async updateDomainById (domainId: string, updates: DeepPartial<Domain>): Promise<boolean> {
const domainRepository = this.dataSource.getRepository(Domain);
const updateResult = await domainRepository.update({ id: Number(domainId) }, updates);
if (updateResult.affected) {
return updateResult.affected > 0;
} else {
return false;
}
}
} }

View File

@ -153,9 +153,9 @@ export const createResolvers = async (db: Database): Promise<any> => {
} }
}, },
updateProject: async (_: any, { projectId, updateProject }: { projectId: string, updateProject: { name: string, description: string } }) => { updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: { name: string, description: string } }) => {
try { try {
return db.updateProjectById(projectId, updateProject); return db.updateProjectById(projectId, projectDetails);
} catch (err) { } catch (err) {
log(err); log(err);
return false; return false;
@ -198,6 +198,16 @@ export const createResolvers = async (db: Database): Promise<any> => {
log(err); log(err);
return false; return false;
} }
},
updateDomain: async (_: any, { domainId, domainDetails }: { domainId: string, domainDetails: {name?: string, isRedirected?: boolean, branch?: string }}) => {
try {
await db.updateDomainById(domainId, domainDetails);
return true;
} catch (err) {
log(err);
return false;
}
} }
} }
}; };

View File

@ -129,11 +129,12 @@ type Mutation {
removeMember(memberId: String!): Boolean! removeMember(memberId: String!): Boolean!
addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean!
updateProject(projectId: String!, updateProject: UpdateProjectInput): Boolean! updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean! redeployToProd(deploymentId: String!): Boolean!
deleteProject(projectId: String!): Boolean! deleteProject(projectId: String!): Boolean!
rollbackDeployment(projectId: String!, deploymentId: String!): Boolean! rollbackDeployment(projectId: String!, deploymentId: String!): Boolean!
addDomain(projectId: String!, domainDetails: AddDomainInput!): Boolean! addDomain(projectId: String!, domainDetails: AddDomainInput!): Boolean!
updateDomain(domainId: String!, domainDetails: UpdateDomainInput!): Boolean!
} }
input AddEnvironmentVariableInput { input AddEnvironmentVariableInput {
@ -150,3 +151,9 @@ input UpdateProjectInput {
input AddDomainInput { input AddDomainInput {
name: String! name: String!
} }
input UpdateDomainInput {
name: String
isRedirected: Boolean
branch: String
}

View File

@ -24,9 +24,11 @@ enum RefreshStatus {
} }
interface DomainCardProps { interface DomainCardProps {
domains: Domain[];
domain: Domain; domain: Domain;
repo: RepositoryDetails; repo: RepositoryDetails;
project: ProjectDetails; project: ProjectDetails;
onUpdate: () => Promise<void>;
} }
const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds
@ -38,7 +40,13 @@ const DOMAIN_RECORD = {
value: '56.49.19.21', value: '56.49.19.21',
}; };
const DomainCard = ({ domain, repo, project }: DomainCardProps) => { const DomainCard = ({
domains,
domain,
repo,
project,
onUpdate,
}: DomainCardProps) => {
const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE); const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false);
@ -163,10 +171,11 @@ const DomainCard = ({ domain, repo, project }: DomainCardProps) => {
handleOpen={() => { handleOpen={() => {
setEditDialogOpen((preVal) => !preVal); setEditDialogOpen((preVal) => !preVal);
}} }}
domains={domains}
open={editDialogOpen} open={editDialogOpen}
domain={domain} domain={domain}
repo={repo} repo={repo}
project={project} onUpdate={onUpdate}
/> />
</> </>
); );

View File

@ -55,10 +55,12 @@ const Domains = () => {
{domains.map((domain) => { {domains.map((domain) => {
return ( return (
<DomainCard <DomainCard
domains={domains}
domain={domain} domain={domain}
key={domain.id} key={domain.id}
repo={linkedRepo!} repo={linkedRepo!}
project={currentProject!} project={currentProject!}
onUpdate={fetchDomains}
/> />
); );
})} })}

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Domain } from 'gql-client'; import { Domain } from 'gql-client';
@ -15,25 +15,30 @@ import {
Option, Option,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import { ProjectDetails, RepositoryDetails } from '../../../../types/project'; import { RepositoryDetails } from '../../../../types/project';
import { useGQLClient } from '../../../../context/GQLClientContext';
const DEFAULT_REDIRECT_OPTIONS = ['none']; const DEFAULT_REDIRECT_OPTIONS = ['none'];
interface EditDomainDialogProp { interface EditDomainDialogProp {
domains: Domain[];
open: boolean; open: boolean;
handleOpen: () => void; handleOpen: () => void;
domain: Domain; domain: Domain;
repo: RepositoryDetails; repo: RepositoryDetails;
project: ProjectDetails; onUpdate: () => Promise<void>;
} }
const EditDomainDialog = ({ const EditDomainDialog = ({
domains,
open, open,
handleOpen, handleOpen,
domain, domain,
repo, repo,
project, onUpdate,
}: EditDomainDialogProp) => { }: EditDomainDialogProp) => {
const client = useGQLClient();
const getRedirectUrl = (domain: Domain) => { const getRedirectUrl = (domain: Domain) => {
const domainArr = domain.name.split('www.'); const domainArr = domain.name.split('www.');
let redirectUrl = ''; let redirectUrl = '';
@ -45,12 +50,6 @@ const EditDomainDialog = ({
return redirectUrl; return redirectUrl;
}; };
const domains = project.deployments
.filter((deployment: any) => {
return deployment.domain != null;
})
.map((deployment: any) => deployment.domain);
const redirectOptions = useMemo(() => { const redirectOptions = useMemo(() => {
const redirectUrl = getRedirectUrl(domain); const redirectUrl = getRedirectUrl(domain);
return [...DEFAULT_REDIRECT_OPTIONS, redirectUrl]; return [...DEFAULT_REDIRECT_OPTIONS, redirectUrl];
@ -63,9 +62,31 @@ const EditDomainDialog = ({
(domain) => domain.name === redirectUrl, (domain) => domain.name === redirectUrl,
); );
return domainRedirected?.isRedirectedto; return domainRedirected?.isRedirected;
}, [domain]); }, [domain]);
const onSubmit = useCallback(
async (data: any) => {
const updates = {
name: data.name,
branch: data.branch,
isRedirected: data.redirectedTo !== 'none',
};
const { updateDomain } = await client.updateDomain(domain.id, updates);
if (updateDomain) {
await onUpdate();
toast.success(`Domain ${domain.name} has been updated`);
} else {
toast.error(`Error updating domain ${domain.name}`);
}
handleOpen();
},
[client],
);
const { const {
handleSubmit, handleSubmit,
register, register,
@ -94,12 +115,7 @@ const EditDomainDialog = ({
X X
</Button> </Button>
</DialogHeader> </DialogHeader>
<form <form onSubmit={handleSubmit(onSubmit)}>
onSubmit={handleSubmit(() => {
handleOpen();
toast.success(`Domain ${domain.name} has been updated`);
})}
>
<DialogBody className="flex flex-col gap-2 p-4"> <DialogBody className="flex flex-col gap-2 p-4">
<Typography variant="small">Domain name</Typography> <Typography variant="small">Domain name</Typography>
<Input crossOrigin={undefined} {...register('name')} /> <Input crossOrigin={undefined} {...register('name')} />

View File

@ -1,8 +1,8 @@
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject, getDomains, getProjectsInOrganization } from './queries'; import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject, getDomains, getProjectsInOrganization } from './queries';
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse } from './types'; import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse, UpdateDomainInput, UpdateDomainResponse } from './types';
import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment } from './mutations'; import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation } from './mutations';
export interface GraphQLConfig { export interface GraphQLConfig {
gqlEndpoint: string; gqlEndpoint: string;
@ -147,12 +147,24 @@ export class GQLClient {
return data; return data;
} }
async updateProject (projectId: string, updateProject: UpdateProjectInput): Promise<UpdateProjectResponse> { async updateProject (projectId: string, projectDetails: UpdateProjectInput): Promise<UpdateProjectResponse> {
const { data } = await this.client.mutate({ const { data } = await this.client.mutate({
mutation: updateProjectMutation, mutation: updateProjectMutation,
variables: { variables: {
projectId, projectId,
updateProject projectDetails
}
});
return data;
}
async updateDomain (domainId: string, domainDetails: UpdateDomainInput): Promise<UpdateDomainResponse> {
const { data } = await this.client.mutate({
mutation: updateDomainMutation,
variables: {
domainId,
domainDetails
} }
}); });

View File

@ -19,8 +19,13 @@ mutation ($deploymentId: String!) {
`; `;
export const updateProjectMutation = gql` export const updateProjectMutation = gql`
mutation ($projectId: String!, $updateProject: UpdateProjectInput) { mutation ($projectId: String!, $projectDetails: UpdateProjectInput) {
updateProject(projectId: $projectId, updateProject: $updateProject) updateProject(projectId: $projectId, projectDetails: $projectDetails)
}`;
export const updateDomainMutation = gql`
mutation ($domainId: String!, $domainDetails: UpdateDomainInput!) {
updateDomain(domainId: $domainId, domainDetails: $domainDetails)
}`; }`;
export const redeployToProd = gql` export const redeployToProd = gql`

View File

@ -187,6 +187,10 @@ export type UpdateProjectResponse = {
updateProject: boolean; updateProject: boolean;
} }
export type UpdateDomainResponse = {
updateDomain: boolean;
}
export type DeleteProjectResponse = { export type DeleteProjectResponse = {
deleteProject: boolean; deleteProject: boolean;
} }
@ -196,6 +200,12 @@ export type UpdateProjectInput = {
description: string description: string
} }
export type UpdateDomainInput = {
name?: string;
isRedirected?: boolean;
branch?: string;
}
export type RedeployToProdResponse = { export type RedeployToProdResponse = {
redeployToProd: boolean redeployToProd: boolean
} }