1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: project change request page frontend (#2361)

- Refactor project menu - merge "Access" and "Environments" with newly
added "Change request configuration" into "Project settings" tab.
- Add Change request config page with table
- Add Change request dialog

Closes https://linear.app/unleash/issue/1-344/frontend-project-options

Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item:
#2251
This commit is contained in:
Tymoteusz Czech 2022-11-10 10:46:23 +01:00 committed by GitHub
parent 0649262c70
commit 45ee135037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 246 additions and 11 deletions

View File

@ -29,6 +29,7 @@ import { DraftBanner } from 'component/changeRequest/DraftBanner/DraftBanner';
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests/ProjectChangeRequests';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { ProjectSettings } from './ProjectSettings/ProjectSettings';
const StyledDiv = styled('div')(() => ({
display: 'flex',
@ -58,7 +59,7 @@ const Project = () => {
const { classes: styles } = useStyles();
const navigate = useNavigate();
const { pathname } = useLocation();
const { isOss } = useUiConfig();
const { isOss, uiConfig } = useUiConfig();
const basePath = `/projects/${projectId}`;
const projectName = project?.name || projectId;
@ -78,21 +79,34 @@ const Project = () => {
path: `${basePath}/health`,
name: 'health',
},
{
title: 'Access',
path: `${basePath}/access`,
name: 'access',
},
{
title: 'Environments',
path: `${basePath}/environments`,
name: 'environments',
},
...(!uiConfig?.flags?.changeRequests
? [
{
title: 'Access',
path: `${basePath}/access`,
name: 'access',
},
{
title: 'Environments',
path: `${basePath}/environments`,
name: 'environments',
},
]
: []),
{
title: 'Archive',
path: `${basePath}/archive`,
name: 'archive',
},
...(uiConfig?.flags?.changeRequests
? [
{
title: 'Project settings',
path: `${basePath}/settings`,
name: 'settings',
},
]
: []),
{
title: 'Event log',
path: `${basePath}/logs`,
@ -263,6 +277,7 @@ const Project = () => {
/>
}
/>
<Route path="settings/*" element={<ProjectSettings />} />
<Route path="*" element={<ProjectOverview />} />
</Routes>
</MainLayout>

View File

@ -0,0 +1,166 @@
import { useMemo, useState, VFC } from 'react';
import { HeaderGroup, Row } from 'react-table';
import { Alert, Box, Typography } from '@mui/material';
import {
SortableTableHeader,
Table,
TableCell,
TableBody,
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';
export const ChangeRequestConfiguration: VFC = () => {
const [dialogState, setDialogState] = useState<{
isOpen: boolean;
enableEnvironment?: string;
}>({
isOpen: false,
enableEnvironment: '',
});
const projectId = useRequiredPathParam('projectId');
const data = [
{
environment: 'dev',
type: 'test',
isEnabled: false,
},
] as any[]; // FIXME: type
const onClick = (enableEnvironment: string) => () => {
setDialogState({ isOpen: true, enableEnvironment });
};
const columns = useMemo(
() => [
{
Header: 'Environment',
accessor: 'environment',
disableSortBy: true,
},
{
Header: 'Type',
accessor: 'type',
disableGlobalFilter: true,
disableSortBy: true,
},
{
Header: 'Status',
accessor: 'isEnabled',
align: 'center',
Cell: ({ value, row: { original } }: any) => (
<Box
sx={{ display: 'flex', justifyContent: 'center' }}
data-loading
>
<PermissionSwitch
checked={value}
environmentId={original.environment}
projectId={projectId}
permission={UPDATE_FEATURE_ENVIRONMENT} // FIXME: permission - enable change request
inputProps={{ 'aria-label': original.environment }}
onClick={onClick(original.environment)}
/>
</Box>
),
width: 100,
disableGlobalFilter: true,
disableSortBy: true,
},
],
[]
);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable(
{
columns,
data,
sortTypes,
autoResetGlobalFilter: false,
disableSortRemove: true,
defaultColumn: {
Cell: TextCell,
},
},
useGlobalFilter
);
return (
<PageContent
header={<PageHeader titleElement="Change request configuration" />}
// 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={() => {
alert('clicked');
/* FIXME: API action */
}}
open={dialogState.isOpen}
onClose={() =>
setDialogState(state => ({ ...state, isOpen: false }))
}
primaryButtonText="Enable"
secondaryButtonText="Cancel"
title="Enable change request"
>
<Typography sx={{ mb: 1 }}>
You are about to enable Change request
<ConditionallyRender
condition={Boolean(dialogState.enableEnvironment)}
show={
<>
{' '}
for{' '}
<strong>{dialogState.enableEnvironment}</strong>
</>
}
/>
.
</Typography>
<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>
);
};

View File

@ -0,0 +1,54 @@
import {
Route,
Routes,
useLocation,
useNavigate,
Navigate,
} from 'react-router-dom';
import { ITab, VerticalTabs } from 'component/common/VerticalTabs/VerticalTabs';
import { ProjectAccess } from 'component/project/ProjectAccess/ProjectAccess';
import ProjectEnvironmentList from 'component/project/ProjectEnvironment/ProjectEnvironment';
import { ChangeRequestConfiguration } from './ChangeRequestConfiguration/ChangeRequestConfiguration';
export const ProjectSettings = () => {
const location = useLocation();
const navigate = useNavigate();
const tabs = [
{ id: 'access', label: 'Access' },
{ id: 'environments', label: 'Environments' },
{ id: 'change-requests', label: 'Change request configuration' },
];
const onChange = (tab: ITab) => {
navigate(tab.id);
};
return (
<VerticalTabs
tabs={tabs}
value={
tabs.find(
({ id }) => id && location.pathname?.includes(`/${id}`)
)?.id || tabs[0].id
}
onChange={onChange}
>
<Routes>
<Route path={`${tabs[0].id}/*`} element={<ProjectAccess />} />
<Route
path={`${tabs[1].id}/*`}
element={<ProjectEnvironmentList />}
/>
<Route
path={`${tabs[2].id}/*`}
element={<ChangeRequestConfiguration />}
/>
<Route
path="*"
element={<Navigate replace to={tabs[0].id} />}
/>
</Routes>
</VerticalTabs>
);
};