mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
refactor: use union types for change request types (#5870)
This changes the two interfaces IChangeRequest and IChangeRequestSchedule to be union types instead of interfaces. It also extracts the constituents of those union types into proper types themselves (so that they can be used in function type signatures etc). It also updates the type names. This turned out to be more work than I had imagined, but I think the end result pays off, giving us more type safety and control. I wanted to use just `ChangeRequest` for the IChangeRequest type, but that caused issues due to naming collisions with the `ChangeRequest` component that we have, causing tests to fail. I've named it `ChangeRequestType` as a potential solution, but suggestions are welcome. The relevant changes are in `frontend/src/component/changeRequest/changeRequest.types.ts`. Everything else is updated references and some necessary refactoring to respect the new types.
This commit is contained in:
parent
6ba4591c7f
commit
39145e2617
@ -5,7 +5,7 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { ChangeRequest } from './ChangeRequest';
|
||||
import {
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
IChangeRequestAddStrategy,
|
||||
IChangeRequestEnabled,
|
||||
} from '../changeRequest.types';
|
||||
@ -98,7 +98,7 @@ const feature = () =>
|
||||
const changeRequestWithDefaultChange = (
|
||||
defaultChange: IChangeRequestEnabled | IChangeRequestAddStrategy,
|
||||
) => {
|
||||
const changeRequest: IChangeRequest = {
|
||||
const changeRequest: ChangeRequestType = {
|
||||
approvals: [],
|
||||
rejections: [],
|
||||
comments: [],
|
||||
@ -142,7 +142,7 @@ const changeRequestWithDefaultChange = (
|
||||
};
|
||||
|
||||
const changeRequest = (variants: StrategyVariantSchema[]) => {
|
||||
const changeRequest: IChangeRequest = {
|
||||
const changeRequest: ChangeRequestType = {
|
||||
approvals: [],
|
||||
rejections: [],
|
||||
comments: [],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { VFC } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import type { IChangeRequest } from '../changeRequest.types';
|
||||
import type { ChangeRequestType } from '../changeRequest.types';
|
||||
import { FeatureToggleChanges } from './Changes/FeatureToggleChanges';
|
||||
import { FeatureChange } from './Changes/Change/FeatureChange';
|
||||
import { ChangeActions } from './Changes/Change/ChangeActions';
|
||||
@ -8,7 +8,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
||||
import { SegmentChange } from './Changes/Change/SegmentChange';
|
||||
|
||||
interface IChangeRequestProps {
|
||||
changeRequest: IChangeRequest;
|
||||
changeRequest: ChangeRequestType;
|
||||
onRefetch?: () => void;
|
||||
onNavigate?: () => void;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import {
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
IChangeRequestAddStrategy,
|
||||
IChangeRequestUpdateStrategy,
|
||||
IChange,
|
||||
@ -28,7 +28,7 @@ import { EditChange } from './EditChange';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { NewEditChange } from './NewEditChange';
|
||||
|
||||
const useShowActions = (changeRequest: IChangeRequest, change: IChange) => {
|
||||
const useShowActions = (changeRequest: ChangeRequestType, change: IChange) => {
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
||||
changeRequest.project,
|
||||
);
|
||||
@ -60,7 +60,7 @@ const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const ChangeActions: FC<{
|
||||
changeRequest: IChangeRequest;
|
||||
changeRequest: ChangeRequestType;
|
||||
feature: string;
|
||||
change: IChange;
|
||||
onRefetch?: () => void;
|
||||
|
@ -3,6 +3,7 @@ import { screen } from '@testing-library/react';
|
||||
import { FeatureChange } from './FeatureChange';
|
||||
import {
|
||||
ChangeRequestState,
|
||||
ChangeRequestType,
|
||||
IChangeRequestFeature,
|
||||
IFeatureChange,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
@ -40,21 +41,39 @@ describe('Schedule conflicts', () => {
|
||||
});
|
||||
|
||||
const changeRequest =
|
||||
(feature: IChangeRequestFeature) => (state: ChangeRequestState) => ({
|
||||
id: 1,
|
||||
state,
|
||||
title: '',
|
||||
project: 'default',
|
||||
environment: 'default',
|
||||
minApprovals: 1,
|
||||
createdBy: { id: 1, username: 'user1', imageUrl: '' },
|
||||
createdAt: new Date(),
|
||||
features: [feature],
|
||||
segments: [],
|
||||
approvals: [],
|
||||
rejections: [],
|
||||
comments: [],
|
||||
});
|
||||
(feature: IChangeRequestFeature) =>
|
||||
(state: ChangeRequestState): ChangeRequestType => {
|
||||
const shared = {
|
||||
id: 1,
|
||||
title: '',
|
||||
project: 'default',
|
||||
environment: 'default',
|
||||
minApprovals: 1,
|
||||
createdBy: { id: 1, username: 'user1', imageUrl: '' },
|
||||
createdAt: new Date(),
|
||||
features: [feature],
|
||||
segments: [],
|
||||
approvals: [],
|
||||
rejections: [],
|
||||
comments: [],
|
||||
};
|
||||
|
||||
if (state === 'Scheduled') {
|
||||
return {
|
||||
...shared,
|
||||
state,
|
||||
schedule: {
|
||||
scheduledAt: '2024-01-12T09:46:51+05:30',
|
||||
status: 'pending',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...shared,
|
||||
state,
|
||||
};
|
||||
};
|
||||
|
||||
it.each(['Draft', 'Scheduled', 'In review', 'Approved'])(
|
||||
'should show schedule conflicts (when they exist) for change request in the %s state',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
import {
|
||||
IFeatureChange,
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
IChangeRequestFeature,
|
||||
} from '../../../changeRequest.types';
|
||||
import { objectId } from 'utils/objectId';
|
||||
@ -69,7 +69,7 @@ const InlineList = styled('ul')(({ theme }) => ({
|
||||
export const FeatureChange: FC<{
|
||||
actions: ReactNode;
|
||||
index: number;
|
||||
changeRequest: IChangeRequest;
|
||||
changeRequest: ChangeRequestType;
|
||||
change: IFeatureChange;
|
||||
feature: IChangeRequestFeature;
|
||||
onNavigate?: () => void;
|
||||
|
@ -2,7 +2,7 @@ import { Box } from '@mui/material';
|
||||
import { FC, useState } from 'react';
|
||||
import { Typography, Tooltip } from '@mui/material';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
import {
|
||||
StyledPaper,
|
||||
@ -15,7 +15,7 @@ import { Separator } from '../../ChangeRequestSidebar/ChangeRequestSidebar';
|
||||
import { ChangeRequestTitle } from '../../ChangeRequestSidebar/EnvironmentChangeRequest/ChangeRequestTitle';
|
||||
import { UpdateCount } from 'component/changeRequest/UpdateCount';
|
||||
|
||||
export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
export const ChangeRequestHeader: FC<{ changeRequest: ChangeRequestType }> = ({
|
||||
changeRequest,
|
||||
}) => {
|
||||
const [title, setTitle] = useState(changeRequest.title);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import { ChangeRequestState, IChangeRequest } from '../changeRequest.types';
|
||||
import { ChangeRequestState, ChangeRequestType } from '../changeRequest.types';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { ChangeRequestOverview } from './ChangeRequestOverview';
|
||||
import {
|
||||
@ -14,11 +14,10 @@ const mockChangeRequest = (
|
||||
featureName: string,
|
||||
state: ChangeRequestState,
|
||||
failureReason?: string,
|
||||
): IChangeRequest => {
|
||||
const result: IChangeRequest = {
|
||||
): ChangeRequestType => {
|
||||
const shared: Omit<ChangeRequestType, 'state' | 'schedule'> = {
|
||||
id: 1,
|
||||
environment: 'production',
|
||||
state: state,
|
||||
minApprovals: 1,
|
||||
project: 'default',
|
||||
createdBy: {
|
||||
@ -60,26 +59,40 @@ const mockChangeRequest = (
|
||||
};
|
||||
|
||||
if (state === 'Scheduled') {
|
||||
result.schedule = {
|
||||
scheduledAt: '2022-12-02T09:19:12.242Z',
|
||||
status: 'pending',
|
||||
if (failureReason) {
|
||||
return {
|
||||
...shared,
|
||||
state,
|
||||
schedule: {
|
||||
scheduledAt: '2022-12-02T09:19:12.242Z',
|
||||
status: 'failed',
|
||||
reason: failureReason,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...shared,
|
||||
state,
|
||||
schedule: {
|
||||
scheduledAt: '2022-12-02T09:19:12.242Z',
|
||||
status: 'pending',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (failureReason) {
|
||||
result.schedule!.failureReason = failureReason;
|
||||
}
|
||||
|
||||
return result;
|
||||
return {
|
||||
...shared,
|
||||
state,
|
||||
};
|
||||
};
|
||||
const pendingChangeRequest = (changeRequest: IChangeRequest) =>
|
||||
const pendingChangeRequest = (changeRequest: ChangeRequestType) =>
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/projects/default/change-requests/pending',
|
||||
[changeRequest],
|
||||
);
|
||||
|
||||
const changeRequest = (changeRequest: IChangeRequest) =>
|
||||
const changeRequest = (changeRequest: ChangeRequestType) =>
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/projects/default/change-requests/1',
|
||||
|
@ -111,7 +111,9 @@ export const ChangeRequestOverview: FC = () => {
|
||||
changeRequest.environment,
|
||||
);
|
||||
|
||||
const hasSchedule = Boolean(changeRequest.schedule?.scheduledAt);
|
||||
const hasSchedule = Boolean(
|
||||
'schedule' in changeRequest && changeRequest.schedule?.scheduledAt,
|
||||
);
|
||||
|
||||
const onApplyChanges = async () => {
|
||||
try {
|
||||
@ -259,6 +261,30 @@ export const ChangeRequestOverview: FC = () => {
|
||||
|
||||
const countOfChanges = changesCount(changeRequest);
|
||||
|
||||
const reason = (() => {
|
||||
if (!('schedule' in changeRequest)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (changeRequest.schedule.status) {
|
||||
case 'failed':
|
||||
return (
|
||||
(changeRequest.schedule.reason ||
|
||||
changeRequest.schedule.failureReason) ??
|
||||
undefined
|
||||
);
|
||||
case 'suspended':
|
||||
return changeRequest.schedule.reason;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
const scheduledAt =
|
||||
'schedule' in changeRequest
|
||||
? changeRequest.schedule.scheduledAt
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeRequestHeader changeRequest={changeRequest} />
|
||||
@ -266,8 +292,12 @@ export const ChangeRequestOverview: FC = () => {
|
||||
<StyledAsideBox>
|
||||
<ChangeRequestTimeline
|
||||
state={changeRequest.state}
|
||||
scheduledAt={changeRequest.schedule?.scheduledAt}
|
||||
failureReason={changeRequest.schedule?.failureReason}
|
||||
scheduledAt={
|
||||
'schedule' in changeRequest
|
||||
? changeRequest.schedule.scheduledAt
|
||||
: undefined
|
||||
}
|
||||
failureReason={reason}
|
||||
/>
|
||||
<ChangeRequestReviewers changeRequest={changeRequest} />
|
||||
</StyledAsideBox>
|
||||
@ -417,10 +447,7 @@ export const ChangeRequestOverview: FC = () => {
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
scheduleChangeRequests &&
|
||||
Boolean(
|
||||
changeRequest.schedule
|
||||
?.scheduledAt,
|
||||
)
|
||||
Boolean(scheduledAt)
|
||||
}
|
||||
show={
|
||||
<StyledButton
|
||||
@ -483,27 +510,22 @@ export const ChangeRequestOverview: FC = () => {
|
||||
projectId={projectId}
|
||||
environment={changeRequest.environment}
|
||||
primaryButtonText={
|
||||
changeRequest?.schedule?.scheduledAt
|
||||
changeRequest.state === 'Scheduled'
|
||||
? 'Update scheduled time'
|
||||
: 'Schedule changes'
|
||||
}
|
||||
title={
|
||||
changeRequest?.schedule?.scheduledAt
|
||||
changeRequest.state === 'Scheduled'
|
||||
? 'Update schedule'
|
||||
: 'Schedule changes'
|
||||
}
|
||||
scheduledAt={
|
||||
changeRequest?.schedule?.scheduledAt ||
|
||||
undefined
|
||||
}
|
||||
scheduledAt={scheduledAt}
|
||||
/>
|
||||
<ChangeRequestApplyScheduledDialogue
|
||||
open={showApplyScheduledDialog}
|
||||
onConfirm={onApplyChanges}
|
||||
onClose={onApplyScheduledAbort}
|
||||
scheduledTime={
|
||||
changeRequest?.schedule?.scheduledAt
|
||||
}
|
||||
scheduledTime={scheduledAt}
|
||||
disabled={!allowChangeRequestActions || loading}
|
||||
projectId={projectId}
|
||||
environment={changeRequest.environment}
|
||||
@ -512,9 +534,7 @@ export const ChangeRequestOverview: FC = () => {
|
||||
open={showRejectScheduledDialog}
|
||||
onConfirm={onReject}
|
||||
onClose={onRejectScheduledAbort}
|
||||
scheduledTime={
|
||||
changeRequest?.schedule?.scheduledAt
|
||||
}
|
||||
scheduledTime={scheduledAt}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -26,15 +26,16 @@ import {
|
||||
} from './ChangeRequestReviewStatus.styles';
|
||||
import {
|
||||
ChangeRequestState,
|
||||
IChangeRequest,
|
||||
IChangeRequestSchedule,
|
||||
ChangeRequestType,
|
||||
ChangeRequestSchedule,
|
||||
ChangeRequestScheduleFailed,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { getBrowserTimezone } from './utils';
|
||||
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||
import { formatDateYMDHMS } from 'utils/formatDate';
|
||||
|
||||
interface ISuggestChangeReviewsStatusProps {
|
||||
changeRequest: IChangeRequest;
|
||||
changeRequest: ChangeRequestType;
|
||||
onEditClick?: () => void;
|
||||
}
|
||||
const resolveBorder = (state: ChangeRequestState, theme: Theme) => {
|
||||
@ -103,7 +104,7 @@ export const ChangeRequestReviewStatus: FC<ISuggestChangeReviewsStatusProps> =
|
||||
};
|
||||
|
||||
interface IResolveComponentProps {
|
||||
changeRequest: IChangeRequest;
|
||||
changeRequest: ChangeRequestType;
|
||||
onEditClick?: () => void;
|
||||
}
|
||||
|
||||
@ -111,7 +112,7 @@ const ResolveComponent = ({
|
||||
changeRequest,
|
||||
onEditClick,
|
||||
}: IResolveComponentProps) => {
|
||||
const { state, schedule } = changeRequest;
|
||||
const { state } = changeRequest;
|
||||
|
||||
if (!state) {
|
||||
return null;
|
||||
@ -134,6 +135,7 @@ const ResolveComponent = ({
|
||||
}
|
||||
|
||||
if (state === 'Scheduled') {
|
||||
const { schedule } = changeRequest;
|
||||
return <Scheduled schedule={schedule} onEditClick={onEditClick} />;
|
||||
}
|
||||
|
||||
@ -228,13 +230,11 @@ const StyledIconButton = styled(IconButton)({
|
||||
});
|
||||
|
||||
interface IScheduledProps {
|
||||
schedule?: IChangeRequest['schedule'];
|
||||
schedule?: ChangeRequestSchedule;
|
||||
onEditClick?: () => any;
|
||||
}
|
||||
const Scheduled = ({ schedule, onEditClick }: IScheduledProps) => {
|
||||
const theme = useTheme();
|
||||
const timezone = getBrowserTimezone();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
|
||||
if (!schedule?.scheduledAt) {
|
||||
return null;
|
||||
@ -258,9 +258,13 @@ const Scheduled = ({ schedule, onEditClick }: IScheduledProps) => {
|
||||
|
||||
<StyledScheduledBox>
|
||||
<ConditionallyRender
|
||||
condition={schedule?.status === 'pending'}
|
||||
condition={schedule.status === 'pending'}
|
||||
show={<ScheduledPending schedule={schedule} />}
|
||||
elseShow={<ScheduledFailed schedule={schedule} />}
|
||||
elseShow={
|
||||
<ScheduledFailed
|
||||
schedule={schedule as ChangeRequestScheduleFailed}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<StyledIconButton onClick={onEditClick}>
|
||||
@ -273,7 +277,7 @@ const Scheduled = ({ schedule, onEditClick }: IScheduledProps) => {
|
||||
|
||||
const ScheduledFailed = ({
|
||||
schedule,
|
||||
}: { schedule: IChangeRequestSchedule }) => {
|
||||
}: { schedule: ChangeRequestScheduleFailed }) => {
|
||||
const theme = useTheme();
|
||||
const timezone = getBrowserTimezone();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
@ -293,7 +297,7 @@ const ScheduledFailed = ({
|
||||
<Box>
|
||||
<StyledReviewTitle color={theme.palette.error.main}>
|
||||
Changes failed to be applied on {scheduledTime} because of{' '}
|
||||
{schedule?.failureReason}
|
||||
{schedule.reason ?? schedule.failureReason}
|
||||
</StyledReviewTitle>
|
||||
<Typography>Your timezone is {timezone}</Typography>
|
||||
</Box>
|
||||
@ -303,7 +307,7 @@ const ScheduledFailed = ({
|
||||
|
||||
const ScheduledPending = ({
|
||||
schedule,
|
||||
}: { schedule: IChangeRequestSchedule }) => {
|
||||
}: { schedule: ChangeRequestSchedule }) => {
|
||||
const theme = useTheme();
|
||||
const timezone = getBrowserTimezone();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
|
@ -3,7 +3,7 @@ import { FC, ReactNode } from 'react';
|
||||
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||
import { ChangeRequestRejections } from './ChangeRequestRejections';
|
||||
import { ChangeRequestApprovals } from './ChangeRequestApprovals';
|
||||
import { IChangeRequest } from '../../changeRequest.types';
|
||||
import { ChangeRequestType } from '../../changeRequest.types';
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
@ -44,7 +44,7 @@ export const ChangeRequestReviewersWrapper: FC<{ header: ReactNode }> = ({
|
||||
|
||||
export const ChangeRequestReviewers: FC<{
|
||||
changeRequest: Pick<
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
'approvals' | 'rejections' | 'state' | 'minApprovals'
|
||||
>;
|
||||
}> = ({ changeRequest }) => (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { Box, Button, IconButton, styled, Typography } from '@mui/material';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { IChangeRequest } from '../../changeRequest.types';
|
||||
import { ChangeRequestType } from '../../changeRequest.types';
|
||||
import { Edit } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
@ -25,7 +25,7 @@ export const StyledHeader = styled(Typography)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const ChangeRequestTitle: FC<{
|
||||
environmentChangeRequest: IChangeRequest;
|
||||
environmentChangeRequest: ChangeRequestType;
|
||||
title: string;
|
||||
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
||||
}> = ({ environmentChangeRequest, title, setTitle, children }) => {
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { IChangeRequest } from '../../changeRequest.types';
|
||||
import { ChangeRequestType } from '../../changeRequest.types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ChangeRequestStatusBadge } from '../../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
@ -54,7 +54,7 @@ const ChangeRequestContent = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const EnvironmentChangeRequest: FC<{
|
||||
environmentChangeRequest: IChangeRequest;
|
||||
environmentChangeRequest: ChangeRequestType;
|
||||
onClose: () => void;
|
||||
onReview: (id: number, comment?: string) => void;
|
||||
onDiscard: (id: number) => void;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { ChangeRequestTitle } from './ChangeRequestTitle';
|
||||
import { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
|
||||
import { UnscheduledChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import { render } from 'utils/testRenderer';
|
||||
|
||||
const changeRequest = {
|
||||
const changeRequest: UnscheduledChangeRequest = {
|
||||
id: 3,
|
||||
state: 'Draft' as ChangeRequestState,
|
||||
state: 'Draft' as const,
|
||||
title: 'My title',
|
||||
project: 'default',
|
||||
environment: 'default',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { VFC } from 'react';
|
||||
import { IChangeRequest } from '../changeRequest.types';
|
||||
import { ChangeRequestType } from '../changeRequest.types';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import {
|
||||
AccessTime,
|
||||
@ -11,7 +11,7 @@ import {
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
|
||||
interface IChangeRequestStatusBadgeProps {
|
||||
changeRequest: IChangeRequest | undefined;
|
||||
changeRequest: ChangeRequestType | undefined;
|
||||
}
|
||||
|
||||
const ReviewRequiredBadge: VFC = () => (
|
||||
@ -71,12 +71,18 @@ export const ChangeRequestStatusBadge: VFC<IChangeRequestStatusBadgeProps> = ({
|
||||
schedule!.scheduledAt,
|
||||
).toLocaleString();
|
||||
|
||||
const tooltipTitle =
|
||||
schedule?.status === 'pending'
|
||||
? `Scheduled for ${scheduledAt}`
|
||||
: `Failed on ${scheduledAt} because of ${
|
||||
schedule!.failureReason
|
||||
}`;
|
||||
const tooltipTitle = (() => {
|
||||
switch (schedule.status) {
|
||||
case 'failed':
|
||||
return `Failed on ${scheduledAt} because of ${
|
||||
schedule.reason || schedule.failureReason
|
||||
}`;
|
||||
case 'suspended':
|
||||
return schedule.reason;
|
||||
default:
|
||||
return `Scheduled for ${scheduledAt}`;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<HtmlTooltip title={tooltipTitle} arrow>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { VFC } from 'react';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||
|
||||
interface IChangeRequestStatusCellProps {
|
||||
value?: string | null; // FIXME: proper type
|
||||
row: { original: IChangeRequest };
|
||||
row: { original: ChangeRequestType };
|
||||
}
|
||||
|
||||
export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
|
||||
|
@ -3,7 +3,7 @@ import { IFeatureStrategy } from '../../interfaces/strategy';
|
||||
import { IUser } from '../../interfaces/user';
|
||||
import { SetStrategySortOrderSchema } from '../../openapi';
|
||||
|
||||
export interface IChangeRequest {
|
||||
type BaseChangeRequest = {
|
||||
id: number;
|
||||
title: string;
|
||||
project: string;
|
||||
@ -17,15 +17,43 @@ export interface IChangeRequest {
|
||||
rejections: IChangeRequestApproval[];
|
||||
comments: IChangeRequestComment[];
|
||||
conflict?: string;
|
||||
state: ChangeRequestState;
|
||||
schedule?: IChangeRequestSchedule;
|
||||
}
|
||||
};
|
||||
|
||||
export interface IChangeRequestSchedule {
|
||||
export type UnscheduledChangeRequest = BaseChangeRequest & {
|
||||
state: Exclude<ChangeRequestState, 'Scheduled'>;
|
||||
};
|
||||
|
||||
export type ScheduledChangeRequest = BaseChangeRequest & {
|
||||
state: 'Scheduled';
|
||||
schedule: ChangeRequestSchedule;
|
||||
};
|
||||
|
||||
export type ChangeRequestType =
|
||||
| UnscheduledChangeRequest
|
||||
| ScheduledChangeRequest;
|
||||
|
||||
export type ChangeRequestSchedulePending = {
|
||||
status: 'pending';
|
||||
scheduledAt: string;
|
||||
status: 'pending' | 'failed';
|
||||
failureReason?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type ChangeRequestScheduleFailed = {
|
||||
status: 'failed';
|
||||
scheduledAt: string;
|
||||
failureReason?: string | null;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type ChangeRequestScheduleSuspended = {
|
||||
status: 'suspended';
|
||||
scheduledAt: string;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type ChangeRequestSchedule =
|
||||
| ChangeRequestSchedulePending
|
||||
| ChangeRequestScheduleFailed
|
||||
| ChangeRequestScheduleSuspended;
|
||||
|
||||
export interface IChangeRequestEnvironmentConfig {
|
||||
environment: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { IChangeRequest } from './changeRequest.types';
|
||||
import { ChangeRequestType } from './changeRequest.types';
|
||||
|
||||
export const changesCount = (changeRequest: IChangeRequest) =>
|
||||
export const changesCount = (changeRequest: ChangeRequestType) =>
|
||||
changeRequest.features.flatMap((feature) => feature.changes).length +
|
||||
changeRequest.segments.length;
|
||||
|
@ -6,7 +6,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { screen } from '@testing-library/dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
ChangeRequestAction,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
@ -30,7 +30,7 @@ const strategy = {
|
||||
const draftRequest = (
|
||||
action: Omit<ChangeRequestAction, 'updateSegment'> = 'updateStrategy',
|
||||
createdBy = 1,
|
||||
): IChangeRequest => {
|
||||
): ChangeRequestType => {
|
||||
return {
|
||||
id: 71,
|
||||
title: 'Change request #71',
|
||||
|
@ -6,7 +6,7 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { arraysHaveSameItems } from 'utils/arraysHaveSameItems';
|
||||
import { Alert, List, ListItem, styled } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
|
||||
@ -29,7 +29,7 @@ interface IFeatureSettingsProjectConfirm {
|
||||
onClose: () => void;
|
||||
onClick: (args: any) => void;
|
||||
feature: IFeatureToggle;
|
||||
changeRequests: IChangeRequest[] | undefined;
|
||||
changeRequests: ChangeRequestType[] | undefined;
|
||||
}
|
||||
|
||||
const FeatureSettingsProjectConfirm = ({
|
||||
|
@ -5,7 +5,7 @@ import { screen } from '@testing-library/dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
ChangeRequestAction,
|
||||
IChangeRequest,
|
||||
ChangeRequestType,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { EnvironmentVariantsCard } from './EnvironmentVariantsCard';
|
||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
@ -30,7 +30,7 @@ const strategy = {
|
||||
const scheduledRequest = (
|
||||
action: Omit<ChangeRequestAction, 'updateSegment'> = 'updateStrategy',
|
||||
createdBy = 1,
|
||||
): IChangeRequest => {
|
||||
): ChangeRequestType => {
|
||||
return {
|
||||
id: 71,
|
||||
title: 'Change request #71',
|
||||
|
@ -3,7 +3,7 @@ import { Box, Button, styled, Typography } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ChangeRequestSidebar } from 'component/changeRequest/ChangeRequestSidebar/ChangeRequestSidebar';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { changesCount } from 'component/changeRequest/changesCount';
|
||||
import { Sticky } from 'component/common/Sticky/Sticky';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
@ -61,7 +61,7 @@ const StyledSpaciousDraftBanner = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
const DraftBannerContent: FC<{
|
||||
changeRequests: IChangeRequest[];
|
||||
changeRequests: ChangeRequestType[];
|
||||
onClick: () => void;
|
||||
}> = ({ changeRequests, onClick }) => {
|
||||
const environments = changeRequests.map(({ environment }) => environment);
|
||||
@ -165,7 +165,7 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
|
||||
show={
|
||||
<DraftBannerContent
|
||||
changeRequests={
|
||||
unfinishedChangeRequests as IChangeRequest[]
|
||||
unfinishedChangeRequests as ChangeRequestType[]
|
||||
}
|
||||
onClick={() => {
|
||||
setIsSidebarOpen(true);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
import {
|
||||
StyledCount,
|
||||
@ -74,10 +74,12 @@ export const ChangeRequestsWidget: FC<IChangeRequestsWidgetProps> = ({
|
||||
const { changeRequests, loading } = useProjectChangeRequests(projectId);
|
||||
const loadingRef = useLoading(loading, `[data-loading="${LOADING_LABEL}"]`);
|
||||
const toBeApplied = changeRequests?.filter(
|
||||
(changeRequest: IChangeRequest) => changeRequest?.state === 'Approved',
|
||||
(changeRequest: ChangeRequestType) =>
|
||||
changeRequest?.state === 'Approved',
|
||||
).length;
|
||||
const toBeReviewed = changeRequests?.filter(
|
||||
(changeRequest: IChangeRequest) => changeRequest?.state === 'In review',
|
||||
(changeRequest: ChangeRequestType) =>
|
||||
changeRequest?.state === 'In review',
|
||||
).length;
|
||||
|
||||
return (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import useSWR from 'swr';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
export const useChangeRequest = (projectId: string, id: string) => {
|
||||
const { data, error, mutate } = useSWR<IChangeRequest>(
|
||||
const { data, error, mutate } = useSWR<ChangeRequestType>(
|
||||
formatApiPath(`api/admin/projects/${projectId}/change-requests/${id}`),
|
||||
fetcher,
|
||||
{ refreshInterval: 15000 },
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
@ -10,7 +10,7 @@ const fetcher = (path: string) => {
|
||||
};
|
||||
|
||||
export const usePendingChangeRequests = (project: string) => {
|
||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||
const { data, error, mutate } = useEnterpriseSWR<ChangeRequestType[]>(
|
||||
[],
|
||||
formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
|
||||
fetcher,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
@ -13,7 +13,7 @@ export const usePendingChangeRequestsForFeature = (
|
||||
project: string,
|
||||
featureName: string,
|
||||
) => {
|
||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||
const { data, error, mutate } = useEnterpriseSWR<ChangeRequestType[]>(
|
||||
[],
|
||||
formatApiPath(
|
||||
`api/admin/projects/${project}/change-requests/pending/${featureName}`,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Alert } from '@mui/material';
|
||||
import { oneOf } from 'utils/oneOf';
|
||||
import { IChangeRequest } from '../component/changeRequest/changeRequest.types';
|
||||
import { ChangeRequestType } from '../component/changeRequest/changeRequest.types';
|
||||
|
||||
export const useChangeRequestInReviewWarning = (
|
||||
draft: IChangeRequest[] | undefined,
|
||||
draft: ChangeRequestType[] | undefined,
|
||||
) => {
|
||||
const changeRequestInReviewOrApproved = (environment: string) => {
|
||||
if (!draft) return false;
|
||||
|
Loading…
Reference in New Issue
Block a user