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 { FeatureToggleChanges } from './Changes/FeatureToggleChanges';
|
||||
import { Change } from './Changes/Change/Change';
|
||||
import { DiscardContainer } from './Changes/Change/Discard';
|
||||
import { ChangeActions } from './Changes/Change/ChangeActions';
|
||||
|
||||
interface IChangeRequestProps {
|
||||
changeRequest: IChangeRequest;
|
||||
@ -30,10 +30,11 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||
<Change
|
||||
key={index}
|
||||
discard={
|
||||
<DiscardContainer
|
||||
<ChangeActions
|
||||
changeRequest={changeRequest}
|
||||
changeId={change.id}
|
||||
onPostDiscard={onRefetch}
|
||||
feature={feature.name}
|
||||
change={change}
|
||||
onRefetch={onRefetch}
|
||||
/>
|
||||
}
|
||||
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 { ChangeRequestState } from '../../changeRequest.types';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { testServerRoute, testServerSetup } from '../../../../utils/testServer';
|
||||
import { render } from '../../../../utils/testRenderer';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { UIProviderContainer } from '../../../providers/UIProvider/UIProviderContainer';
|
||||
|
||||
const changeRequest = {
|
||||
|
@ -111,9 +111,18 @@ export const ChangeRequestsTabs = ({
|
||||
Header: 'Updated feature toggles',
|
||||
canSort: false,
|
||||
accessor: 'features',
|
||||
Cell: ({ value }: any) => {
|
||||
return <FeaturesCell project={projectId} value={value} />;
|
||||
Cell: ({
|
||||
value,
|
||||
row: {
|
||||
original: { title },
|
||||
},
|
||||
}: any) => (
|
||||
<FeaturesCell
|
||||
project={projectId}
|
||||
value={value}
|
||||
key={title}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'By',
|
||||
|
@ -106,7 +106,7 @@ type ChangeRequestEnabled = { enabled: boolean };
|
||||
|
||||
type ChangeRequestAddStrategy = Pick<
|
||||
IFeatureStrategy,
|
||||
'parameters' | 'constraints'
|
||||
'parameters' | 'constraints' | 'segments'
|
||||
> & { name: string };
|
||||
|
||||
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
|
||||
|
@ -160,7 +160,6 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
||||
}) => {
|
||||
const { setToastData } = useToast();
|
||||
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
||||
|
||||
const copyCommand = () => {
|
||||
if (copy(formatApiCode())) {
|
||||
setToastData({
|
||||
|
@ -9,6 +9,7 @@ interface ISidebarModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
label: string;
|
||||
onClick?: (e: React.SyntheticEvent) => void;
|
||||
children: React.ReactElement<any, any>;
|
||||
}
|
||||
|
||||
@ -39,6 +40,7 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||
export const BaseModal: FC<ISidebarModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
onClick,
|
||||
label,
|
||||
children,
|
||||
}) => {
|
||||
@ -46,6 +48,7 @@ export const BaseModal: FC<ISidebarModalProps> = ({
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClick={onClick}
|
||||
closeAfterTransition
|
||||
aria-label={label}
|
||||
BackdropComponent={Backdrop}
|
||||
|
@ -37,6 +37,7 @@ interface IFeatureStrategyFormProps {
|
||||
environmentId: string;
|
||||
permission: string;
|
||||
onSubmit: () => void;
|
||||
onCancel?: () => void;
|
||||
loading: boolean;
|
||||
isChangeRequest?: boolean;
|
||||
strategy: Partial<IFeatureStrategy>;
|
||||
@ -74,6 +75,7 @@ export const FeatureStrategyForm = ({
|
||||
environmentId,
|
||||
permission,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
loading,
|
||||
strategy,
|
||||
setStrategy,
|
||||
@ -149,7 +151,7 @@ export const FeatureStrategyForm = ({
|
||||
.every(Boolean);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
const onDefaultCancel = () => {
|
||||
navigate(formatFeaturePath(feature.project, feature.name));
|
||||
};
|
||||
|
||||
@ -270,7 +272,7 @@ export const FeatureStrategyForm = ({
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={onCancel}
|
||||
onClick={onCancel ? onCancel : onDefaultCancel}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
|
@ -79,9 +79,9 @@ export const useChangeRequestApi = () => {
|
||||
const discardChange = async (
|
||||
project: string,
|
||||
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, {
|
||||
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 ({
|
||||
project,
|
||||
enabled,
|
||||
@ -176,6 +194,7 @@ export const useChangeRequestApi = () => {
|
||||
|
||||
return {
|
||||
addChange,
|
||||
editChange,
|
||||
changeState,
|
||||
discardChange,
|
||||
updateChangeRequestEnvironmentConfig,
|
||||
|
Loading…
Reference in New Issue
Block a user