mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-28 17:55:15 +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'
|
size='small'
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
|
disabled={links.length >= 10}
|
||||||
projectId={project}
|
projectId={project}
|
||||||
variant='text'
|
variant='text'
|
||||||
onClick={() => setShowAddLinkDialogue(true)}
|
onClick={() => setShowAddLinkDialogue(true)}
|
||||||
|
@ -7,6 +7,20 @@ import type {
|
|||||||
export default class FakeFeatureLinkStore implements IFeatureLinkStore {
|
export default class FakeFeatureLinkStore implements IFeatureLinkStore {
|
||||||
private links: IFeatureLink[] = [];
|
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> {
|
async insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink> {
|
||||||
const newLink: IFeatureLink = {
|
const newLink: IFeatureLink = {
|
||||||
...link,
|
...link,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createFakeFeatureLinkService } from './createFeatureLinkService';
|
import { createFakeFeatureLinkService } from './createFeatureLinkService';
|
||||||
import type { IAuditUser, IUnleashConfig } from '../../types';
|
import type { IAuditUser, IUnleashConfig } from '../../types';
|
||||||
import getLogger from '../../../test/fixtures/no-logger';
|
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 () => {
|
test('create, update and delete feature link', async () => {
|
||||||
const { featureLinkStore, featureLinkService } =
|
const { featureLinkStore, featureLinkService } =
|
||||||
@ -101,3 +101,33 @@ test('cannot create/update invalid link', async () => {
|
|||||||
),
|
),
|
||||||
).rejects.toThrow(BadDataError);
|
).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,
|
IFeatureLinkStore,
|
||||||
} from './feature-link-store-type';
|
} from './feature-link-store-type';
|
||||||
import type EventService from '../events/event-service';
|
import type EventService from '../events/event-service';
|
||||||
import { BadDataError, NotFoundError } from '../../error';
|
import { BadDataError, NotFoundError, OperationDeniedError } from '../../error';
|
||||||
import normalizeUrl from 'normalize-url';
|
import normalizeUrl from 'normalize-url';
|
||||||
import { parse } from 'tldts';
|
import { parse } from 'tldts';
|
||||||
|
|
||||||
@ -51,6 +51,14 @@ export default class FeatureLinkService {
|
|||||||
newLink: Omit<IFeatureLink, 'id' | 'domain'>,
|
newLink: Omit<IFeatureLink, 'id' | 'domain'>,
|
||||||
auditUser: IAuditUser,
|
auditUser: IAuditUser,
|
||||||
): Promise<IFeatureLink> {
|
): 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 normalizedUrl = this.normalize(newLink.url);
|
||||||
const { domainWithoutSuffix } = parse(normalizedUrl);
|
const { domainWithoutSuffix } = parse(normalizedUrl);
|
||||||
|
|
||||||
|
@ -11,4 +11,5 @@ export interface IFeatureLink {
|
|||||||
export interface IFeatureLinkStore extends Store<IFeatureLink, string> {
|
export interface IFeatureLinkStore extends Store<IFeatureLink, string> {
|
||||||
insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
|
insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
|
||||||
update(id: string, 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