1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: prevent more than 10 links in the UI and backend (#9937)

This commit is contained in:
Mateusz Kwasniewski 2025-05-08 21:21:28 +02:00 committed by GitHub
parent c72f39bb4f
commit 857ee7da5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 2 deletions

View File

@ -117,6 +117,7 @@ const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
size='small'
startIcon={<AddIcon />}
permission={UPDATE_FEATURE}
disabled={links.length >= 10}
projectId={project}
variant='text'
onClick={() => setShowAddLinkDialogue(true)}

View File

@ -7,6 +7,20 @@ import type {
export default class FakeFeatureLinkStore implements IFeatureLinkStore {
private links: IFeatureLink[] = [];
async count(query?: Partial<Omit<IFeatureLink, 'id'>>): Promise<number> {
if (!query) {
return this.links.length;
}
const filteredLinks = this.links.filter((link) => {
return Object.entries(query).every(([key, value]) => {
return link[key] === value;
});
});
return filteredLinks.length;
}
async insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink> {
const newLink: IFeatureLink = {
...link,

View File

@ -1,7 +1,7 @@
import { createFakeFeatureLinkService } from './createFeatureLinkService';
import type { IAuditUser, IUnleashConfig } from '../../types';
import getLogger from '../../../test/fixtures/no-logger';
import { BadDataError, NotFoundError } from '../../error';
import { BadDataError, NotFoundError, OperationDeniedError } from '../../error';
test('create, update and delete feature link', async () => {
const { featureLinkStore, featureLinkService } =
@ -101,3 +101,33 @@ test('cannot create/update invalid link', async () => {
),
).rejects.toThrow(BadDataError);
});
test('cannot exceed allowed link count', async () => {
const { featureLinkService } = createFakeFeatureLinkService({
getLogger,
} as unknown as IUnleashConfig);
for (let i = 0; i < 10; i++) {
await featureLinkService.createLink(
'default',
{
featureName: 'feature',
url: 'example.com',
title: 'some title',
},
{} as IAuditUser,
);
}
await expect(
featureLinkService.createLink(
'default',
{
featureName: 'feature',
url: 'example.com',
title: 'some title',
},
{} as IAuditUser,
),
).rejects.toThrow(OperationDeniedError);
});

View File

@ -11,7 +11,7 @@ import type {
IFeatureLinkStore,
} from './feature-link-store-type';
import type EventService from '../events/event-service';
import { BadDataError, NotFoundError } from '../../error';
import { BadDataError, NotFoundError, OperationDeniedError } from '../../error';
import normalizeUrl from 'normalize-url';
import { parse } from 'tldts';
@ -51,6 +51,14 @@ export default class FeatureLinkService {
newLink: Omit<IFeatureLink, 'id' | 'domain'>,
auditUser: IAuditUser,
): Promise<IFeatureLink> {
const countLinks = await this.featureLinkStore.count({
featureName: newLink.featureName,
});
if (countLinks >= 10) {
throw new OperationDeniedError(
'Too many links (10) exist for this feature',
);
}
const normalizedUrl = this.normalize(newLink.url);
const { domainWithoutSuffix } = parse(normalizedUrl);

View File

@ -11,4 +11,5 @@ export interface IFeatureLink {
export interface IFeatureLinkStore extends Store<IFeatureLink, string> {
insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
update(id: string, link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
count(query?: Partial<Omit<IFeatureLink, 'id'>>): Promise<number>;
}