1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-19 17:52:45 +02:00

chore: release template resource limits (#10514)

https://linear.app/unleash/issue/2-3790/use-resource-limits-to-limit-the-amount-of-release-templates-you-can

Adds a new resource limit for release templates.

Needs a follow-up PR in Enterprise.
This commit is contained in:
Nuno Góis 2025-08-21 11:52:09 +01:00 committed by GitHub
parent d2452b91f2
commit edae8801d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 48 additions and 2 deletions

View File

@ -82,7 +82,7 @@ export const Limit: FC<{
const footerContent = isOss() ? ( const footerContent = isOss() ? (
<> <>
Need help with resource limits? Try the the{' '} Need help with resource limits? Try the{' '}
<a href='https://slack.unleash.run'>Unleash community Slack</a>. <a href='https://slack.unleash.run'>Unleash community Slack</a>.
</> </>
) : ( ) : (

View File

@ -13,6 +13,8 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Limit } from 'component/common/Limit/Limit.tsx';
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates.ts';
const StyledButtonContainer = styled('div')(() => ({ const StyledButtonContainer = styled('div')(() => ({
marginTop: 'auto', marginTop: 'auto',
@ -31,7 +33,12 @@ export const CreateReleasePlanTemplate = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { createReleasePlanTemplate } = useReleasePlanTemplatesApi(); const { createReleasePlanTemplate } = useReleasePlanTemplatesApi();
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const { templates } = useReleasePlanTemplates();
const releaseTemplateLimit = uiConfig.resourceLimits.releaseTemplates;
const canCreateMore = templates.length < releaseTemplateLimit;
usePageTitle('Create release template'); usePageTitle('Create release template');
const { const {
name, name,
setName, setName,
@ -102,11 +109,19 @@ export const CreateReleasePlanTemplate = () => {
formTitle='Create release template' formTitle='Create release template'
formatApiCode={formatApiCode} formatApiCode={formatApiCode}
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
Limit={
<Limit
name='release templates'
limit={releaseTemplateLimit}
currentValue={templates.length}
/>
}
> >
<StyledButtonContainer> <StyledButtonContainer>
<CreateButton <CreateButton
name='template' name='template'
permission={RELEASE_PLAN_TEMPLATE_CREATE} permission={RELEASE_PLAN_TEMPLATE_CREATE}
disabled={!canCreateMore}
> >
Save template Save template
</CreateButton> </CreateButton>

View File

@ -1,5 +1,5 @@
import Input from 'component/common/Input/Input'; import Input from 'component/common/Input/Input';
import { Alert, styled, useTheme } from '@mui/material'; import { Alert, Box, styled, useTheme } from '@mui/material';
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans'; import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { TemplateFormDescription } from './TemplateFormDescription.tsx'; import { TemplateFormDescription } from './TemplateFormDescription.tsx';
@ -33,6 +33,14 @@ const StyledForm = styled('form')(({ theme }) => ({
paddingTop: theme.spacing(5), paddingTop: theme.spacing(5),
})); }));
const StyledLimitContainer = styled(Box)(({ theme }) => ({
flex: 1,
display: 'flex',
alignItems: 'flex-end',
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
}));
interface ITemplateFormProps { interface ITemplateFormProps {
name: string; name: string;
setName: React.Dispatch<React.SetStateAction<string>>; setName: React.Dispatch<React.SetStateAction<string>>;
@ -49,6 +57,7 @@ interface ITemplateFormProps {
formatApiCode: () => string; formatApiCode: () => string;
handleSubmit: (e: React.FormEvent) => void; handleSubmit: (e: React.FormEvent) => void;
loading?: boolean; loading?: boolean;
Limit?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
} }
@ -65,6 +74,7 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
archived, archived,
formatApiCode, formatApiCode,
handleSubmit, handleSubmit,
Limit,
children, children,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
@ -135,6 +145,8 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
milestoneChanged={milestoneChanged} milestoneChanged={milestoneChanged}
/> />
<StyledLimitContainer>{Limit}</StyledLimitContainer>
{children} {children}
</StyledForm> </StyledForm>
</FormTemplate> </FormTemplate>

View File

@ -45,5 +45,6 @@ export const defaultValue: IUiConfig = {
segments: 300, segments: 300,
apiTokens: 2000, apiTokens: 2000,
featureFlags: 5000, featureFlags: 5000,
releaseTemplates: 5,
}, },
}; };

View File

@ -42,6 +42,8 @@ export interface ResourceLimitsSchema {
* @minimum 1 * @minimum 1
*/ */
projects: number; projects: number;
/** The maximum number of release templates allowed. */
releaseTemplates: number;
/** The maximum number of segments allowed. */ /** The maximum number of segments allowed. */
segments: number; segments: number;
/** The maximum number of values per segment allowed. */ /** The maximum number of values per segment allowed. */

View File

@ -121,6 +121,7 @@ exports[`should create default config 1`] = `
"featureEnvironmentStrategies": 30, "featureEnvironmentStrategies": 30,
"featureFlags": 5000, "featureFlags": 5000,
"projects": 500, "projects": 500,
"releaseTemplates": 5,
"segmentValues": 1000, "segmentValues": 1000,
"segments": 300, "segments": 300,
"signalEndpoints": 5, "signalEndpoints": 5,

View File

@ -758,6 +758,13 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
options?.resourceLimits?.featureFlags ?? 5000, options?.resourceLimits?.featureFlags ?? 5000,
), ),
), ),
releaseTemplates: Math.max(
0,
parseEnvVarNumber(
process.env.UNLEASH_RELEASE_TEMPLATES_LIMIT,
options?.resourceLimits?.releaseTemplates ?? 5,
),
),
}; };
const openAIAPIKey = process.env.OPENAI_API_KEY; const openAIAPIKey = process.env.OPENAI_API_KEY;

View File

@ -21,6 +21,7 @@ export const resourceLimitsSchema = {
'segments', 'segments',
'featureFlags', 'featureFlags',
'constraints', 'constraints',
'releaseTemplates',
], ],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@ -118,6 +119,11 @@ export const resourceLimitsSchema = {
description: description:
'The maximum number of feature flags you can have at the same time. Archived flags do not count towards this limit.', 'The maximum number of feature flags you can have at the same time. Archived flags do not count towards this limit.',
}, },
releaseTemplates: {
type: 'integer',
example: 5,
description: 'The maximum number of release templates allowed.',
},
}, },
components: {}, components: {},
} as const; } as const;

View File

@ -26,6 +26,7 @@ test('uiConfigSchema', () => {
segments: 0, segments: 0,
featureFlags: 1, featureFlags: 1,
constraints: 0, constraints: 0,
releaseTemplates: 0,
}, },
versionInfo: { versionInfo: {
current: {}, current: {},

View File

@ -135,6 +135,7 @@ export interface ResourceLimits {
actionSetFilterValues: number; actionSetFilterValues: number;
signalEndpoints: number; signalEndpoints: number;
signalTokensPerEndpoint: number; signalTokensPerEndpoint: number;
releaseTemplates: number;
} }
export interface IUnleashOptions { export interface IUnleashOptions {