diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 07ffc736..4841a320 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -58,6 +58,16 @@ export const createResolvers = async (db: Database): Promise => { 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); diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 87824458..afa676e4 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -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!] } diff --git a/packages/backend/test/fixtures/project-members.json b/packages/backend/test/fixtures/project-members.json index c4164b77..0a488052 100644 --- a/packages/backend/test/fixtures/project-members.json +++ b/packages/backend/test/fixtures/project-members.json @@ -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"] } ] diff --git a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx index 4645dfbe..53573c6a 100644 --- a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx +++ b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx @@ -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[]; } diff --git a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx index fadd91d3..9c58d670 100644 --- a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx +++ b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx @@ -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, diff --git a/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx b/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx index ce627570..00171cad 100644 --- a/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx +++ b/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx @@ -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(); - - 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 = () => { -
{ - toast.success( - data.variables.length > 1 - ? `${data.variables.length} variables added` - : `Variable added`, - ); - reset(); - })} - > + {fields.map((field, index) => { return ( {
diff --git a/packages/frontend/src/layouts/ProjectSearch.tsx b/packages/frontend/src/layouts/ProjectSearch.tsx index 6788319c..43945f4d 100644 --- a/packages/frontend/src/layouts/ProjectSearch.tsx +++ b/packages/frontend/src/layouts/ProjectSearch.tsx @@ -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: '', diff --git a/packages/frontend/src/types/project.ts b/packages/frontend/src/types/project.ts index 1db328a7..5870482a 100644 --- a/packages/frontend/src/types/project.ts +++ b/packages/frontend/src/types/project.ts @@ -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; diff --git a/packages/gql-client/src/client.ts b/packages/gql-client/src/client.ts index ad8f2b41..5f35fbad 100644 --- a/packages/gql-client/src/client.ts +++ b/packages/gql-client/src/client.ts @@ -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 { + const { data } = await this.client.query({ + query: getEnvironmentVariables, + variables: { + projectId + } + }); + + return data; + } + async removeMember (memberId: string): Promise { const { data } = await this.client.mutate({ mutation: removeMember, @@ -90,4 +101,16 @@ export class GQLClient { return data; } + + async addEnvironmentVariables (projectId: string, environmentVariables: AddEnvironmentVariableInput[]): Promise { + const { data } = await this.client.mutate({ + mutation: addEnvironmentVariables, + variables: { + projectId, + environmentVariables + } + }); + + return data; + } } diff --git a/packages/gql-client/src/mutations.ts b/packages/gql-client/src/mutations.ts index 4cca83c4..dd2dfe47 100644 --- a/packages/gql-client/src/mutations.ts +++ b/packages/gql-client/src/mutations.ts @@ -5,3 +5,9 @@ mutation ($memberId: String!) { removeMember(memberId: $memberId) } `; + +export const addEnvironmentVariables = gql` +mutation ($projectId: String!, $environmentVariables: [AddEnvironmentVariableInput!]) { + addEnvironmentVariables(projectId: $projectId, environmentVariables: $environmentVariables) +} +`; diff --git a/packages/gql-client/src/queries.ts b/packages/gql-client/src/queries.ts index a730d332..a076f0c7 100644 --- a/packages/gql-client/src/queries.ts +++ b/packages/gql-client/src/queries.ts @@ -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) { diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index 1cec546f..b7692afa 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -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; +}