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