diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts index 6ffb49eff4..89765d9106 100644 --- a/frontend/src/component/changeRequest/changeRequest.types.ts +++ b/frontend/src/component/changeRequest/changeRequest.types.ts @@ -12,6 +12,12 @@ export interface IChangeRequest { approvals: IChangeRequestApproval[]; } +export interface IChangeRequestEnvironmentConfig { + environment: string; + type: string; + changeRequestEnabled: boolean; +} + export interface IChangeRequestFeature { name: string; conflict?: string; diff --git a/frontend/src/component/project/Project/ProjectSettings/ChangeRequestConfiguration/ChangeRequestConfiguration.tsx b/frontend/src/component/project/Project/ProjectSettings/ChangeRequestConfiguration/ChangeRequestConfiguration.tsx index 80a8a0681c..be3f998455 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ChangeRequestConfiguration/ChangeRequestConfiguration.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ChangeRequestConfiguration/ChangeRequestConfiguration.tsx @@ -1,43 +1,71 @@ import { useMemo, useState, VFC } from 'react'; -import { HeaderGroup, Row } from 'react-table'; +import { HeaderGroup, useGlobalFilter, useTable } from 'react-table'; import { Alert, Box, Typography } from '@mui/material'; import { SortableTableHeader, Table, - TableCell, TableBody, + TableCell, TableRow, } from 'component/common/Table'; -import { useGlobalFilter, useTable } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch'; -import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useChangeRequestConfig } from '../../../../../hooks/api/getters/useChangeRequestConfig/useChangeRequestConfig'; +import { useChangeRequestApi } from '../../../../../hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; +import { UPDATE_PROJECT } from '@server/types/permissions'; +import useToast from '../../../../../hooks/useToast'; +import { formatUnknownError } from '../../../../../utils/formatUnknownError'; export const ChangeRequestConfiguration: VFC = () => { const [dialogState, setDialogState] = useState<{ isOpen: boolean; enableEnvironment?: string; + isEnabled: boolean; }>({ isOpen: false, enableEnvironment: '', + isEnabled: false, }); const projectId = useRequiredPathParam('projectId'); - const data = [ - { - environment: 'dev', - type: 'test', - isEnabled: false, - }, - ] as any[]; // FIXME: type + const { data, loading, refetchChangeRequestConfig } = + useChangeRequestConfig(projectId); + const { updateChangeRequestEnvironmentConfig } = useChangeRequestApi(); + const { setToastData, setToastApiError } = useToast(); - const onClick = (enableEnvironment: string) => () => { - setDialogState({ isOpen: true, enableEnvironment }); + const onClick = (enableEnvironment: string, isEnabled: boolean) => () => { + setDialogState({ isOpen: true, enableEnvironment, isEnabled }); + }; + + const onConfirm = async () => { + if (dialogState.enableEnvironment) { + try { + await updateChangeRequestEnvironmentConfig( + projectId, + dialogState.enableEnvironment, + !dialogState.isEnabled + ); + setToastData({ + type: 'success', + title: 'Updated change request status', + text: 'Successfully updated change request status.', + }); + refetchChangeRequestConfig(); + } catch (error) { + const message = formatUnknownError(error); + setToastApiError(message); + } + } + setDialogState({ + isOpen: false, + enableEnvironment: '', + isEnabled: false, + }); }; const columns = useMemo( @@ -55,7 +83,8 @@ export const ChangeRequestConfiguration: VFC = () => { }, { Header: 'Status', - accessor: 'isEnabled', + accessor: 'changeRequestEnabled', + id: 'changeRequestEnabled', align: 'center', Cell: ({ value, row: { original } }: any) => ( @@ -67,9 +96,12 @@ export const ChangeRequestConfiguration: VFC = () => { checked={value} environmentId={original.environment} projectId={projectId} - permission={UPDATE_FEATURE_ENVIRONMENT} // FIXME: permission - enable change request + permission={UPDATE_PROJECT} inputProps={{ 'aria-label': original.environment }} - onClick={onClick(original.environment)} + onClick={onClick( + original.environment, + original.changeRequestEnabled + )} /> ), @@ -84,6 +116,7 @@ export const ChangeRequestConfiguration: VFC = () => { const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable( { + // @ts-ignore columns, data, sortTypes, @@ -95,11 +128,10 @@ export const ChangeRequestConfiguration: VFC = () => { }, useGlobalFilter ); - return ( } - // isLoading={loading} + isLoading={loading} > If change request is enabled for an environment, then any change @@ -128,20 +160,21 @@ export const ChangeRequestConfiguration: VFC = () => { { - alert('clicked'); - /* FIXME: API action */ - }} + onClick={() => onConfirm()} open={dialogState.isOpen} onClose={() => setDialogState(state => ({ ...state, isOpen: false })) } - primaryButtonText="Enable" + primaryButtonText={dialogState.isEnabled ? 'Disable' : 'Enable'} secondaryButtonText="Cancel" - title="Enable change request" + title={`${ + dialogState.isEnabled ? 'Disable' : 'Enable' + } change requests`} > - You are about to enable “Change request” + You are about to{' '} + {dialogState.isEnabled ? 'disable' : 'enable'} “Change + request” { /> . - - When enabling change request for an environment, you need to - be sure that your Unleash Admin already have created the - custom project roles in your Unleash instance so you can - assign your project members from the project access page. - + + When enabling change request for an environment, you + need to be sure that your Unleash Admin already have + created the custom project roles in your Unleash + instance so you can assign your project members from + the project access page. + + } + /> ); diff --git a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts index 490683f95a..9c8d20ffd0 100644 --- a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts +++ b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts @@ -67,10 +67,28 @@ export const useChangeRequestApi = () => { } }; + const updateChangeRequestEnvironmentConfig = async ( + project: string, + environment: string, + enabled: boolean + ) => { + const path = `api/admin/projects/${project}/environments/${environment}/change-requests/config`; + const req = createRequest(path, { + method: 'PUT', + body: JSON.stringify({ changeRequestsEnabled: enabled }), + }); + try { + return await makeRequest(req.caller, req.id); + } catch (e) { + throw e; + } + }; + return { addChangeRequest, changeState, discardChangeRequestEvent, + updateChangeRequestEnvironmentConfig, errors, loading, }; diff --git a/frontend/src/hooks/api/getters/useChangeRequestConfig/useChangeRequestConfig.ts b/frontend/src/hooks/api/getters/useChangeRequestConfig/useChangeRequestConfig.ts new file mode 100644 index 0000000000..1d8f2580e1 --- /dev/null +++ b/frontend/src/hooks/api/getters/useChangeRequestConfig/useChangeRequestConfig.ts @@ -0,0 +1,24 @@ +import useSWR from 'swr'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { IChangeRequestEnvironmentConfig } from 'component/changeRequest/changeRequest.types'; + +export const useChangeRequestConfig = (projectId: string) => { + const { data, error, mutate } = useSWR( + formatApiPath(`api/admin/projects/${projectId}/change-requests/config`), + fetcher + ); + + return { + data: data || [], + loading: !error && !data, + refetchChangeRequestConfig: () => mutate(), + error, + }; +}; + +const fetcher = (path: string) => { + return fetch(path) + .then(handleErrorResponses('Request changes')) + .then(res => res.json()); +}; diff --git a/yarn.lock b/yarn.lock index 793527b2c8..f935f808c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2126,13 +2126,12 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" - concat-map "0.0.1" braces@^3.0.1, braces@^3.0.2: version "3.0.2" @@ -2505,11 +2504,6 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" @@ -5396,12 +5390,12 @@ min-indent@^1.0.1: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2, minimatch@^5.0.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.2, minimatch@^5.0.0, minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" minimist-options@4.1.0: version "4.1.0"