1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01: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:
Jaanus Sellin 2024-08-15 12:51:04 +03:00 committed by GitHub
parent a89f05181d
commit 183a9fc737
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 149 additions and 7 deletions

View File

@ -19,7 +19,7 @@ import { normalizeQueryParams } from '../../features/feature-search/search-utils
import Controller from '../../routes/controller';
import type { IAuthRequest } from '../../server-impl';
import type { IEvent } from '../../types';
import { anonymiseKeys } from '../../util';
import { anonymiseKeys, extractUserIdFromUser } from '../../util';
const ANON_KEYS = ['email', 'username', 'createdBy'];
const version = 1 as const;
@ -67,6 +67,7 @@ export default class EventSearchController extends Controller {
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
res: Response<EventSearchResponseSchema>,
): Promise<void> {
const { user } = req;
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
req.query,
{
@ -75,11 +76,14 @@ export default class EventSearchController extends Controller {
},
);
const { events, totalEvents } = await this.eventService.searchEvents({
...req.query,
offset: normalizedOffset,
limit: normalizedLimit,
});
const { events, totalEvents } = await this.eventService.searchEvents(
{
...req.query,
offset: normalizedOffset,
limit: normalizedLimit,
},
extractUserIdFromUser(user),
);
this.openApiService.respondWithValidation(
200,

View 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');
});
});

View File

@ -15,6 +15,7 @@ import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strateg
import { parseSearchOperatorValue } from '../feature-search/search-utils';
import { endOfDay, formatISO } from 'date-fns';
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import type { ProjectAccess } from '../private-project/privateProjectStore';
export default class EventService {
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 totalEvents = await this.eventStore.searchEventsCount(
{
limit: search.limit,
@ -210,3 +223,35 @@ export default class EventService {
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;
};