import React, { useContext, useMemo, useState, VFC } from 'react'; import { HeaderGroup, useGlobalFilter, useTable } from 'react-table'; import { Alert, Box, styled, Typography } from '@mui/material'; import { SortableTableHeader, Table, TableBody, TableCell, TableRow, } from 'component/common/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 { 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 { IChangeRequestConfig, useChangeRequestApi, } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { UPDATE_PROJECT } from '@server/types/permissions'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { ChangeRequestProcessHelp } from './ChangeRequestProcessHelp/ChangeRequestProcessHelp'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import { KeyboardArrowDownOutlined } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import AccessContext from 'contexts/AccessContext'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; const StyledBox = styled(Box)(({ theme }) => ({ padding: theme.spacing(1), display: 'flex', justifyContent: 'center', '& .MuiInputBase-input': { fontSize: theme.fontSizes.smallBody, }, })); export const ChangeRequestTable: VFC = () => { const { trackEvent } = usePlausibleTracker(); const [dialogState, setDialogState] = useState<{ isOpen: boolean; enableEnvironment: string; isEnabled: boolean; requiredApprovals: number; }>({ isOpen: false, enableEnvironment: '', isEnabled: false, requiredApprovals: 1, }); const theme = useTheme(); const projectId = useRequiredPathParam('projectId'); const { data, loading, refetchChangeRequestConfig } = useChangeRequestConfig(projectId); const { updateChangeRequestEnvironmentConfig } = useChangeRequestApi(); const { setToastData, setToastApiError } = useToast(); const onRowChange = ( enableEnvironment: string, isEnabled: boolean, requiredApprovals: number ) => () => { setDialogState({ isOpen: true, enableEnvironment, isEnabled, requiredApprovals, }); }; const onConfirm = async () => { if (dialogState.enableEnvironment) { await updateConfiguration(); } setDialogState(state => ({ ...state, isOpen: false })); }; async function updateConfiguration(config?: IChangeRequestConfig) { try { await updateChangeRequestEnvironmentConfig( config || { project: projectId, environment: dialogState.enableEnvironment, enabled: !dialogState.isEnabled, requiredApprovals: dialogState.requiredApprovals, } ); setToastData({ type: 'success', title: 'Updated change request status', text: 'Successfully updated change request status.', }); await refetchChangeRequestConfig(); } catch (error) { setToastApiError(formatUnknownError(error)); } } const approvalOptions = Array.from(Array(10).keys()) .map(key => String(key + 1)) .map(key => { const labelText = key === '1' ? 'approval' : 'approvals'; return { key, label: `${key} ${labelText}`, sx: { 'font-size': theme.fontSizes.smallBody }, }; }); function onRequiredApprovalsChange(original: any, approvals: string) { updateConfiguration({ project: projectId, environment: original.environment, enabled: original.changeRequestEnabled, requiredApprovals: Number(approvals), }); } const columns = useMemo( () => [ { Header: 'Environment', accessor: 'environment', disableSortBy: true, }, { Header: 'Type', accessor: 'type', disableGlobalFilter: true, disableSortBy: true, }, { Header: 'Required approvals', Cell: ({ row: { original } }: any) => { const { hasAccess } = useContext(AccessContext); return ( { onRequiredApprovalsChange( original, approvals ); }} disabled={ !hasAccess( UPDATE_PROJECT, projectId ) } IconComponent={ KeyboardArrowDownOutlined } fullWidth /> } /> ); }, width: 100, disableGlobalFilter: true, disableSortBy: true, }, { Header: 'Status', accessor: 'changeRequestEnabled', id: 'changeRequestEnabled', align: 'center', Cell: ({ value, row: { original } }: any) => ( ), width: 100, disableGlobalFilter: true, disableSortBy: true, }, ], [] ); const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable( { // @ts-ignore columns, data, sortTypes, autoResetGlobalFilter: false, disableSortRemove: true, defaultColumn: { Cell: TextCell, }, }, useGlobalFilter ); return ( } /> } isLoading={loading} > If change request is enabled for an environment, then any change in that environment needs to be approved before it will be applied []} /> {rows.map(row => { prepareRow(row); return ( {row.cells.map(cell => ( {cell.render('Cell')} ))} ); })}
{ trackEvent('change_request', { props: { eventType: `change request ${ !dialogState.isEnabled ? 'enabled' : 'disabled' }`, }, }); onConfirm(); }} open={dialogState.isOpen} onClose={() => setDialogState(state => ({ ...state, isOpen: false })) } primaryButtonText={dialogState.isEnabled ? 'Disable' : 'Enable'} secondaryButtonText="Cancel" title={`${ dialogState.isEnabled ? 'Disable' : 'Enable' } change requests`} > You are about to{' '} {dialogState.isEnabled ? 'disable' : 'enable'} “Change request” {' '} for{' '} {dialogState.enableEnvironment} } /> . 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. } />
); };