mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-09 00:18:26 +01:00
feat: edit change requests (#3573)
This commit is contained in:
parent
e4f7a644e8
commit
514961632f
@ -3,7 +3,7 @@ import { Box, Typography } from '@mui/material';
|
|||||||
import type { IChangeRequest } from '../changeRequest.types';
|
import type { IChangeRequest } from '../changeRequest.types';
|
||||||
import { FeatureToggleChanges } from './Changes/FeatureToggleChanges';
|
import { FeatureToggleChanges } from './Changes/FeatureToggleChanges';
|
||||||
import { Change } from './Changes/Change/Change';
|
import { Change } from './Changes/Change/Change';
|
||||||
import { DiscardContainer } from './Changes/Change/Discard';
|
import { ChangeActions } from './Changes/Change/ChangeActions';
|
||||||
|
|
||||||
interface IChangeRequestProps {
|
interface IChangeRequestProps {
|
||||||
changeRequest: IChangeRequest;
|
changeRequest: IChangeRequest;
|
||||||
@ -30,10 +30,11 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
<Change
|
<Change
|
||||||
key={index}
|
key={index}
|
||||||
discard={
|
discard={
|
||||||
<DiscardContainer
|
<ChangeActions
|
||||||
changeRequest={changeRequest}
|
changeRequest={changeRequest}
|
||||||
changeId={change.id}
|
feature={feature.name}
|
||||||
onPostDiscard={onRefetch}
|
change={change}
|
||||||
|
onRefetch={onRefetch}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
index={index}
|
index={index}
|
||||||
|
@ -0,0 +1,200 @@
|
|||||||
|
import React, { FC, useState } from 'react';
|
||||||
|
import {
|
||||||
|
IChange,
|
||||||
|
IChangeRequest,
|
||||||
|
IChangeRequestAddStrategy,
|
||||||
|
IChangeRequestUpdateStrategy,
|
||||||
|
} from '../../../changeRequest.types';
|
||||||
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
|
import { changesCount } from '../../../changesCount';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Popover,
|
||||||
|
styled,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { Delete, Edit, GroupRounded, MoreVert } from '@mui/icons-material';
|
||||||
|
import { EditChange } from './EditChange';
|
||||||
|
|
||||||
|
const useShowActions = (changeRequest: IChangeRequest, change: IChange) => {
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
||||||
|
changeRequest.project
|
||||||
|
);
|
||||||
|
const allowChangeRequestActions = isChangeRequestConfigured(
|
||||||
|
changeRequest.environment
|
||||||
|
);
|
||||||
|
const isPending = !['Cancelled', 'Applied'].includes(changeRequest.state);
|
||||||
|
|
||||||
|
const { user } = useAuthUser();
|
||||||
|
const isAuthor = user?.id === changeRequest.createdBy.id;
|
||||||
|
|
||||||
|
const showActions = allowChangeRequestActions && isPending && isAuthor;
|
||||||
|
|
||||||
|
const showEdit =
|
||||||
|
showActions &&
|
||||||
|
['addStrategy', 'updateStrategy'].includes(change.action);
|
||||||
|
|
||||||
|
const showDiscard = showActions && changesCount(changeRequest) > 1;
|
||||||
|
|
||||||
|
return { showEdit, showDiscard };
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
padding: theme.spacing(1, 1.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const ChangeActions: FC<{
|
||||||
|
changeRequest: IChangeRequest;
|
||||||
|
feature: string;
|
||||||
|
change: IChange;
|
||||||
|
onRefetch?: () => void;
|
||||||
|
}> = ({ changeRequest, feature, change, onRefetch }) => {
|
||||||
|
const { showDiscard, showEdit } = useShowActions(changeRequest, change);
|
||||||
|
const { discardChange } = useChangeRequestApi();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
|
|
||||||
|
const id = `cr-${change.id}-actions`;
|
||||||
|
const menuId = `${id}-menu`;
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdit = () => {
|
||||||
|
setEditOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDiscard = async () => {
|
||||||
|
try {
|
||||||
|
handleClose();
|
||||||
|
await discardChange(
|
||||||
|
changeRequest.project,
|
||||||
|
changeRequest.id,
|
||||||
|
change.id
|
||||||
|
);
|
||||||
|
setToastData({
|
||||||
|
title: 'Change discarded from change request draft.',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
onRefetch?.();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showEdit || showDiscard}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<Tooltip title="Change request actions" arrow describeChild>
|
||||||
|
<IconButton
|
||||||
|
id={id}
|
||||||
|
aria-controls={open ? menuId : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={open ? 'true' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<MoreVert />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<StyledPopover
|
||||||
|
id={menuId}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
transformOrigin={{
|
||||||
|
horizontal: 'right',
|
||||||
|
vertical: 'top',
|
||||||
|
}}
|
||||||
|
anchorOrigin={{
|
||||||
|
horizontal: 'right',
|
||||||
|
vertical: 'bottom',
|
||||||
|
}}
|
||||||
|
disableScrollLock={true}
|
||||||
|
>
|
||||||
|
<MenuList aria-labelledby={id}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showEdit}
|
||||||
|
show={
|
||||||
|
<MenuItem onClick={onEdit}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Edit />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Edit change
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
<EditChange
|
||||||
|
changeRequestId={changeRequest.id}
|
||||||
|
featureId={feature}
|
||||||
|
change={
|
||||||
|
change as
|
||||||
|
| IChangeRequestAddStrategy
|
||||||
|
| IChangeRequestUpdateStrategy
|
||||||
|
}
|
||||||
|
environment={
|
||||||
|
changeRequest.environment
|
||||||
|
}
|
||||||
|
open={editOpen}
|
||||||
|
onSubmit={() => {
|
||||||
|
setEditOpen(false);
|
||||||
|
onRefetch?.();
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setEditOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showDiscard}
|
||||||
|
show={
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
onDiscard();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Delete />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Discard change
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</StyledPopover>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,74 +0,0 @@
|
|||||||
import React, { FC } from 'react';
|
|
||||||
import { IChangeRequest } from '../../../changeRequest.types';
|
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
|
||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
|
||||||
import { changesCount } from '../../../changesCount';
|
|
||||||
import { Box, Link, styled } from '@mui/material';
|
|
||||||
|
|
||||||
const useShowDiscard = (changeRequest: IChangeRequest) => {
|
|
||||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
|
||||||
changeRequest.project
|
|
||||||
);
|
|
||||||
const allowChangeRequestActions = isChangeRequestConfigured(
|
|
||||||
changeRequest.environment
|
|
||||||
);
|
|
||||||
const isPending = !['Cancelled', 'Applied'].includes(changeRequest.state);
|
|
||||||
|
|
||||||
const { user } = useAuthUser();
|
|
||||||
const isAuthor = user?.id === changeRequest.createdBy.id;
|
|
||||||
|
|
||||||
const showDiscard =
|
|
||||||
allowChangeRequestActions &&
|
|
||||||
isPending &&
|
|
||||||
isAuthor &&
|
|
||||||
changesCount(changeRequest) > 1;
|
|
||||||
|
|
||||||
return showDiscard;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledLink = styled(Link)(() => ({
|
|
||||||
textDecoration: 'none',
|
|
||||||
'&:hover, &:focus': {
|
|
||||||
textDecoration: 'underline',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Discard: FC<{ onDiscard: () => void }> = ({ onDiscard }) => (
|
|
||||||
<Box>
|
|
||||||
<StyledLink onClick={onDiscard}>Discard</StyledLink>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DiscardContainer: FC<{
|
|
||||||
changeRequest: IChangeRequest;
|
|
||||||
changeId: number;
|
|
||||||
onPostDiscard?: () => void;
|
|
||||||
}> = ({ changeRequest, changeId, onPostDiscard }) => {
|
|
||||||
const showDiscard = useShowDiscard(changeRequest);
|
|
||||||
const { discardChange } = useChangeRequestApi();
|
|
||||||
const { setToastData, setToastApiError } = useToast();
|
|
||||||
|
|
||||||
const onDiscard = (id: number) => async () => {
|
|
||||||
try {
|
|
||||||
await discardChange(changeRequest.project, changeRequest.id, id);
|
|
||||||
setToastData({
|
|
||||||
title: 'Change discarded from change request draft.',
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
onPostDiscard?.();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={showDiscard}
|
|
||||||
show={<Discard onDiscard={onDiscard(changeId)} />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,199 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
||||||
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { ISegment } from 'interfaces/segment';
|
||||||
|
import { formatStrategyName } from 'utils/strategyNames';
|
||||||
|
import { useFormErrors } from 'hooks/useFormErrors';
|
||||||
|
import { useCollaborateData } from 'hooks/useCollaborateData';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
|
import { comparisonModerator } from 'component/feature/FeatureStrategy/featureStrategy.utils';
|
||||||
|
import {
|
||||||
|
IChangeRequestAddStrategy,
|
||||||
|
IChangeRequestUpdateStrategy,
|
||||||
|
} from 'component/changeRequest/changeRequest.types';
|
||||||
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
|
|
||||||
|
interface IEditChangeProps {
|
||||||
|
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
|
||||||
|
changeRequestId: number;
|
||||||
|
featureId: string;
|
||||||
|
environment: string;
|
||||||
|
open: boolean;
|
||||||
|
onSubmit: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditChange = ({
|
||||||
|
change,
|
||||||
|
changeRequestId,
|
||||||
|
environment,
|
||||||
|
open,
|
||||||
|
onSubmit,
|
||||||
|
onClose,
|
||||||
|
featureId,
|
||||||
|
}: IEditChangeProps) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const { editChange } = useChangeRequestApi();
|
||||||
|
|
||||||
|
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>(
|
||||||
|
change.payload
|
||||||
|
);
|
||||||
|
|
||||||
|
const { segments: allSegments } = useSegments();
|
||||||
|
const strategySegments =
|
||||||
|
allSegments?.filter(segment => {
|
||||||
|
return change.payload.segments?.includes(segment.id);
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
const [segments, setSegments] = useState<ISegment[]>(strategySegments);
|
||||||
|
|
||||||
|
const strategyDefinition = {
|
||||||
|
parameters: change.payload.parameters,
|
||||||
|
name: change.payload.name,
|
||||||
|
};
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const errors = useFormErrors();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
const { unleashUrl } = uiConfig;
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
|
|
||||||
|
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||||
|
|
||||||
|
const ref = useRef<IFeatureToggle>(feature);
|
||||||
|
|
||||||
|
const { data, staleDataNotification, forceRefreshCache } =
|
||||||
|
useCollaborateData<IFeatureToggle>(
|
||||||
|
{
|
||||||
|
unleashGetter: useFeature,
|
||||||
|
params: [projectId, featureId],
|
||||||
|
dataKey: 'feature',
|
||||||
|
refetchFunctionKey: 'refetchFeature',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
feature,
|
||||||
|
{
|
||||||
|
afterSubmitAction: refetchFeature,
|
||||||
|
},
|
||||||
|
comparisonModerator
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current.name === '' && feature.name) {
|
||||||
|
forceRefreshCache(feature);
|
||||||
|
ref.current = feature;
|
||||||
|
}
|
||||||
|
}, [feature]);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...strategy,
|
||||||
|
segments: segments.map(segment => segment.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInternalSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await editChange(projectId, changeRequestId, change.id, {
|
||||||
|
action: strategy.id ? 'updateStrategy' : 'addStrategy',
|
||||||
|
feature: featureId,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
onSubmit();
|
||||||
|
setToastData({
|
||||||
|
title: 'Change updated',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!strategyDefinition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarModal
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
label="Edit change"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormTemplate
|
||||||
|
modal
|
||||||
|
title={formatStrategyName(strategyDefinition.name ?? '')}
|
||||||
|
description={featureStrategyHelp}
|
||||||
|
documentationLink={featureStrategyDocsLink}
|
||||||
|
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
||||||
|
formatApiCode={() =>
|
||||||
|
formatUpdateStrategyApiCode(
|
||||||
|
projectId,
|
||||||
|
changeRequestId,
|
||||||
|
change.id,
|
||||||
|
payload,
|
||||||
|
unleashUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FeatureStrategyForm
|
||||||
|
projectId={projectId}
|
||||||
|
feature={data}
|
||||||
|
strategy={strategy}
|
||||||
|
setStrategy={setStrategy}
|
||||||
|
segments={segments}
|
||||||
|
setSegments={setSegments}
|
||||||
|
environmentId={environment}
|
||||||
|
onSubmit={onInternalSubmit}
|
||||||
|
onCancel={onClose}
|
||||||
|
loading={false}
|
||||||
|
permission={UPDATE_FEATURE_STRATEGY}
|
||||||
|
errors={errors}
|
||||||
|
isChangeRequest={isChangeRequestConfigured(environment)}
|
||||||
|
/>
|
||||||
|
{staleDataNotification}
|
||||||
|
</FormTemplate>
|
||||||
|
</SidebarModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUpdateStrategyApiCode = (
|
||||||
|
projectId: string,
|
||||||
|
changeRequestId: number,
|
||||||
|
changeId: number,
|
||||||
|
strategy: Partial<IFeatureStrategy>,
|
||||||
|
unleashUrl?: string
|
||||||
|
): string => {
|
||||||
|
if (!unleashUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${unleashUrl}/api/admin/projects/${projectId}/change-requests/${changeRequestId}/changes/${changeId}`;
|
||||||
|
const payload = JSON.stringify(strategy, undefined, 2);
|
||||||
|
|
||||||
|
return `curl --location --request PUT '${url}' \\
|
||||||
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
|
--header 'Content-Type: application/json' \\
|
||||||
|
--data-raw '${payload}'`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const featureStrategyHelp = `
|
||||||
|
An activation strategy will only run when a feature toggle is enabled and provides a way to control who will get access to the feature.
|
||||||
|
If any of a feature toggle's activation strategies returns true, the user will get access.
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const featureStrategyDocsLink =
|
||||||
|
'https://docs.getunleash.io/reference/activation-strategies';
|
||||||
|
|
||||||
|
export const featureStrategyDocsLinkLabel = 'Strategies documentation';
|
@ -3,8 +3,8 @@ import { screen } from '@testing-library/react';
|
|||||||
import { ChangeRequestTitle } from './ChangeRequestTitle';
|
import { ChangeRequestTitle } from './ChangeRequestTitle';
|
||||||
import { ChangeRequestState } from '../../changeRequest.types';
|
import { ChangeRequestState } from '../../changeRequest.types';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { testServerRoute, testServerSetup } from '../../../../utils/testServer';
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
import { render } from '../../../../utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { UIProviderContainer } from '../../../providers/UIProvider/UIProviderContainer';
|
import { UIProviderContainer } from '../../../providers/UIProvider/UIProviderContainer';
|
||||||
|
|
||||||
const changeRequest = {
|
const changeRequest = {
|
||||||
|
@ -111,9 +111,18 @@ export const ChangeRequestsTabs = ({
|
|||||||
Header: 'Updated feature toggles',
|
Header: 'Updated feature toggles',
|
||||||
canSort: false,
|
canSort: false,
|
||||||
accessor: 'features',
|
accessor: 'features',
|
||||||
Cell: ({ value }: any) => {
|
Cell: ({
|
||||||
return <FeaturesCell project={projectId} value={value} />;
|
value,
|
||||||
},
|
row: {
|
||||||
|
original: { title },
|
||||||
|
},
|
||||||
|
}: any) => (
|
||||||
|
<FeaturesCell
|
||||||
|
project={projectId}
|
||||||
|
value={value}
|
||||||
|
key={title}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'By',
|
Header: 'By',
|
||||||
|
@ -106,7 +106,7 @@ type ChangeRequestEnabled = { enabled: boolean };
|
|||||||
|
|
||||||
type ChangeRequestAddStrategy = Pick<
|
type ChangeRequestAddStrategy = Pick<
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
'parameters' | 'constraints'
|
'parameters' | 'constraints' | 'segments'
|
||||||
> & { name: string };
|
> & { name: string };
|
||||||
|
|
||||||
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
|
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
|
||||||
|
@ -160,7 +160,6 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
||||||
|
|
||||||
const copyCommand = () => {
|
const copyCommand = () => {
|
||||||
if (copy(formatApiCode())) {
|
if (copy(formatApiCode())) {
|
||||||
setToastData({
|
setToastData({
|
||||||
|
@ -9,6 +9,7 @@ interface ISidebarModalProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
label: string;
|
label: string;
|
||||||
|
onClick?: (e: React.SyntheticEvent) => void;
|
||||||
children: React.ReactElement<any, any>;
|
children: React.ReactElement<any, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
|||||||
export const BaseModal: FC<ISidebarModalProps> = ({
|
export const BaseModal: FC<ISidebarModalProps> = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
|
onClick,
|
||||||
label,
|
label,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
@ -46,6 +48,7 @@ export const BaseModal: FC<ISidebarModalProps> = ({
|
|||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
onClick={onClick}
|
||||||
closeAfterTransition
|
closeAfterTransition
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
BackdropComponent={Backdrop}
|
BackdropComponent={Backdrop}
|
||||||
|
@ -37,6 +37,7 @@ interface IFeatureStrategyFormProps {
|
|||||||
environmentId: string;
|
environmentId: string;
|
||||||
permission: string;
|
permission: string;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
isChangeRequest?: boolean;
|
isChangeRequest?: boolean;
|
||||||
strategy: Partial<IFeatureStrategy>;
|
strategy: Partial<IFeatureStrategy>;
|
||||||
@ -74,6 +75,7 @@ export const FeatureStrategyForm = ({
|
|||||||
environmentId,
|
environmentId,
|
||||||
permission,
|
permission,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
loading,
|
loading,
|
||||||
strategy,
|
strategy,
|
||||||
setStrategy,
|
setStrategy,
|
||||||
@ -149,7 +151,7 @@ export const FeatureStrategyForm = ({
|
|||||||
.every(Boolean);
|
.every(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onDefaultCancel = () => {
|
||||||
navigate(formatFeaturePath(feature.project, feature.name));
|
navigate(formatFeaturePath(feature.project, feature.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -270,7 +272,7 @@ export const FeatureStrategyForm = ({
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={onCancel}
|
onClick={onCancel ? onCancel : onDefaultCancel}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
@ -79,9 +79,9 @@ export const useChangeRequestApi = () => {
|
|||||||
const discardChange = async (
|
const discardChange = async (
|
||||||
project: string,
|
project: string,
|
||||||
changeRequestId: number,
|
changeRequestId: number,
|
||||||
changeRequestEventId: number
|
changeId: number
|
||||||
) => {
|
) => {
|
||||||
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/changes/${changeRequestEventId}`;
|
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/changes/${changeId}`;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
@ -92,6 +92,24 @@ export const useChangeRequestApi = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editChange = async (
|
||||||
|
project: string,
|
||||||
|
changeRequestId: number,
|
||||||
|
changeId: number,
|
||||||
|
payload: IChangeSchema
|
||||||
|
) => {
|
||||||
|
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/changes/${changeId}`;
|
||||||
|
const req = createRequest(path, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
return await makeRequest(req.caller, req.id);
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateChangeRequestEnvironmentConfig = async ({
|
const updateChangeRequestEnvironmentConfig = async ({
|
||||||
project,
|
project,
|
||||||
enabled,
|
enabled,
|
||||||
@ -176,6 +194,7 @@ export const useChangeRequestApi = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
addChange,
|
addChange,
|
||||||
|
editChange,
|
||||||
changeState,
|
changeState,
|
||||||
discardChange,
|
discardChange,
|
||||||
updateChangeRequestEnvironmentConfig,
|
updateChangeRequestEnvironmentConfig,
|
||||||
|
Loading…
Reference in New Issue
Block a user