mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-28 17:55:15 +02:00
314 lines
12 KiB
TypeScript
314 lines
12 KiB
TypeScript
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 (
|
|
<ConditionallyRender
|
|
condition={original.changeRequestEnabled}
|
|
show={
|
|
<StyledBox data-loading>
|
|
<GeneralSelect
|
|
sx={{ width: '140px', marginLeft: 1 }}
|
|
options={approvalOptions}
|
|
value={original.requiredApprovals || 1}
|
|
onChange={approvals => {
|
|
onRequiredApprovalsChange(
|
|
original,
|
|
approvals
|
|
);
|
|
}}
|
|
disabled={
|
|
!hasAccess(
|
|
UPDATE_PROJECT,
|
|
projectId
|
|
)
|
|
}
|
|
IconComponent={
|
|
KeyboardArrowDownOutlined
|
|
}
|
|
fullWidth
|
|
/>
|
|
</StyledBox>
|
|
}
|
|
/>
|
|
);
|
|
},
|
|
width: 100,
|
|
disableGlobalFilter: true,
|
|
disableSortBy: true,
|
|
},
|
|
{
|
|
Header: 'Status',
|
|
accessor: 'changeRequestEnabled',
|
|
id: 'changeRequestEnabled',
|
|
align: 'center',
|
|
|
|
Cell: ({ value, row: { original } }: any) => (
|
|
<StyledBox data-loading>
|
|
<PermissionSwitch
|
|
checked={value}
|
|
projectId={projectId}
|
|
permission={UPDATE_PROJECT}
|
|
inputProps={{ 'aria-label': original.environment }}
|
|
onClick={onRowChange(
|
|
original.environment,
|
|
original.changeRequestEnabled,
|
|
original.requiredApprovals
|
|
)}
|
|
/>
|
|
</StyledBox>
|
|
),
|
|
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 (
|
|
<PageContent
|
|
header={
|
|
<PageHeader
|
|
titleElement="Change request configuration"
|
|
actions={<ChangeRequestProcessHelp />}
|
|
/>
|
|
}
|
|
isLoading={loading}
|
|
>
|
|
<Alert severity="info" sx={{ mb: 3 }}>
|
|
If change request is enabled for an environment, then any change
|
|
in that environment needs to be approved before it will be
|
|
applied
|
|
</Alert>
|
|
|
|
<Table {...getTableProps()}>
|
|
<SortableTableHeader
|
|
headerGroups={headerGroups as HeaderGroup<object>[]}
|
|
/>
|
|
<TableBody {...getTableBodyProps()}>
|
|
{rows.map(row => {
|
|
prepareRow(row);
|
|
return (
|
|
<TableRow hover {...row.getRowProps()}>
|
|
{row.cells.map(cell => (
|
|
<TableCell {...cell.getCellProps()}>
|
|
{cell.render('Cell')}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
<Dialogue
|
|
onClick={() => {
|
|
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`}
|
|
>
|
|
<Typography sx={{ mb: 1 }}>
|
|
You are about to{' '}
|
|
{dialogState.isEnabled ? 'disable' : 'enable'} “Change
|
|
request”
|
|
<ConditionallyRender
|
|
condition={Boolean(dialogState.enableEnvironment)}
|
|
show={
|
|
<>
|
|
{' '}
|
|
for{' '}
|
|
<strong>{dialogState.enableEnvironment}</strong>
|
|
</>
|
|
}
|
|
/>
|
|
.
|
|
</Typography>
|
|
<ConditionallyRender
|
|
condition={!dialogState.isEnabled}
|
|
show={
|
|
<Typography variant="body2" color="text.secondary">
|
|
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.
|
|
</Typography>
|
|
}
|
|
/>
|
|
</Dialogue>
|
|
</PageContent>
|
|
);
|
|
};
|