mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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