mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-31 01:16:01 +02:00
chore: release template sidebar (#8871)
https://linear.app/unleash/issue/2-3026/release-template-sidebar-documentationcommand Implements the release plan template form sidebar / description. Took some liberties in the text compared to what we had in our sketches. Also includes some slight refactoring. 
This commit is contained in:
parent
9044d4c537
commit
679e9d12ef
3
frontend/src/assets/icons/milestone.svg
Normal file
3
frontend/src/assets/icons/milestone.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.48149 0.771598C5.27968 -0.159625 6.72032 -0.159624 7.51851 0.771599L10.8844 4.69842C11.5263 5.4474 11.5263 6.5526 10.8844 7.30158L7.51851 11.2284C6.72032 12.1596 5.27968 12.1596 4.48149 11.2284L1.11564 7.30158C0.47366 6.5526 0.47366 5.4474 1.11564 4.69842L4.48149 0.771598Z" fill="#FFC46F"/>
|
||||
</svg>
|
After Width: | Height: | Size: 407 B |
@ -11,6 +11,7 @@ import { scrollToTop } from 'component/common/util';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledButtonContainer = styled('div')(() => ({
|
||||
marginTop: 'auto',
|
||||
@ -23,6 +24,7 @@ const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const CreateReleasePlanTemplate = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const releasePlansEnabled = useUiFlag('releasePlans');
|
||||
const { setToastApiError, setToastData } = useToast();
|
||||
const navigate = useNavigate();
|
||||
@ -50,12 +52,10 @@ export const CreateReleasePlanTemplate = () => {
|
||||
clearErrors();
|
||||
const isValid = validate();
|
||||
if (isValid) {
|
||||
const payload = getTemplatePayload();
|
||||
try {
|
||||
const template = await createReleasePlanTemplate({
|
||||
...payload,
|
||||
milestones,
|
||||
});
|
||||
const template = await createReleasePlanTemplate(
|
||||
getTemplatePayload(),
|
||||
);
|
||||
scrollToTop();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
@ -68,6 +68,13 @@ export const CreateReleasePlanTemplate = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatApiCode = () => `curl --location --request POST '${
|
||||
uiConfig.unleashUrl
|
||||
}/api/admin/release-plan-templates' \\
|
||||
--header 'Authorization: INSERT_API_KEY' \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--data-raw '${JSON.stringify(getTemplatePayload(), undefined, 2)}'`;
|
||||
|
||||
if (!releasePlansEnabled) {
|
||||
return null;
|
||||
}
|
||||
@ -83,7 +90,7 @@ export const CreateReleasePlanTemplate = () => {
|
||||
errors={errors}
|
||||
clearErrors={clearErrors}
|
||||
formTitle='Create release plan template'
|
||||
formDescription='Create a release plan template to make it easier for you and your team to release features.'
|
||||
formatApiCode={formatApiCode}
|
||||
handleSubmit={handleSubmit}
|
||||
>
|
||||
<StyledButtonContainer>
|
||||
|
@ -11,6 +11,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import useToast from 'hooks/useToast';
|
||||
import useReleasePlanTemplatesApi from 'hooks/api/actions/useReleasePlanTemplatesApi/useReleasePlanTemplatesApi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledButtonContainer = styled('div')(() => ({
|
||||
marginTop: 'auto',
|
||||
@ -23,6 +24,7 @@ const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const EditReleasePlanTemplate = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const releasePlansEnabled = useUiFlag('releasePlans');
|
||||
const templateId = useRequiredPathParam('templateId');
|
||||
const { template, loading, error, refetch } =
|
||||
@ -56,13 +58,11 @@ export const EditReleasePlanTemplate = () => {
|
||||
clearErrors();
|
||||
const isValid = validate();
|
||||
if (isValid) {
|
||||
const payload = getTemplatePayload();
|
||||
try {
|
||||
await updateReleasePlanTemplate({
|
||||
...payload,
|
||||
id: templateId,
|
||||
milestones,
|
||||
});
|
||||
await updateReleasePlanTemplate(
|
||||
templateId,
|
||||
getTemplatePayload(),
|
||||
);
|
||||
await refetch();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
@ -74,6 +74,13 @@ export const EditReleasePlanTemplate = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatApiCode = () => `curl --location --request PUT '${
|
||||
uiConfig.unleashUrl
|
||||
}/api/admin/release-plan-templates/${templateId}' \\
|
||||
--header 'Authorization: INSERT_API_KEY' \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--data-raw '${JSON.stringify(getTemplatePayload(), undefined, 2)}'`;
|
||||
|
||||
if (!releasePlansEnabled) {
|
||||
return null;
|
||||
}
|
||||
@ -89,7 +96,7 @@ export const EditReleasePlanTemplate = () => {
|
||||
errors={errors}
|
||||
clearErrors={clearErrors}
|
||||
formTitle={`Edit template ${template.name}`}
|
||||
formDescription='Edit a release plan template that makes it easier for you and your team to release features.'
|
||||
formatApiCode={formatApiCode}
|
||||
handleSubmit={handleSubmit}
|
||||
loading={loading}
|
||||
>
|
||||
|
@ -6,10 +6,10 @@ import type {
|
||||
IReleasePlanMilestoneStrategy,
|
||||
} from 'interfaces/releasePlans';
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import ReleaseTemplateIcon from '@mui/icons-material/DashboardOutlined';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { useState } from 'react';
|
||||
import { ReleasePlanTemplateAddStrategyForm } from './ReleasePlanTemplateAddStrategyForm';
|
||||
import { TemplateFormDescription } from './TemplateFormDescription';
|
||||
|
||||
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
@ -38,7 +38,7 @@ interface ITemplateFormProps {
|
||||
errors: { [key: string]: string };
|
||||
clearErrors: () => void;
|
||||
formTitle: string;
|
||||
formDescription: string;
|
||||
formatApiCode: () => string;
|
||||
handleSubmit: (e: React.FormEvent) => void;
|
||||
loading?: boolean;
|
||||
children?: React.ReactNode;
|
||||
@ -54,7 +54,7 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
||||
errors,
|
||||
clearErrors,
|
||||
formTitle,
|
||||
formDescription,
|
||||
formatApiCode,
|
||||
handleSubmit,
|
||||
children,
|
||||
}) => {
|
||||
@ -115,8 +115,8 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
||||
return (
|
||||
<FormTemplate
|
||||
title={formTitle}
|
||||
description={formDescription}
|
||||
documentationIcon={<ReleaseTemplateIcon />}
|
||||
description={<TemplateFormDescription />}
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledInputDescription>
|
||||
|
@ -0,0 +1,98 @@
|
||||
import ReleaseTemplateIcon from '@mui/icons-material/DashboardOutlined';
|
||||
import { ReactComponent as MilestoneIcon } from 'assets/icons/milestone.svg';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const StyledDescription = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledDescriptionHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
}));
|
||||
|
||||
const StyledExampleUsage = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledMilestones = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledLabel = styled('p')(({ theme }) => ({
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
}));
|
||||
|
||||
const StyledMilestoneHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const TemplateFormDescription = () => {
|
||||
return (
|
||||
<StyledDescription>
|
||||
<StyledDescriptionHeader>
|
||||
<ReleaseTemplateIcon />
|
||||
Release templates
|
||||
</StyledDescriptionHeader>
|
||||
<p>
|
||||
Standardize your team's approach to rolling out new
|
||||
functionality with release templates. These templates allow you
|
||||
to predefine strategies, or groups of strategies, making it
|
||||
easier to set up new flags and ensure alignment in how rollouts
|
||||
are managed.
|
||||
</p>
|
||||
<p>
|
||||
Customize templates to suit your needs by adding strategies to
|
||||
specific milestones. Each milestone will execute sequentially,
|
||||
streamlining your release process.
|
||||
</p>
|
||||
<StyledExampleUsage>
|
||||
<StyledLabel>Example usage</StyledLabel>
|
||||
<StyledMilestones>
|
||||
<div>
|
||||
<StyledMilestoneHeader>
|
||||
<MilestoneIcon />
|
||||
Milestone 1
|
||||
</StyledMilestoneHeader>
|
||||
<p>
|
||||
Enable the feature for internal teams to test
|
||||
functionality and resolve initial issues.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<StyledMilestoneHeader>
|
||||
<MilestoneIcon />
|
||||
Milestone 2
|
||||
</StyledMilestoneHeader>
|
||||
<p>
|
||||
Expand the rollout to 20% of beta users to gather
|
||||
feedback and monitor performance.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<StyledMilestoneHeader>
|
||||
<MilestoneIcon />
|
||||
Milestone 3
|
||||
</StyledMilestoneHeader>
|
||||
<p>
|
||||
Release the feature to all users after confirming
|
||||
stability and addressing earlier feedback.
|
||||
</p>
|
||||
</div>
|
||||
</StyledMilestones>
|
||||
</StyledExampleUsage>
|
||||
</StyledDescription>
|
||||
);
|
||||
};
|
@ -42,6 +42,7 @@ export const useTemplateForm = (
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
milestones,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import type { IReleasePlanTemplatePayload } from 'interfaces/releasePlans';
|
||||
import type {
|
||||
IReleasePlanTemplate,
|
||||
IReleasePlanTemplatePayload,
|
||||
} from 'interfaces/releasePlans';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
export const useReleasePlanTemplatesApi = () => {
|
||||
@ -23,7 +26,7 @@ export const useReleasePlanTemplatesApi = () => {
|
||||
|
||||
const createReleasePlanTemplate = async (
|
||||
template: IReleasePlanTemplatePayload,
|
||||
): Promise<IReleasePlanTemplatePayload> => {
|
||||
): Promise<IReleasePlanTemplate> => {
|
||||
const requestId = 'createReleasePlanTemplate';
|
||||
const path = 'api/admin/release-plan-templates';
|
||||
const req = createRequest(
|
||||
@ -40,10 +43,11 @@ export const useReleasePlanTemplatesApi = () => {
|
||||
};
|
||||
|
||||
const updateReleasePlanTemplate = async (
|
||||
templateId: string,
|
||||
template: IReleasePlanTemplatePayload,
|
||||
) => {
|
||||
const requestId = 'updateReleasePlanTemplate';
|
||||
const path = `api/admin/release-plan-templates/${template.id}`;
|
||||
const path = `api/admin/release-plan-templates/${templateId}`;
|
||||
const req = createRequest(
|
||||
path,
|
||||
{
|
||||
|
@ -41,10 +41,9 @@ export interface IReleasePlanMilestoneStrategy extends IFeatureStrategy {
|
||||
}
|
||||
|
||||
export interface IReleasePlanTemplatePayload {
|
||||
id?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
milestones?: IReleasePlanMilestonePayload[];
|
||||
milestones: IReleasePlanMilestonePayload[];
|
||||
}
|
||||
|
||||
export interface IReleasePlanMilestonePayload {
|
||||
|
Loading…
Reference in New Issue
Block a user