1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-13 11:17:26 +02:00
unleash.unleash/frontend/src/component/changeRequest/ChangeRequests/ChangeRequests.tsx
Thomas Heartman fabf76e12c
feat: global change requests table (#10650)
Adds basic table layout for the global change requests page and makes
the page accessible at `/change-requests`.

The table is based on the project-based change request table, but with a
slightly different set of columns.

Uses mock data for now. 

There's still some styling to be done for the column widths and handling
narrower screens.

<img width="1386" height="671" alt="image"
src="https://github.com/user-attachments/assets/b24ed625-d3f6-4281-ba44-30744d5063f3"
/>

If the flag is disabled, we render nothing useful.
<img width="1429" height="287" alt="image"
src="https://github.com/user-attachments/assets/289b5707-4389-4c08-bf68-55d63e186ba5"
/>


closes 1-4076
2025-09-11 10:15:57 +03:00

298 lines
10 KiB
TypeScript

import { useMemo } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import {
SortableTableHeader,
Table,
TableBody,
TableCell,
TableRow,
} from 'component/common/Table';
import { useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { ChangeRequestStatusCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestStatusCell';
import { AvatarCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/AvatarCell';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { GlobalChangeRequestTitleCell } from './GlobalChangeRequestTitleCell.js';
import { FeaturesCell } from '../ProjectChangeRequests/ChangeRequestsTabs/FeaturesCell.js';
import { useUiFlag } from 'hooks/useUiFlag.js';
// Mock data with varied projects and change requests
const mockChangeRequests = [
{
id: 101,
title: 'Activate harpoons',
project: 'payment-service',
projectName: 'Payment Service',
features: [{ name: 'securePaymentFlow' }],
segments: [],
createdBy: { username: 'alice', name: 'Alice Johnson', imageUrl: null },
createdAt: '2024-01-10T10:22:00Z',
environment: 'Production',
state: 'Review required',
},
{
id: 102,
title: 'change request #102',
project: 'user-management',
projectName: 'User Management',
features: [{ name: 'enhancedValidation' }],
segments: [],
createdBy: { username: 'bob', name: 'Bob Smith', imageUrl: null },
createdAt: '2024-01-10T08:15:00Z',
environment: 'Production',
state: 'Approved',
},
{
id: 103,
title: 'Enable new checkout flow',
project: 'e-commerce-platform',
projectName: 'E-commerce Platform',
features: [{ name: 'newCheckoutUX' }, { name: 'paymentOptionsV2' }],
segments: [],
createdBy: { username: 'carol', name: 'Carol Davis', imageUrl: null },
createdAt: '2024-01-10T12:30:00Z',
environment: 'Testing',
state: 'Review required',
},
{
id: 104,
title: 'Update user permissions',
project: 'user-management',
projectName: 'User Management',
features: [
{ name: 'roleBasedAccess' },
{ name: 'permissionMatrix' },
{ name: 'adminDashboard' },
],
segments: [],
createdBy: { username: 'david', name: 'David Wilson', imageUrl: null },
createdAt: '2024-01-09T16:45:00Z',
environment: 'Sandbox',
state: 'Review required',
},
{
id: 105,
title: 'Deploy feature rollback',
project: 'analytics-platform',
projectName: 'Analytics Platform',
features: [
{ name: 'performanceTracking' },
{ name: 'realTimeAnalytics' },
{ name: 'customDashboards' },
{ name: 'dataExport' },
],
segments: [],
createdBy: { username: 'eve', name: 'Eve Brown', imageUrl: null },
createdAt: '2024-01-09T14:20:00Z',
environment: 'Sandbox',
state: 'Scheduled',
schedule: {
scheduledAt: '2024-01-12T09:46:51+05:30',
status: 'pending',
},
},
{
id: 106,
title: 'change request #106',
project: 'notification-service',
projectName: 'Notification Service',
features: [{ name: 'emailTemplates' }],
segments: [],
createdBy: { username: 'frank', name: 'Frank Miller', imageUrl: null },
createdAt: '2024-01-08T11:00:00Z',
environment: 'Testing',
state: 'Approved',
},
{
id: 107,
title: 'Optimize database queries',
project: 'data-warehouse',
projectName: 'Data Warehouse',
features: [{ name: 'queryOptimization' }],
segments: [],
createdBy: { username: 'grace', name: 'Grace Lee', imageUrl: null },
createdAt: '2024-01-08T09:30:00Z',
environment: 'Testing',
state: 'Approved',
},
{
id: 108,
title: 'change request #108',
project: 'mobile-app',
projectName: 'Mobile App',
features: [{ name: 'pushNotifications' }],
segments: [],
createdBy: { username: 'henry', name: 'Henry Chen', imageUrl: null },
createdAt: '2024-01-07T15:20:00Z',
environment: 'Production',
state: 'Approved',
},
{
id: 109,
title: 'Archive legacy features',
project: 'payment-service',
projectName: 'Payment Service',
features: [{ name: 'legacyPaymentGateway' }],
segments: [],
createdBy: { username: 'alice', name: 'Alice Johnson', imageUrl: null },
createdAt: '2024-01-07T13:10:00Z',
environment: 'Production',
state: 'Scheduled',
schedule: {
scheduledAt: '2024-01-12T09:46:51+05:30',
status: 'failed',
reason: 'Mr Freeze',
},
},
];
const ChangeRequestsInner = () => {
const loading = false;
const columns = useMemo(
() => [
{
id: 'Title',
Header: 'Title',
// todo (globalChangeRequestList): sort out width calculation. It's configured both here with a min width down in the inner cell?
width: 300,
canSort: true,
accessor: 'title',
Cell: GlobalChangeRequestTitleCell,
},
{
id: 'Updated feature flags',
Header: 'Updated feature flags',
canSort: false,
accessor: 'features',
searchable: true,
filterName: 'feature',
filterParsing: (values: Array<{ name: string }>) => {
return values?.map(({ name }) => name).join('\n') || '';
},
filterBy: (
row: { features: Array<{ name: string }> },
values: Array<string>,
) => {
return row.features.find((feature) =>
values
.map((value) => value.toLowerCase())
.includes(feature.name.toLowerCase()),
);
},
Cell: ({
value,
row: {
original: { title, project },
},
}: any) => (
<FeaturesCell project={project} value={value} key={title} />
),
},
{
Header: 'By',
accessor: 'createdBy',
maxWidth: 180,
canSort: false,
Cell: AvatarCell,
align: 'left',
searchable: true,
filterName: 'by',
filterParsing: (value: { username?: string }) =>
value?.username || '',
},
{
Header: 'Submitted',
accessor: 'createdAt',
maxWidth: 100,
Cell: TimeAgoCell,
},
{
Header: 'Environment',
accessor: 'environment',
searchable: true,
maxWidth: 100,
Cell: HighlightCell,
filterName: 'environment',
},
{
Header: 'Status',
accessor: 'state',
searchable: true,
maxWidth: '170px',
Cell: ChangeRequestStatusCell,
filterName: 'status',
},
],
[],
);
const { headerGroups, rows, prepareRow, getTableProps, getTableBodyProps } =
useTable(
{
columns: columns as any[],
data: mockChangeRequests,
initialState: {
sortBy: [
{
id: 'createdAt',
desc: true,
},
],
},
sortTypes,
autoResetHiddenColumns: false,
disableSortRemove: true,
autoResetSortBy: false,
defaultColumn: {
Cell: TextCell,
},
},
useSortBy,
);
return (
<PageContent
isLoading={loading}
header={<PageHeader title='Change requests' />}
>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<TableBody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
const { key, ...rowProps } = row.getRowProps();
return (
<TableRow hover key={key} {...rowProps}>
{row.cells.map((cell) => {
const { key, ...cellProps } =
cell.getCellProps();
return (
<TableCell key={key} {...cellProps}>
{cell.render('Cell')}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</PageContent>
);
};
export const ChangeRequests = () => {
if (!useUiFlag('globalChangeRequestList')) {
return (
<PageContent header={<PageHeader title='Change requests' />}>
<p>Nothing to see here. Move along.</p>
</PageContent>
);
}
return <ChangeRequestsInner />;
};