Add and refresh environment variables in project settings tab (#37)
* Create and use add environment variables gql client method * Implement get environment variables method * Display fetched environment variables * Refactor create environment variables submit handler * Use environment variables type from gql-client * Add fixtures for project member --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
c2b997a17b
commit
44310d4eb8
@ -58,6 +58,16 @@ export const createResolvers = async (db: Database): Promise<any> => {
|
||||
return deployments;
|
||||
},
|
||||
|
||||
environmentVariables: async (_: any, { projectId }: { projectId: string }) => {
|
||||
const dbEnvironmentVariables = await db.getEnvironmentVariablesByProjectId(projectId);
|
||||
|
||||
const environmentVariables = dbEnvironmentVariables.map(dbEnvironmentVariable => {
|
||||
return environmentVariableToGqlType(dbEnvironmentVariable);
|
||||
});
|
||||
|
||||
return environmentVariables;
|
||||
},
|
||||
|
||||
projectMembers: async (_: any, { projectId }: { projectId: string }) => {
|
||||
const dbProjectMembers = await db.getProjectMembersByProjectId(projectId);
|
||||
|
||||
|
@ -116,6 +116,7 @@ type Query {
|
||||
organizations: [Organization!]
|
||||
projects: [Project!]
|
||||
deployments(projectId: String!): [Deployment!]
|
||||
environmentVariables(projectId: String!): [EnvironmentVariable!]
|
||||
projectMembers(projectId: String!): [ProjectMember!]
|
||||
searchProjects(searchText: String!): [Project!]
|
||||
}
|
||||
|
@ -2,11 +2,46 @@
|
||||
{
|
||||
"memberIndex": 1,
|
||||
"projectIndex": 0,
|
||||
"permissions": ["View", "Edit"]
|
||||
"permissions": ["View"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 2,
|
||||
"projectIndex": 0,
|
||||
"permissions": ["View", "Edit"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 2,
|
||||
"projectIndex": 1,
|
||||
"permissions": ["View"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 0,
|
||||
"projectIndex": 2,
|
||||
"permissions": ["View"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 1,
|
||||
"projectIndex": 2,
|
||||
"permissions": ["View", "Edit"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 0,
|
||||
"projectIndex": 3,
|
||||
"permissions": ["View"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 2,
|
||||
"projectIndex": 3,
|
||||
"permissions": ["View", "Edit"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 1,
|
||||
"projectIndex": 4,
|
||||
"permissions": ["View"]
|
||||
},
|
||||
{
|
||||
"memberIndex": 2,
|
||||
"projectIndex": 4,
|
||||
"permissions": ["View", "Edit"]
|
||||
}
|
||||
]
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Card, Collapse, Typography } from '@material-tailwind/react';
|
||||
|
||||
import { Environment, EnvironmentVariable } from 'gql-client/dist/src/types';
|
||||
|
||||
import EditEnvironmentVariableRow from './EditEnvironmentVariableRow';
|
||||
import { Environments, EnvironmentVariable } from '../../../../types/project';
|
||||
|
||||
interface DisplayEnvironmentVariablesProps {
|
||||
environment: Environments;
|
||||
environment: Environment;
|
||||
variables: EnvironmentVariable[];
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import { IconButton, Input, Typography } from '@material-tailwind/react';
|
||||
|
||||
import { EnvironmentVariable } from 'gql-client';
|
||||
|
||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||
import { EnvironmentVariable } from '../../../../types/project';
|
||||
|
||||
const ShowPasswordIcon = ({
|
||||
handler,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useOutletContext, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Typography,
|
||||
@ -12,14 +12,12 @@ import {
|
||||
Chip,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||
|
||||
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow';
|
||||
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
||||
import {
|
||||
EnvironmentVariable,
|
||||
Environments,
|
||||
ProjectSearchOutletContext,
|
||||
} from '../../../../types/project';
|
||||
import HorizontalLine from '../../../HorizontalLine';
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
|
||||
export type EnvironmentVariablesFormValues = {
|
||||
variables: {
|
||||
@ -35,14 +33,11 @@ export type EnvironmentVariablesFormValues = {
|
||||
|
||||
export const EnvironmentVariablesTabPanel = () => {
|
||||
const { id } = useParams();
|
||||
const client = useGQLClient();
|
||||
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
return projects.find((project) => {
|
||||
return project.id === id;
|
||||
});
|
||||
}, [id, projects]);
|
||||
const [environmentVariables, setEnvironmentVariables] = useState<
|
||||
EnvironmentVariable[]
|
||||
>([]);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@ -74,13 +69,16 @@ export const EnvironmentVariablesTabPanel = () => {
|
||||
if (isSubmitSuccessful) {
|
||||
reset();
|
||||
}
|
||||
}, [isSubmitSuccessful, reset]);
|
||||
}, [isSubmitSuccessful, reset, id]);
|
||||
|
||||
const getEnvironmentVariable = useCallback((environment: Environments) => {
|
||||
return (
|
||||
currentProject?.environmentVariables as EnvironmentVariable[]
|
||||
).filter((item) => item.environments.includes(environment));
|
||||
}, []);
|
||||
const getEnvironmentVariable = useCallback(
|
||||
(environment: Environment) => {
|
||||
return environmentVariables.filter((item) =>
|
||||
item.environments.includes(environment),
|
||||
);
|
||||
},
|
||||
[environmentVariables, id],
|
||||
);
|
||||
|
||||
const isFieldEmpty = useMemo(() => {
|
||||
if (errors.variables) {
|
||||
@ -95,7 +93,55 @@ export const EnvironmentVariablesTabPanel = () => {
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [fields, errors.variables]);
|
||||
}, [fields, errors.variables, id]);
|
||||
|
||||
const fetchEnvironmentVariables = useCallback(
|
||||
async (id: string | undefined) => {
|
||||
if (id) {
|
||||
const { environmentVariables } =
|
||||
await client.getEnvironmentVariables(id);
|
||||
setEnvironmentVariables(environmentVariables);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEnvironmentVariables(id);
|
||||
}, [id]);
|
||||
|
||||
const createEnvironmentVariablesHandler = useCallback(
|
||||
async (createFormData: EnvironmentVariablesFormValues) => {
|
||||
const environmentVariables = createFormData.variables.map((variable) => {
|
||||
return {
|
||||
key: variable.key,
|
||||
value: variable.value,
|
||||
environments: Object.entries(createFormData.environment)
|
||||
.filter(([, value]) => value === true)
|
||||
.map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)),
|
||||
};
|
||||
});
|
||||
|
||||
const { addEnvironmentVariables: isEnvironmentVariablesAdded } =
|
||||
await client.addEnvironmentVariables(id!, environmentVariables);
|
||||
|
||||
if (isEnvironmentVariablesAdded) {
|
||||
reset();
|
||||
setCreateNewVariable((cur) => !cur);
|
||||
|
||||
fetchEnvironmentVariables(id);
|
||||
|
||||
toast.success(
|
||||
createFormData.variables.length > 1
|
||||
? `${createFormData.variables.length} variables added`
|
||||
: `Variable added`,
|
||||
);
|
||||
} else {
|
||||
toast.error('Environment variables not added');
|
||||
}
|
||||
},
|
||||
[id, client],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -112,16 +158,7 @@ export const EnvironmentVariablesTabPanel = () => {
|
||||
</div>
|
||||
<Collapse open={createNewVariable}>
|
||||
<Card className="bg-white p-2">
|
||||
<form
|
||||
onSubmit={handleSubmit((data) => {
|
||||
toast.success(
|
||||
data.variables.length > 1
|
||||
? `${data.variables.length} variables added`
|
||||
: `Variable added`,
|
||||
);
|
||||
reset();
|
||||
})}
|
||||
>
|
||||
<form onSubmit={handleSubmit(createEnvironmentVariablesHandler)}>
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<AddEnvironmentVariableRow
|
||||
@ -189,18 +226,18 @@ export const EnvironmentVariablesTabPanel = () => {
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<DisplayEnvironmentVariables
|
||||
environment={Environments.PRODUCTION}
|
||||
variables={getEnvironmentVariable(Environments.PRODUCTION)}
|
||||
environment={Environment.Production}
|
||||
variables={getEnvironmentVariable(Environment.Production)}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
<DisplayEnvironmentVariables
|
||||
environment={Environments.PREVIEW}
|
||||
variables={getEnvironmentVariable(Environments.PREVIEW)}
|
||||
environment={Environment.Preview}
|
||||
variables={getEnvironmentVariable(Environment.Preview)}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
<DisplayEnvironmentVariables
|
||||
environment={Environments.DEVELOPMENT}
|
||||
variables={getEnvironmentVariable(Environments.DEVELOPMENT)}
|
||||
environment={Environment.Development}
|
||||
variables={getEnvironmentVariable(Environment.Development)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Environment } from 'gql-client';
|
||||
|
||||
import HorizontalLine from '../components/HorizontalLine';
|
||||
import { IconButton, Typography } from '@material-tailwind/react';
|
||||
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
||||
import { useGQLClient } from '../context/GQLClientContext';
|
||||
import { Environments, ProjectDetails } from '../types/project';
|
||||
import { ProjectDetails } from '../types/project';
|
||||
|
||||
const ProjectSearch = () => {
|
||||
const client = useGQLClient();
|
||||
@ -25,7 +27,7 @@ const ProjectSearch = () => {
|
||||
const updatedDeployments = deployments.map((deployment: any) => {
|
||||
return {
|
||||
...deployment,
|
||||
isProduction: deployment.environment === Environments.PRODUCTION,
|
||||
isProduction: deployment.environment === Environment.Production,
|
||||
author: '',
|
||||
commit: {
|
||||
hash: '',
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||
|
||||
export interface ProjectDetails {
|
||||
icon: string;
|
||||
name: string;
|
||||
@ -36,7 +38,7 @@ export interface DeploymentDetails {
|
||||
domain: DomainDetails;
|
||||
status: Status;
|
||||
branch: string;
|
||||
environment: Environments;
|
||||
environment: Environment;
|
||||
isCurrent: boolean;
|
||||
commit: {
|
||||
hash: string;
|
||||
@ -52,19 +54,6 @@ export enum Status {
|
||||
ERROR = 'Error',
|
||||
}
|
||||
|
||||
export enum Environments {
|
||||
PRODUCTION = 'Production',
|
||||
PREVIEW = 'Preview',
|
||||
DEVELOPMENT = 'Development',
|
||||
}
|
||||
|
||||
export interface EnvironmentVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
id: number;
|
||||
environments: Environments[];
|
||||
}
|
||||
|
||||
export interface RepositoryDetails {
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||
|
||||
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects } from './queries';
|
||||
import { GetDeploymentsResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse } from './types';
|
||||
import { removeMember } from './mutations';
|
||||
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables } from './queries';
|
||||
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse } from './types';
|
||||
import { removeMember, addEnvironmentVariables } from './mutations';
|
||||
|
||||
export interface GraphQLConfig {
|
||||
gqlEndpoint: string;
|
||||
@ -58,6 +58,17 @@ export class GQLClient {
|
||||
return data;
|
||||
}
|
||||
|
||||
async getEnvironmentVariables (projectId: string) : Promise<GetEnvironmentVariablesResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: getEnvironmentVariables,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async removeMember (memberId: string): Promise<RemoveMemberResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: removeMember,
|
||||
@ -90,4 +101,16 @@ export class GQLClient {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async addEnvironmentVariables (projectId: string, environmentVariables: AddEnvironmentVariableInput[]): Promise<AddEnvironmentVariablesResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: addEnvironmentVariables,
|
||||
variables: {
|
||||
projectId,
|
||||
environmentVariables
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -5,3 +5,9 @@ mutation ($memberId: String!) {
|
||||
removeMember(memberId: $memberId)
|
||||
}
|
||||
`;
|
||||
|
||||
export const addEnvironmentVariables = gql`
|
||||
mutation ($projectId: String!, $environmentVariables: [AddEnvironmentVariableInput!]) {
|
||||
addEnvironmentVariables(projectId: $projectId, environmentVariables: $environmentVariables)
|
||||
}
|
||||
`;
|
||||
|
@ -82,6 +82,19 @@ query ($projectId: String!) {
|
||||
}
|
||||
`;
|
||||
|
||||
export const getEnvironmentVariables = gql`
|
||||
query ($projectId: String!) {
|
||||
environmentVariables(projectId: $projectId) {
|
||||
createdAt
|
||||
environments
|
||||
id
|
||||
key
|
||||
updatedAt
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const getProjectMembers = gql`
|
||||
query ($projectId: String!) {
|
||||
projectMembers(projectId: $projectId) {
|
||||
|
@ -140,6 +140,10 @@ export type GetDeploymentsResponse = {
|
||||
deployments: Deployment[]
|
||||
}
|
||||
|
||||
export type GetEnvironmentVariablesResponse = {
|
||||
environmentVariables: EnvironmentVariable[]
|
||||
}
|
||||
|
||||
export type GetOrganizationsResponse = {
|
||||
organizations: Organization[]
|
||||
}
|
||||
@ -151,3 +155,13 @@ export type GetUserResponse = {
|
||||
export type SearchProjectsResponse = {
|
||||
searchProjects: Project[]
|
||||
}
|
||||
|
||||
export type AddEnvironmentVariablesResponse = {
|
||||
addEnvironmentVariables: boolean;
|
||||
}
|
||||
|
||||
export type AddEnvironmentVariableInput = {
|
||||
environments: string[];
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user