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:
parent
c72f39bb4f
commit
857ee7da5c
@ -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)}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user