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;
|
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 }) => {
|
projectMembers: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
const dbProjectMembers = await db.getProjectMembersByProjectId(projectId);
|
const dbProjectMembers = await db.getProjectMembersByProjectId(projectId);
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ type Query {
|
|||||||
organizations: [Organization!]
|
organizations: [Organization!]
|
||||||
projects: [Project!]
|
projects: [Project!]
|
||||||
deployments(projectId: String!): [Deployment!]
|
deployments(projectId: String!): [Deployment!]
|
||||||
|
environmentVariables(projectId: String!): [EnvironmentVariable!]
|
||||||
projectMembers(projectId: String!): [ProjectMember!]
|
projectMembers(projectId: String!): [ProjectMember!]
|
||||||
searchProjects(searchText: String!): [Project!]
|
searchProjects(searchText: String!): [Project!]
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,46 @@
|
|||||||
{
|
{
|
||||||
"memberIndex": 1,
|
"memberIndex": 1,
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"permissions": ["View", "Edit"]
|
"permissions": ["View"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 2,
|
"memberIndex": 2,
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"permissions": ["View", "Edit"]
|
"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 React, { useState } from 'react';
|
||||||
|
|
||||||
import { Card, Collapse, Typography } from '@material-tailwind/react';
|
import { Card, Collapse, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
|
import { Environment, EnvironmentVariable } from 'gql-client/dist/src/types';
|
||||||
|
|
||||||
import EditEnvironmentVariableRow from './EditEnvironmentVariableRow';
|
import EditEnvironmentVariableRow from './EditEnvironmentVariableRow';
|
||||||
import { Environments, EnvironmentVariable } from '../../../../types/project';
|
|
||||||
|
|
||||||
interface DisplayEnvironmentVariablesProps {
|
interface DisplayEnvironmentVariablesProps {
|
||||||
environment: Environments;
|
environment: Environment;
|
||||||
variables: EnvironmentVariable[];
|
variables: EnvironmentVariable[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
import { IconButton, Input, Typography } from '@material-tailwind/react';
|
import { IconButton, Input, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
|
import { EnvironmentVariable } from 'gql-client';
|
||||||
|
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import { EnvironmentVariable } from '../../../../types/project';
|
|
||||||
|
|
||||||
const ShowPasswordIcon = ({
|
const ShowPasswordIcon = ({
|
||||||
handler,
|
handler,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useOutletContext, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@ -12,14 +12,12 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
|
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||||
|
|
||||||
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow';
|
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow';
|
||||||
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
||||||
import {
|
|
||||||
EnvironmentVariable,
|
|
||||||
Environments,
|
|
||||||
ProjectSearchOutletContext,
|
|
||||||
} from '../../../../types/project';
|
|
||||||
import HorizontalLine from '../../../HorizontalLine';
|
import HorizontalLine from '../../../HorizontalLine';
|
||||||
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
export type EnvironmentVariablesFormValues = {
|
export type EnvironmentVariablesFormValues = {
|
||||||
variables: {
|
variables: {
|
||||||
@ -35,14 +33,11 @@ export type EnvironmentVariablesFormValues = {
|
|||||||
|
|
||||||
export const EnvironmentVariablesTabPanel = () => {
|
export const EnvironmentVariablesTabPanel = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const client = useGQLClient();
|
||||||
|
|
||||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
const [environmentVariables, setEnvironmentVariables] = useState<
|
||||||
|
EnvironmentVariable[]
|
||||||
const currentProject = useMemo(() => {
|
>([]);
|
||||||
return projects.find((project) => {
|
|
||||||
return project.id === id;
|
|
||||||
});
|
|
||||||
}, [id, projects]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -74,13 +69,16 @@ export const EnvironmentVariablesTabPanel = () => {
|
|||||||
if (isSubmitSuccessful) {
|
if (isSubmitSuccessful) {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
}, [isSubmitSuccessful, reset]);
|
}, [isSubmitSuccessful, reset, id]);
|
||||||
|
|
||||||
const getEnvironmentVariable = useCallback((environment: Environments) => {
|
const getEnvironmentVariable = useCallback(
|
||||||
return (
|
(environment: Environment) => {
|
||||||
currentProject?.environmentVariables as EnvironmentVariable[]
|
return environmentVariables.filter((item) =>
|
||||||
).filter((item) => item.environments.includes(environment));
|
item.environments.includes(environment),
|
||||||
}, []);
|
);
|
||||||
|
},
|
||||||
|
[environmentVariables, id],
|
||||||
|
);
|
||||||
|
|
||||||
const isFieldEmpty = useMemo(() => {
|
const isFieldEmpty = useMemo(() => {
|
||||||
if (errors.variables) {
|
if (errors.variables) {
|
||||||
@ -95,7 +93,55 @@ export const EnvironmentVariablesTabPanel = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -112,16 +158,7 @@ export const EnvironmentVariablesTabPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Collapse open={createNewVariable}>
|
<Collapse open={createNewVariable}>
|
||||||
<Card className="bg-white p-2">
|
<Card className="bg-white p-2">
|
||||||
<form
|
<form onSubmit={handleSubmit(createEnvironmentVariablesHandler)}>
|
||||||
onSubmit={handleSubmit((data) => {
|
|
||||||
toast.success(
|
|
||||||
data.variables.length > 1
|
|
||||||
? `${data.variables.length} variables added`
|
|
||||||
: `Variable added`,
|
|
||||||
);
|
|
||||||
reset();
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{fields.map((field, index) => {
|
{fields.map((field, index) => {
|
||||||
return (
|
return (
|
||||||
<AddEnvironmentVariableRow
|
<AddEnvironmentVariableRow
|
||||||
@ -189,18 +226,18 @@ export const EnvironmentVariablesTabPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<DisplayEnvironmentVariables
|
<DisplayEnvironmentVariables
|
||||||
environment={Environments.PRODUCTION}
|
environment={Environment.Production}
|
||||||
variables={getEnvironmentVariable(Environments.PRODUCTION)}
|
variables={getEnvironmentVariable(Environment.Production)}
|
||||||
/>
|
/>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<DisplayEnvironmentVariables
|
<DisplayEnvironmentVariables
|
||||||
environment={Environments.PREVIEW}
|
environment={Environment.Preview}
|
||||||
variables={getEnvironmentVariable(Environments.PREVIEW)}
|
variables={getEnvironmentVariable(Environment.Preview)}
|
||||||
/>
|
/>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<DisplayEnvironmentVariables
|
<DisplayEnvironmentVariables
|
||||||
environment={Environments.DEVELOPMENT}
|
environment={Environment.Development}
|
||||||
variables={getEnvironmentVariable(Environments.DEVELOPMENT)}
|
variables={getEnvironmentVariable(Environment.Development)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Outlet, useNavigate } from 'react-router-dom';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Environment } from 'gql-client';
|
||||||
|
|
||||||
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 { useGQLClient } from '../context/GQLClientContext';
|
||||||
import { Environments, ProjectDetails } from '../types/project';
|
import { ProjectDetails } from '../types/project';
|
||||||
|
|
||||||
const ProjectSearch = () => {
|
const ProjectSearch = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -25,7 +27,7 @@ const ProjectSearch = () => {
|
|||||||
const updatedDeployments = deployments.map((deployment: any) => {
|
const updatedDeployments = deployments.map((deployment: any) => {
|
||||||
return {
|
return {
|
||||||
...deployment,
|
...deployment,
|
||||||
isProduction: deployment.environment === Environments.PRODUCTION,
|
isProduction: deployment.environment === Environment.Production,
|
||||||
author: '',
|
author: '',
|
||||||
commit: {
|
commit: {
|
||||||
hash: '',
|
hash: '',
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||||
|
|
||||||
export interface ProjectDetails {
|
export interface ProjectDetails {
|
||||||
icon: string;
|
icon: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -36,7 +38,7 @@ export interface DeploymentDetails {
|
|||||||
domain: DomainDetails;
|
domain: DomainDetails;
|
||||||
status: Status;
|
status: Status;
|
||||||
branch: string;
|
branch: string;
|
||||||
environment: Environments;
|
environment: Environment;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
commit: {
|
commit: {
|
||||||
hash: string;
|
hash: string;
|
||||||
@ -52,19 +54,6 @@ export enum Status {
|
|||||||
ERROR = 'Error',
|
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 {
|
export interface RepositoryDetails {
|
||||||
title: string;
|
title: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
@ -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 } from './queries';
|
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables } from './queries';
|
||||||
import { GetDeploymentsResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse } from './types';
|
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse } from './types';
|
||||||
import { removeMember } from './mutations';
|
import { removeMember, addEnvironmentVariables } from './mutations';
|
||||||
|
|
||||||
export interface GraphQLConfig {
|
export interface GraphQLConfig {
|
||||||
gqlEndpoint: string;
|
gqlEndpoint: string;
|
||||||
@ -58,6 +58,17 @@ export class GQLClient {
|
|||||||
return data;
|
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> {
|
async removeMember (memberId: string): Promise<RemoveMemberResponse> {
|
||||||
const { data } = await this.client.mutate({
|
const { data } = await this.client.mutate({
|
||||||
mutation: removeMember,
|
mutation: removeMember,
|
||||||
@ -90,4 +101,16 @@ export class GQLClient {
|
|||||||
|
|
||||||
return data;
|
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)
|
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`
|
export const getProjectMembers = gql`
|
||||||
query ($projectId: String!) {
|
query ($projectId: String!) {
|
||||||
projectMembers(projectId: $projectId) {
|
projectMembers(projectId: $projectId) {
|
||||||
|
@ -140,6 +140,10 @@ export type GetDeploymentsResponse = {
|
|||||||
deployments: Deployment[]
|
deployments: Deployment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GetEnvironmentVariablesResponse = {
|
||||||
|
environmentVariables: EnvironmentVariable[]
|
||||||
|
}
|
||||||
|
|
||||||
export type GetOrganizationsResponse = {
|
export type GetOrganizationsResponse = {
|
||||||
organizations: Organization[]
|
organizations: Organization[]
|
||||||
}
|
}
|
||||||
@ -151,3 +155,13 @@ export type GetUserResponse = {
|
|||||||
export type SearchProjectsResponse = {
|
export type SearchProjectsResponse = {
|
||||||
searchProjects: Project[]
|
searchProjects: Project[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AddEnvironmentVariablesResponse = {
|
||||||
|
addEnvironmentVariables: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddEnvironmentVariableInput = {
|
||||||
|
environments: string[];
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user