1
0
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.


![image](https://github.com/user-attachments/assets/529bf306-b545-4efa-8330-afc19782765a)
This commit is contained in:
Nuno Góis 2024-11-27 13:16:27 +00:00 committed by GitHub
parent 9044d4c537
commit 679e9d12ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 142 additions and 23 deletions

View 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

View File

@ -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>

View File

@ -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}
>

View File

@ -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>

View File

@ -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>
);
};

View File

@ -42,6 +42,7 @@ export const useTemplateForm = (
return {
name,
description,
milestones,
};
};

View File

@ -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,
{

View File

@ -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 {