mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: support private projects for event search (#7884)
Adds private projects support for event search. Unit test should cover the usecases.
This commit is contained in:
parent
a89f05181d
commit
183a9fc737
@ -19,7 +19,7 @@ import { normalizeQueryParams } from '../../features/feature-search/search-utils
|
|||||||
import Controller from '../../routes/controller';
|
import Controller from '../../routes/controller';
|
||||||
import type { IAuthRequest } from '../../server-impl';
|
import type { IAuthRequest } from '../../server-impl';
|
||||||
import type { IEvent } from '../../types';
|
import type { IEvent } from '../../types';
|
||||||
import { anonymiseKeys } from '../../util';
|
import { anonymiseKeys, extractUserIdFromUser } from '../../util';
|
||||||
|
|
||||||
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
||||||
const version = 1 as const;
|
const version = 1 as const;
|
||||||
@ -67,6 +67,7 @@ export default class EventSearchController extends Controller {
|
|||||||
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
|
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
|
||||||
res: Response<EventSearchResponseSchema>,
|
res: Response<EventSearchResponseSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { user } = req;
|
||||||
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
|
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
|
||||||
req.query,
|
req.query,
|
||||||
{
|
{
|
||||||
@ -75,11 +76,14 @@ export default class EventSearchController extends Controller {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { events, totalEvents } = await this.eventService.searchEvents({
|
const { events, totalEvents } = await this.eventService.searchEvents(
|
||||||
...req.query,
|
{
|
||||||
offset: normalizedOffset,
|
...req.query,
|
||||||
limit: normalizedLimit,
|
offset: normalizedOffset,
|
||||||
});
|
limit: normalizedLimit,
|
||||||
|
},
|
||||||
|
extractUserIdFromUser(user),
|
||||||
|
);
|
||||||
|
|
||||||
this.openApiService.respondWithValidation(
|
this.openApiService.respondWithValidation(
|
||||||
200,
|
200,
|
||||||
|
93
src/lib/features/events/event-service.test.ts
Normal file
93
src/lib/features/events/event-service.test.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import type { ProjectAccess } from '../private-project/privateProjectStore';
|
||||||
|
import { filterAccessibleProjects } from './event-service';
|
||||||
|
|
||||||
|
describe('filterPrivateProjectsFromParams', () => {
|
||||||
|
it('should return IS_ANY_OF with allowed projects when projectParam is undefined and mode is limited', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'limited',
|
||||||
|
projects: ['project1', 'project2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = undefined;
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBe('IS_ANY_OF:project1,project2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the original projectParam when mode is all', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'all',
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = 'IS:project3';
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBe(projectParam);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out projects not in allowedProjects when mode is limited', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'limited',
|
||||||
|
projects: ['project1', 'project2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = 'IS_ANY_OF:project1,project3';
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBe('IS_ANY_OF:project1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a single project if only one is allowed', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'limited',
|
||||||
|
projects: ['project1'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = 'IS_ANY_OF:project1,project2';
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBe('IS_ANY_OF:project1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if projectParam is undefined and projectAccess mode is all', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'all',
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = undefined;
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the original projectParam if all projects are allowed when mode is limited', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'limited',
|
||||||
|
projects: ['project1', 'project2', 'project3'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = 'IS_ANY_OF:project1,project2';
|
||||||
|
|
||||||
|
const result = filterAccessibleProjects(projectParam, projectAccess);
|
||||||
|
|
||||||
|
expect(result).toBe('IS_ANY_OF:project1,project2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if no projects match', () => {
|
||||||
|
const projectAccess: ProjectAccess = {
|
||||||
|
mode: 'limited',
|
||||||
|
projects: ['project1', 'project2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectParam = 'IS_ANY_OF:project3,project4';
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
filterAccessibleProjects(projectParam, projectAccess),
|
||||||
|
).toThrow('No accessible projects in the search parameters');
|
||||||
|
});
|
||||||
|
});
|
@ -15,6 +15,7 @@ import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strateg
|
|||||||
import { parseSearchOperatorValue } from '../feature-search/search-utils';
|
import { parseSearchOperatorValue } from '../feature-search/search-utils';
|
||||||
import { endOfDay, formatISO } from 'date-fns';
|
import { endOfDay, formatISO } from 'date-fns';
|
||||||
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
||||||
|
import type { ProjectAccess } from '../private-project/privateProjectStore';
|
||||||
|
|
||||||
export default class EventService {
|
export default class EventService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -63,8 +64,20 @@ export default class EventService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchEvents(search: IEventSearchParams): Promise<IEventList> {
|
async searchEvents(
|
||||||
|
search: IEventSearchParams,
|
||||||
|
userId: number,
|
||||||
|
): Promise<IEventList> {
|
||||||
|
const projectAccess =
|
||||||
|
await this.privateProjectChecker.getUserAccessibleProjects(userId);
|
||||||
|
|
||||||
|
search.project = filterAccessibleProjects(
|
||||||
|
search.project,
|
||||||
|
projectAccess,
|
||||||
|
);
|
||||||
|
|
||||||
const queryParams = this.convertToDbParams(search);
|
const queryParams = this.convertToDbParams(search);
|
||||||
|
|
||||||
const totalEvents = await this.eventStore.searchEventsCount(
|
const totalEvents = await this.eventStore.searchEventsCount(
|
||||||
{
|
{
|
||||||
limit: search.limit,
|
limit: search.limit,
|
||||||
@ -210,3 +223,35 @@ export default class EventService {
|
|||||||
return this.eventStore.getEventCreators();
|
return this.eventStore.getEventCreators();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const filterAccessibleProjects = (
|
||||||
|
projectParam: string | undefined,
|
||||||
|
projectAccess: ProjectAccess,
|
||||||
|
): string | undefined => {
|
||||||
|
if (projectAccess.mode !== 'all') {
|
||||||
|
const allowedProjects = projectAccess.projects;
|
||||||
|
|
||||||
|
if (!projectParam) {
|
||||||
|
return `IS_ANY_OF:${allowedProjects.join(',')}`;
|
||||||
|
} else {
|
||||||
|
const searchProjectList = projectParam.split(',');
|
||||||
|
const filteredProjects = searchProjectList
|
||||||
|
.filter((proj) =>
|
||||||
|
allowedProjects.includes(
|
||||||
|
proj.replace(/^(IS|IS_ANY_OF):/, ''),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
if (!filteredProjects) {
|
||||||
|
throw new Error(
|
||||||
|
'No accessible projects in the search parameters',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredProjects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectParam;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user