mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: edit release plan template (#8723)
This commit is contained in:
parent
584be706ec
commit
7feba0c4d4
@ -230,20 +230,6 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"title": "Strategy types",
|
"title": "Strategy types",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"component": [Function],
|
|
||||||
"enterprise": true,
|
|
||||||
"flag": "releasePlans",
|
|
||||||
"menu": {
|
|
||||||
"advanced": true,
|
|
||||||
"mode": [
|
|
||||||
"enterprise",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"path": "/release-management",
|
|
||||||
"title": "Release management",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": {},
|
"menu": {},
|
||||||
@ -270,6 +256,34 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"title": "Environments",
|
"title": "Environments",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"component": [Function],
|
||||||
|
"enterprise": true,
|
||||||
|
"flag": "releasePlans",
|
||||||
|
"menu": {
|
||||||
|
"advanced": true,
|
||||||
|
"mode": [
|
||||||
|
"enterprise",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"path": "/release-management",
|
||||||
|
"title": "Release management",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": [Function],
|
||||||
|
"enterprise": true,
|
||||||
|
"flag": "releasePlans",
|
||||||
|
"menu": {
|
||||||
|
"mode": [
|
||||||
|
"enterprise",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"parent": "/release-management",
|
||||||
|
"path": "/release-management/edit/:templateId",
|
||||||
|
"title": "Edit release plan template",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": {},
|
"menu": {},
|
||||||
|
@ -48,6 +48,7 @@ import { Signals } from 'component/signals/Signals';
|
|||||||
import { LazyCreateProject } from '../project/Project/CreateProject/LazyCreateProject';
|
import { LazyCreateProject } from '../project/Project/CreateProject/LazyCreateProject';
|
||||||
import { PersonalDashboard } from '../personalDashboard/PersonalDashboard';
|
import { PersonalDashboard } from '../personalDashboard/PersonalDashboard';
|
||||||
import { ReleaseManagement } from 'component/releases/ReleaseManagement/ReleaseManagement';
|
import { ReleaseManagement } from 'component/releases/ReleaseManagement/ReleaseManagement';
|
||||||
|
import { EditReleasePlanTemplate } from 'component/releases/ReleasePlanTemplate/EditReleasePlanTemplate';
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
// Splash
|
// Splash
|
||||||
@ -246,15 +247,6 @@ export const routes: IRoute[] = [
|
|||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { mobile: true, advanced: true },
|
menu: { mobile: true, advanced: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/release-management',
|
|
||||||
title: 'Release management',
|
|
||||||
component: ReleaseManagement,
|
|
||||||
type: 'protected',
|
|
||||||
menu: { advanced: true, mode: ['enterprise'] },
|
|
||||||
flag: 'releasePlans',
|
|
||||||
enterprise: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/environments/create',
|
path: '/environments/create',
|
||||||
title: 'Environments',
|
title: 'Environments',
|
||||||
@ -279,6 +271,27 @@ export const routes: IRoute[] = [
|
|||||||
enterprise: true,
|
enterprise: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Release management/plans
|
||||||
|
{
|
||||||
|
path: '/release-management',
|
||||||
|
title: 'Release management',
|
||||||
|
component: ReleaseManagement,
|
||||||
|
type: 'protected',
|
||||||
|
menu: { advanced: true, mode: ['enterprise'] },
|
||||||
|
flag: 'releasePlans',
|
||||||
|
enterprise: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/release-management/edit/:templateId',
|
||||||
|
title: 'Edit release plan template',
|
||||||
|
parent: '/release-management',
|
||||||
|
component: EditReleasePlanTemplate,
|
||||||
|
type: 'protected',
|
||||||
|
menu: { mode: ['enterprise'] },
|
||||||
|
flag: 'releasePlans',
|
||||||
|
enterprise: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
{
|
{
|
||||||
path: '/tag-types/create',
|
path: '/tag-types/create',
|
||||||
|
@ -2,6 +2,7 @@ import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
|||||||
import { ReactComponent as ReleaseTemplateIcon } from 'assets/img/releaseTemplates.svg';
|
import { ReactComponent as ReleaseTemplateIcon } from 'assets/img/releaseTemplates.svg';
|
||||||
import { styled, Typography } from '@mui/material';
|
import { styled, Typography } from '@mui/material';
|
||||||
import { ReleasePlanTemplateCardMenu } from './ReleasePlanTemplateCardMenu';
|
import { ReleasePlanTemplateCardMenu } from './ReleasePlanTemplateCardMenu';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const StyledTemplateCard = styled('aside')(({ theme }) => ({
|
const StyledTemplateCard = styled('aside')(({ theme }) => ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -58,8 +59,13 @@ const StyledMenu = styled('div')(({ theme }) => ({
|
|||||||
export const ReleasePlanTemplateCard = ({
|
export const ReleasePlanTemplateCard = ({
|
||||||
template,
|
template,
|
||||||
}: { template: IReleasePlanTemplate }) => {
|
}: { template: IReleasePlanTemplate }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const onClick = () => {
|
||||||
|
navigate(`/release-management/edit/${template.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTemplateCard>
|
<StyledTemplateCard onClick={onClick}>
|
||||||
<TemplateCardHeader>
|
<TemplateCardHeader>
|
||||||
<StyledCenter>
|
<StyledCenter>
|
||||||
<ReleaseTemplateIcon />
|
<ReleaseTemplateIcon />
|
||||||
@ -71,8 +77,16 @@ export const ReleasePlanTemplateCard = ({
|
|||||||
<StyledCreatedBy>
|
<StyledCreatedBy>
|
||||||
Created by {template.createdByUserId}
|
Created by {template.createdByUserId}
|
||||||
</StyledCreatedBy>
|
</StyledCreatedBy>
|
||||||
<StyledMenu onClick={(e) => e.preventDefault()}>
|
<StyledMenu
|
||||||
<ReleasePlanTemplateCardMenu template={template} />
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReleasePlanTemplateCardMenu
|
||||||
|
template={template}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
</StyledMenu>
|
</StyledMenu>
|
||||||
</StyledDiv>
|
</StyledDiv>
|
||||||
</TemplateCardBody>
|
</TemplateCardBody>
|
||||||
|
@ -16,7 +16,8 @@ import { TemplateDeleteDialog } from './TemplateDeleteDialog';
|
|||||||
|
|
||||||
export const ReleasePlanTemplateCardMenu = ({
|
export const ReleasePlanTemplateCardMenu = ({
|
||||||
template,
|
template,
|
||||||
}: { template: IReleasePlanTemplate }) => {
|
onClick,
|
||||||
|
}: { template: IReleasePlanTemplate; onClick: () => void }) => {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||||
const { deleteReleasePlanTemplate } = useReleasePlanTemplatesApi();
|
const { deleteReleasePlanTemplate } = useReleasePlanTemplatesApi();
|
||||||
@ -43,6 +44,7 @@ export const ReleasePlanTemplateCardMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuClick = (event: React.SyntheticEvent) => {
|
const handleMenuClick = (event: React.SyntheticEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
if (isMenuOpen) {
|
if (isMenuOpen) {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
} else {
|
} else {
|
||||||
@ -81,7 +83,7 @@ export const ReleasePlanTemplateCardMenu = ({
|
|||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeMenu();
|
onClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemText>Edit template</ListItemText>
|
<ListItemText>Edit template</ListItemText>
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { useReleasePlanTemplate } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplate';
|
||||||
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
|
import { useTemplateForm } from '../hooks/useTemplateForm';
|
||||||
|
import { TemplateForm } from './TemplateForm';
|
||||||
|
import { Box, Button, Card, styled } from '@mui/material';
|
||||||
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import { ADMIN } from '@server/types/permissions';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import useReleasePlanTemplatesApi from 'hooks/api/actions/useReleasePlanTemplatesApi/useReleasePlanTemplatesApi';
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMilestoneCard = styled(Card)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
transition: 'background-color 0.2s ease-in-out',
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.neutral.light,
|
||||||
|
},
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMilestoneCardBody = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(3, 2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMilestoneCardTitle = styled('span')(({ theme }) => ({
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(() => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const EditReleasePlanTemplate = () => {
|
||||||
|
const releasePlansEnabled = useUiFlag('releasePlans');
|
||||||
|
const templateId = useRequiredPathParam('templateId');
|
||||||
|
const { template, loading, error, refetch } =
|
||||||
|
useReleasePlanTemplate(templateId);
|
||||||
|
usePageTitle(`Edit template: ${template.name}`);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setToastApiError } = useToast();
|
||||||
|
const { updateReleasePlanTemplate } = useReleasePlanTemplatesApi();
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
setName,
|
||||||
|
description,
|
||||||
|
setDescription,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
validate,
|
||||||
|
getTemplatePayload,
|
||||||
|
} = useTemplateForm(template.name, template.description);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
navigate('/release-management');
|
||||||
|
};
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
clearErrors();
|
||||||
|
const isValid = validate();
|
||||||
|
if (isValid) {
|
||||||
|
const payload = getTemplatePayload();
|
||||||
|
try {
|
||||||
|
await updateReleasePlanTemplate({
|
||||||
|
...payload,
|
||||||
|
id: templateId,
|
||||||
|
milestones: template.milestones,
|
||||||
|
});
|
||||||
|
navigate('/release-management');
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!releasePlansEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormTemplate
|
||||||
|
title={`Edit template ${template.name}`}
|
||||||
|
description='Edit a release plan template that makes it easier for you and your team to release features.'
|
||||||
|
>
|
||||||
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
|
<TemplateForm
|
||||||
|
name={name}
|
||||||
|
setName={setName}
|
||||||
|
description={description}
|
||||||
|
setDescription={setDescription}
|
||||||
|
errors={errors}
|
||||||
|
clearErrors={clearErrors}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{template.milestones.map((milestone) => (
|
||||||
|
<StyledMilestoneCard key={milestone.id}>
|
||||||
|
<StyledMilestoneCardBody>
|
||||||
|
<StyledMilestoneCardTitle>
|
||||||
|
{milestone.name}
|
||||||
|
</StyledMilestoneCardTitle>
|
||||||
|
</StyledMilestoneCardBody>
|
||||||
|
</StyledMilestoneCard>
|
||||||
|
))}
|
||||||
|
<StyledButtonContainer>
|
||||||
|
<UpdateButton name='template' permission={ADMIN} />
|
||||||
|
<StyledCancelButton onClick={handleCancel}>
|
||||||
|
Cancel
|
||||||
|
</StyledCancelButton>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</StyledForm>
|
||||||
|
</FormTemplate>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
import Input from 'component/common/Input/Input';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface ITemplateForm {
|
||||||
|
name: string;
|
||||||
|
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
description: string;
|
||||||
|
setDescription: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
errors: { [key: string]: string };
|
||||||
|
clearErrors: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TemplateForm: React.FC<ITemplateForm> = ({
|
||||||
|
name,
|
||||||
|
setName,
|
||||||
|
description,
|
||||||
|
setDescription,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledInputDescription>
|
||||||
|
What would you like to call your template?
|
||||||
|
</StyledInputDescription>
|
||||||
|
<StyledInput
|
||||||
|
label='Template name'
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
error={Boolean(errors.name)}
|
||||||
|
errorText={errors.name}
|
||||||
|
onFocus={() => clearErrors()}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<StyledInputDescription>
|
||||||
|
What's the purpose of this template?
|
||||||
|
</StyledInputDescription>
|
||||||
|
<StyledInput
|
||||||
|
label='Template description (optional)'
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
error={Boolean(errors.description)}
|
||||||
|
errorText={errors.description}
|
||||||
|
onFocus={() => clearErrors()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
45
frontend/src/component/releases/hooks/useTemplateForm.ts
Normal file
45
frontend/src/component/releases/hooks/useTemplateForm.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useTemplateForm = (initialName = '', initialDescription = '') => {
|
||||||
|
const [name, setName] = useState(initialName);
|
||||||
|
const [description, setDescription] = useState(initialDescription);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(initialName);
|
||||||
|
}, [initialName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDescription(initialDescription);
|
||||||
|
}, [initialDescription]);
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (name.length === 0) {
|
||||||
|
setErrors((prev) => ({ ...prev, name: 'Name can not be empty.' }));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearErrors = () => {
|
||||||
|
setErrors({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTemplatePayload = () => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
setName,
|
||||||
|
description,
|
||||||
|
setDescription,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
validate,
|
||||||
|
getTemplatePayload,
|
||||||
|
};
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { IReleasePlanTemplatePayload } from 'interfaces/releasePlans';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
|
|
||||||
export const useReleasePlanTemplatesApi = () => {
|
export const useReleasePlanTemplatesApi = () => {
|
||||||
@ -7,16 +8,39 @@ export const useReleasePlanTemplatesApi = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const deleteReleasePlanTemplate = async (id: string) => {
|
const deleteReleasePlanTemplate = async (id: string) => {
|
||||||
|
const requestId = 'deleteReleasePlanTemplate';
|
||||||
const path = `api/admin/release-plan-templates/${id}`;
|
const path = `api/admin/release-plan-templates/${id}`;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(
|
||||||
method: 'DELETE',
|
path,
|
||||||
});
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
requestId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateReleasePlanTemplate = async (
|
||||||
|
template: IReleasePlanTemplatePayload,
|
||||||
|
) => {
|
||||||
|
const requestId = 'updateReleasePlanTemplate';
|
||||||
|
const path = `api/admin/release-plan-templates/${template.id}`;
|
||||||
|
const req = createRequest(
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(template),
|
||||||
|
},
|
||||||
|
requestId,
|
||||||
|
);
|
||||||
|
|
||||||
return makeRequest(req.caller, req.id);
|
return makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deleteReleasePlanTemplate,
|
deleteReleasePlanTemplate,
|
||||||
|
updateReleasePlanTemplate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||||
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
||||||
|
|
||||||
|
const path = (templateId: string) =>
|
||||||
|
`api/admin/release-plan-templates/${templateId}`;
|
||||||
|
|
||||||
|
const DEFAULT_DATA: IReleasePlanTemplate = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
milestones: [],
|
||||||
|
createdAt: '',
|
||||||
|
createdByUserId: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useReleasePlanTemplate = (templateId: string) => {
|
||||||
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const releasePlansEnabled = useUiFlag('releasePlans');
|
||||||
|
|
||||||
|
const { data, error, mutate } = useConditionalSWR<IReleasePlanTemplate>(
|
||||||
|
isEnterprise() && releasePlansEnabled,
|
||||||
|
DEFAULT_DATA,
|
||||||
|
formatApiPath(path(templateId)),
|
||||||
|
fetcher,
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
template: data ?? DEFAULT_DATA,
|
||||||
|
loading: !error && !data,
|
||||||
|
refetch: () => mutate(),
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
[data, error, mutate],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetcher = (path: string) => {
|
||||||
|
return fetch(path)
|
||||||
|
.then(handleErrorResponses('Release plan template'))
|
||||||
|
.then((res) => res.json());
|
||||||
|
};
|
@ -5,3 +5,24 @@ export interface IReleasePlanTemplate {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
createdByUserId: number;
|
createdByUserId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IReleasePlanTemplate {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdByUserId: number;
|
||||||
|
milestones: IReleasePlanMilestone[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReleasePlanMilestone {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReleasePlanTemplatePayload {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
milestones?: IReleasePlanMilestone[];
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user