mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-24 17:51:14 +02:00
chore: use paginated table for change request list (#10660)
Adds a paginated table to the change request overview page and integrates it with the search API hook. The current implementation still has some rough edges to work out, but it's getting closer. There's no sort buttons in this implementation. I've got it working on the side, but TS is complaining about types not matching up, so I'm spinning that out to a separate PR. <img width="1808" height="1400" alt="image" src="https://github.com/user-attachments/assets/bdee97b7-ee2a-46c0-8460-a8b8e14d3c92" />
This commit is contained in:
parent
c824b3e26b
commit
4dd97b97f4
@ -1,5 +1,4 @@
|
|||||||
import type { VFC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ChangeRequestType } from '../changeRequest.types';
|
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
import AccessTime from '@mui/icons-material/AccessTime';
|
import AccessTime from '@mui/icons-material/AccessTime';
|
||||||
import Check from '@mui/icons-material/Check';
|
import Check from '@mui/icons-material/Check';
|
||||||
@ -9,20 +8,27 @@ import ErrorIcon from '@mui/icons-material/Error';
|
|||||||
import PauseCircle from '@mui/icons-material/PauseCircle';
|
import PauseCircle from '@mui/icons-material/PauseCircle';
|
||||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import type {
|
||||||
|
ScheduledChangeRequest,
|
||||||
|
UnscheduledChangeRequest,
|
||||||
|
} from '../changeRequest.types';
|
||||||
|
|
||||||
interface IChangeRequestStatusBadgeProps {
|
export interface IChangeRequestStatusBadgeProps {
|
||||||
changeRequest: ChangeRequestType | undefined;
|
changeRequest:
|
||||||
|
| Pick<UnscheduledChangeRequest, 'state'>
|
||||||
|
| Pick<ScheduledChangeRequest, 'state' | 'schedule'>
|
||||||
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewRequiredBadge: VFC = () => (
|
const ReviewRequiredBadge: FC = () => (
|
||||||
<Badge color='secondary' icon={<CircleOutlined fontSize={'small'} />}>
|
<Badge color='secondary' icon={<CircleOutlined fontSize={'small'} />}>
|
||||||
Review required
|
Review required
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DraftBadge: VFC = () => <Badge color='warning'>Draft</Badge>;
|
const DraftBadge: FC = () => <Badge color='warning'>Draft</Badge>;
|
||||||
|
|
||||||
export const ChangeRequestStatusBadge: VFC<IChangeRequestStatusBadgeProps> = ({
|
export const ChangeRequestStatusBadge: FC<IChangeRequestStatusBadgeProps> = ({
|
||||||
changeRequest,
|
changeRequest,
|
||||||
}) => {
|
}) => {
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
|
@ -1,285 +1,174 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import {
|
import { PaginatedTable } from 'component/common/Table';
|
||||||
SortableTableHeader,
|
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
||||||
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 { 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 { ChangeRequestStatusCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestStatusCell';
|
||||||
import { AvatarCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/AvatarCell';
|
import { AvatarCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/AvatarCell';
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||||
import { GlobalChangeRequestTitleCell } from './GlobalChangeRequestTitleCell.js';
|
import { GlobalChangeRequestTitleCell } from './GlobalChangeRequestTitleCell.js';
|
||||||
import { FeaturesCell } from '../ProjectChangeRequests/ChangeRequestsTabs/FeaturesCell.js';
|
import { FeaturesCell } from '../ProjectChangeRequests/ChangeRequestsTabs/FeaturesCell.js';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag.js';
|
import { useUiFlag } from 'hooks/useUiFlag.js';
|
||||||
|
import { withTableState } from 'utils/withTableState';
|
||||||
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
|
import {
|
||||||
|
useChangeRequestSearch,
|
||||||
|
DEFAULT_PAGE_LIMIT,
|
||||||
|
type SearchChangeRequestsInput,
|
||||||
|
} from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch';
|
||||||
|
import type { ChangeRequestSearchItemSchema } from 'openapi';
|
||||||
|
import {
|
||||||
|
NumberParam,
|
||||||
|
StringParam,
|
||||||
|
withDefault,
|
||||||
|
useQueryParams,
|
||||||
|
encodeQueryParams,
|
||||||
|
} from 'use-query-params';
|
||||||
|
import useLoading from 'hooks/useLoading';
|
||||||
|
import { styles as themeStyles } from 'component/common';
|
||||||
|
import { FilterItemParam } from 'utils/serializeQueryParams';
|
||||||
|
|
||||||
// Mock data with varied projects and change requests
|
const columnHelper = createColumnHelper<ChangeRequestSearchItemSchema>();
|
||||||
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 ChangeRequestsInner = () => {
|
||||||
const loading = false;
|
const { user } = useAuthUser();
|
||||||
|
|
||||||
|
const shouldApplyDefaults = useMemo(() => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return (
|
||||||
|
!urlParams.has('createdBy') &&
|
||||||
|
!urlParams.has('requestedApproverId') &&
|
||||||
|
user
|
||||||
|
);
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const stateConfig = {
|
||||||
|
offset: withDefault(NumberParam, 0),
|
||||||
|
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
||||||
|
sortBy: withDefault(StringParam, 'createdAt'),
|
||||||
|
sortOrder: withDefault(StringParam, 'desc'),
|
||||||
|
createdBy: FilterItemParam,
|
||||||
|
requestedApproverId: FilterItemParam,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState = shouldApplyDefaults
|
||||||
|
? {
|
||||||
|
createdBy: {
|
||||||
|
operator: 'IS' as const,
|
||||||
|
values: user ? [user.id.toString()] : [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const [tableState, setTableState] = useQueryParams(stateConfig, {
|
||||||
|
updateType: 'replaceIn',
|
||||||
|
});
|
||||||
|
|
||||||
|
const effectiveTableState = useMemo(
|
||||||
|
() => ({
|
||||||
|
...initialState,
|
||||||
|
...tableState,
|
||||||
|
}),
|
||||||
|
[initialState, tableState],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
changeRequests: data,
|
||||||
|
total,
|
||||||
|
loading,
|
||||||
|
} = useChangeRequestSearch(
|
||||||
|
encodeQueryParams(
|
||||||
|
stateConfig,
|
||||||
|
effectiveTableState,
|
||||||
|
) as SearchChangeRequestsInput,
|
||||||
|
);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
columnHelper.accessor('title', {
|
||||||
id: 'Title',
|
id: 'Title',
|
||||||
Header: 'Title',
|
header: 'Title',
|
||||||
// todo (globalChangeRequestList): sort out width calculation. It's configured both here with a min width down in the inner cell?
|
meta: { width: '300px' },
|
||||||
width: 300,
|
cell: ({ getValue, row }) => (
|
||||||
canSort: true,
|
<GlobalChangeRequestTitleCell
|
||||||
accessor: 'title',
|
value={getValue()}
|
||||||
Cell: GlobalChangeRequestTitleCell,
|
row={row}
|
||||||
},
|
/>
|
||||||
{
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('features', {
|
||||||
id: 'Updated feature flags',
|
id: 'Updated feature flags',
|
||||||
Header: 'Updated feature flags',
|
header: 'Updated feature flags',
|
||||||
canSort: false,
|
enableSorting: false,
|
||||||
accessor: 'features',
|
cell: ({
|
||||||
searchable: true,
|
getValue,
|
||||||
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: {
|
row: {
|
||||||
original: { title, project },
|
original: { title, project },
|
||||||
},
|
},
|
||||||
}: any) => (
|
}) => {
|
||||||
<FeaturesCell project={project} value={value} key={title} />
|
const features = getValue();
|
||||||
|
const featureObjects = features.map((name: string) => ({
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<FeaturesCell
|
||||||
|
project={project}
|
||||||
|
value={featureObjects}
|
||||||
|
key={title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('createdBy', {
|
||||||
|
id: 'By',
|
||||||
|
header: 'By',
|
||||||
|
meta: { width: '180px', align: 'left' },
|
||||||
|
enableSorting: false,
|
||||||
|
cell: ({ getValue }) => <AvatarCell value={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('createdAt', {
|
||||||
|
id: 'Submitted',
|
||||||
|
header: 'Submitted',
|
||||||
|
meta: { width: '100px' },
|
||||||
|
cell: ({ getValue }) => <TimeAgoCell value={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('environment', {
|
||||||
|
id: 'Environment',
|
||||||
|
header: 'Environment',
|
||||||
|
meta: { width: '100px' },
|
||||||
|
cell: ({ getValue }) => <HighlightCell value={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('state', {
|
||||||
|
id: 'Status',
|
||||||
|
header: 'Status',
|
||||||
|
meta: { width: '170px' },
|
||||||
|
cell: ({ getValue, row }) => (
|
||||||
|
// @ts-expect-error (`globalChangeRequestList`) The schema (and query) needs to be updated
|
||||||
|
<ChangeRequestStatusCell value={getValue()} row={row} />
|
||||||
),
|
),
|
||||||
},
|
}),
|
||||||
{
|
|
||||||
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 } =
|
const table = useReactTable(
|
||||||
useTable(
|
withTableState(effectiveTableState, setTableState, {
|
||||||
{
|
columns,
|
||||||
columns: columns as any[],
|
data,
|
||||||
data: mockChangeRequests,
|
}),
|
||||||
initialState: {
|
|
||||||
sortBy: [
|
|
||||||
{
|
|
||||||
id: 'createdAt',
|
|
||||||
desc: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
sortTypes,
|
|
||||||
autoResetHiddenColumns: false,
|
|
||||||
disableSortRemove: true,
|
|
||||||
autoResetSortBy: false,
|
|
||||||
defaultColumn: {
|
|
||||||
Cell: TextCell,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
useSortBy,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bodyLoadingRef = useLoading(loading);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent
|
<PageContent
|
||||||
isLoading={loading}
|
bodyClass='no-padding'
|
||||||
header={<PageHeader title='Change requests' />}
|
header={<PageHeader title='Change requests' />}
|
||||||
>
|
>
|
||||||
<Table {...getTableProps()}>
|
<div className={themeStyles.fullwidth} ref={bodyLoadingRef}>
|
||||||
<SortableTableHeader headerGroups={headerGroups} />
|
<PaginatedTable tableInstance={table} totalItems={total} />
|
||||||
<TableBody {...getTableBodyProps()}>
|
</div>
|
||||||
{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>
|
</PageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import { Link, styled, Typography } from '@mui/material';
|
import { Link, styled, Typography } from '@mui/material';
|
||||||
import { Link as RouterLink, type LinkProps } from 'react-router-dom';
|
import { Link as RouterLink, type LinkProps } from 'react-router-dom';
|
||||||
|
import { useProjectOverviewNameOrId } from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||||
|
|
||||||
type IGlobalChangeRequestTitleCellProps = {
|
type IGlobalChangeRequestTitleCellProps = {
|
||||||
value?: any;
|
value?: any;
|
||||||
@ -41,12 +42,12 @@ export const GlobalChangeRequestTitleCell = ({
|
|||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
project,
|
project,
|
||||||
projectName,
|
|
||||||
features: featureChanges,
|
features: featureChanges,
|
||||||
segments: segmentChanges,
|
segments: segmentChanges,
|
||||||
} = original;
|
} = original;
|
||||||
|
const projectName = useProjectOverviewNameOrId(project);
|
||||||
const totalChanges =
|
const totalChanges =
|
||||||
(featureChanges || []).length + (segmentChanges || []).length;
|
featureChanges?.length ?? 0 + segmentChanges?.length ?? 0;
|
||||||
const projectPath = `/projects/${project}`;
|
const projectPath = `/projects/${project}`;
|
||||||
const crPath = `${projectPath}/change-requests/${id}`;
|
const crPath = `${projectPath}/change-requests/${id}`;
|
||||||
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import type { VFC } from 'react';
|
|
||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
import {
|
||||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
ChangeRequestStatusBadge,
|
||||||
|
type IChangeRequestStatusBadgeProps,
|
||||||
|
} from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface IChangeRequestStatusCellProps {
|
interface IChangeRequestStatusCellProps {
|
||||||
value?: string | null; // FIXME: proper type
|
value?: string | null; // FIXME: proper type
|
||||||
row: { original: ChangeRequestType };
|
row: {
|
||||||
|
original: IChangeRequestStatusBadgeProps['changeRequest'];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
|
export const ChangeRequestStatusCell: FC<IChangeRequestStatusCellProps> = ({
|
||||||
value,
|
value,
|
||||||
row: { original },
|
row: { original },
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -31,6 +31,12 @@ const fallbackData: ChangeRequestSearchResponseSchema = {
|
|||||||
const SWR_CACHE_SIZE = 10;
|
const SWR_CACHE_SIZE = 10;
|
||||||
const PATH = 'api/admin/search/change-requests?';
|
const PATH = 'api/admin/search/change-requests?';
|
||||||
|
|
||||||
|
export type SearchChangeRequestsInput = {
|
||||||
|
[K in keyof SearchChangeRequestsParams]?:
|
||||||
|
| SearchChangeRequestsParams[K]
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
|
||||||
const createChangeRequestSearch = () => {
|
const createChangeRequestSearch = () => {
|
||||||
const internalCache: InternalCache = {};
|
const internalCache: InternalCache = {};
|
||||||
|
|
||||||
@ -56,7 +62,7 @@ const createChangeRequestSearch = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
params: SearchChangeRequestsParams,
|
params: SearchChangeRequestsInput,
|
||||||
options: SWRConfiguration = {},
|
options: SWRConfiguration = {},
|
||||||
cachePrefix: string = '',
|
cachePrefix: string = '',
|
||||||
): UseChangeRequestSearchOutput => {
|
): UseChangeRequestSearchOutput => {
|
||||||
@ -100,11 +106,13 @@ const createChangeRequestSearch = () => {
|
|||||||
|
|
||||||
export const DEFAULT_PAGE_LIMIT = 25;
|
export const DEFAULT_PAGE_LIMIT = 25;
|
||||||
|
|
||||||
const getChangeRequestSearchFetcher = (params: SearchChangeRequestsParams) => {
|
const getChangeRequestSearchFetcher = (params: SearchChangeRequestsInput) => {
|
||||||
const urlSearchParams = new URLSearchParams(
|
const urlSearchParams = new URLSearchParams(
|
||||||
Array.from(
|
Array.from(
|
||||||
Object.entries(params)
|
Object.entries(params)
|
||||||
.filter(([_, value]) => !!value)
|
.filter(
|
||||||
|
(param): param is [string, string | number] => !!param[1],
|
||||||
|
)
|
||||||
.map(([key, value]) => [key, value.toString()]),
|
.map(([key, value]) => [key, value.toString()]),
|
||||||
),
|
),
|
||||||
).toString();
|
).toString();
|
||||||
|
Loading…
Reference in New Issue
Block a user