Skip to content

Commit

Permalink
cherry-pick #5304 (#5310)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaanus authored Nov 9, 2023
1 parent d44e00f commit 3b23719
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React, { useContext } from 'react';
import { CreateButton } from 'component/common/CreateButton/CreateButton';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
import {
CREATE_SEGMENT,
UPDATE_PROJECT_SEGMENT,
} from 'component/providers/AccessProvider/permissions';
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
Expand Down Expand Up @@ -112,7 +115,8 @@ export const CreateSegment = ({ modal }: ICreateSegmentProps) => {
>
<CreateButton
name='segment'
permission={CREATE_SEGMENT}
permission={[CREATE_SEGMENT, UPDATE_PROJECT_SEGMENT]}
projectId={projectId}
disabled={!hasValidConstraints || overSegmentValuesLimit}
data-testid={SEGMENT_CREATE_BTN_ID}
/>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/component/segments/EditSegment/EditSegment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { UPDATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
import {
UPDATE_PROJECT_SEGMENT,
UPDATE_SEGMENT,
} from 'component/providers/AccessProvider/permissions';
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation';
import { useSegment } from 'hooks/api/getters/useSegment/useSegment';
Expand Down Expand Up @@ -135,7 +138,8 @@ export const EditSegment = ({ modal }: IEditSegmentProps) => {
mode='edit'
>
<UpdateButton
permission={UPDATE_SEGMENT}
permission={[UPDATE_SEGMENT, UPDATE_PROJECT_SEGMENT]}
projectId={projectId}
disabled={!hasValidConstraints || overSegmentValuesLimit}
data-testid={SEGMENT_SAVE_BTN_ID}
>
Expand Down
82 changes: 81 additions & 1 deletion frontend/src/hooks/api/actions/useApi/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type ApiErrorHandler = (
requestId: string,
) => void;

type ApiCaller = () => Promise<Response>;
type RequestFunction = (
apiCaller: ApiCaller,
requestId: string,
loadingOn?: boolean,
) => Promise<Response>;

interface IUseAPI {
handleBadRequest?: ApiErrorHandler;
handleNotFound?: ApiErrorHandler;
Expand All @@ -33,6 +40,29 @@ interface IUseAPI {
propagateErrors?: boolean;
}

const timeApiCallStart = (requestId: string) => {
// Store the start time in milliseconds
console.log(`Starting timing for request: ${requestId}`);
return Date.now();
};

const timeApiCallEnd = (startTime: number, requestId: string) => {
// Calculate the end time and subtract the start time
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`Timing for request ${requestId}: ${duration} ms`);

if (duration > 500) {
console.error(
'API call took over 500ms. This may indicate a rendering performance problem in your React component.',
requestId,
duration,
);
}

return duration;
};

const useAPI = ({
handleBadRequest,
handleNotFound,
Expand Down Expand Up @@ -157,6 +187,27 @@ const useAPI = ({
],
);

const requestWithTimer = (requestFunction: RequestFunction) => {
return async (
apiCaller: () => Promise<Response>,
requestId: string,
loadingOn: boolean = true,
) => {
const start = timeApiCallStart(
requestId || `Unknown request happening on ${apiCaller}`,
);

const res = await requestFunction(apiCaller, requestId, loadingOn);

timeApiCallEnd(
start,
requestId || `Unknown request happening on ${apiCaller}`,
);

return res;
};
};

const makeRequest = useCallback(
async (
apiCaller: () => Promise<Response>,
Expand Down Expand Up @@ -187,6 +238,27 @@ const useAPI = ({
[handleResponses],
);

const makeLightRequest = useCallback(
async (
apiCaller: () => Promise<Response>,
requestId: string,
loadingOn: boolean = true,
): Promise<Response> => {
try {
const res = await apiCaller();

if (!res.ok) {
throw new Error();
}

return res;
} catch (e) {
throw new Error('Could not make request | makeLightRequest');
}
},
[],
);

const createRequest = useCallback(
(path: string, options: any, requestId: string = '') => {
const defaultOptions: RequestInit = {
Expand All @@ -207,9 +279,17 @@ const useAPI = ({
[],
);

const makeRequestWithTimer = requestWithTimer(makeRequest);
const makeLightRequestWithTimer = requestWithTimer(makeLightRequest);

const isDevelopment = process.env.NODE_ENV === 'development';

return {
loading,
makeRequest,
makeRequest: isDevelopment ? makeRequestWithTimer : makeRequest,
makeLightRequest: isDevelopment
? makeLightRequestWithTimer
: makeLightRequest,
createRequest,
errors,
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib/error/permission-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PermissionError extends UnleashError {
const permissionsMessage =
permissions.length === 1
? `the "${permissions[0]}" permission`
: `all of the following permissions: ${permissions
: `one of the following permissions: ${permissions
.map((perm) => `"${perm}"`)
.join(', ')}`;

Expand Down
7 changes: 4 additions & 3 deletions src/lib/features/segment/segment-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
IFlagResolver,
NONE,
UPDATE_FEATURE_STRATEGY,
UPDATE_PROJECT_SEGMENT,
UPDATE_SEGMENT,
serializeDates,
} from '../../types';
Expand Down Expand Up @@ -165,7 +166,7 @@ export class SegmentsController extends Controller {
method: 'delete',
path: '/:id',
handler: this.removeSegment,
permission: DELETE_SEGMENT,
permission: [DELETE_SEGMENT, UPDATE_PROJECT_SEGMENT],
acceptAnyContentType: true,
middleware: [
openApiService.validPath({
Expand All @@ -186,7 +187,7 @@ export class SegmentsController extends Controller {
method: 'put',
path: '/:id',
handler: this.updateSegment,
permission: UPDATE_SEGMENT,
permission: [UPDATE_SEGMENT, UPDATE_PROJECT_SEGMENT],
middleware: [
openApiService.validPath({
summary: 'Update segment by id',
Expand Down Expand Up @@ -225,7 +226,7 @@ export class SegmentsController extends Controller {
method: 'post',
path: '',
handler: this.createSegment,
permission: CREATE_SEGMENT,
permission: [CREATE_SEGMENT, UPDATE_PROJECT_SEGMENT],
middleware: [
openApiService.validPath({
summary: 'Create a new segment',
Expand Down
82 changes: 69 additions & 13 deletions src/lib/middleware/rbac-middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import ApiUser from '../types/api-user';
import { IFeatureToggleStore } from '../features/feature-toggle/types/feature-toggle-store-type';
import FakeFeatureToggleStore from '../features/feature-toggle/fakes/fake-feature-toggle-store';
import { ApiTokenType } from '../types/models/api-token';
import { ISegmentStore } from '../types';
import FakeSegmentStore from '../../test/fixtures/fake-segment-store';

let config: IUnleashConfig;
let featureToggleStore: IFeatureToggleStore;
let segmentStore: ISegmentStore;

beforeEach(() => {
featureToggleStore = new FakeFeatureToggleStore();
segmentStore = new FakeSegmentStore();
config = createTestConfig();
});

Expand All @@ -21,7 +25,11 @@ test('should add checkRbac to request', () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();

Expand All @@ -40,7 +48,11 @@ test('should give api-user ADMIN permission', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand All @@ -66,7 +78,11 @@ test('should not give api-user ADMIN permission', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down Expand Up @@ -94,7 +110,11 @@ test('should not allow user to miss userId', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand All @@ -116,7 +136,11 @@ test('should return false for missing user', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {};
Expand All @@ -134,7 +158,11 @@ test('should verify permission for root resource', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down Expand Up @@ -163,7 +191,11 @@ test('should lookup projectId from params', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down Expand Up @@ -198,7 +230,11 @@ test('should lookup projectId from feature toggle', async () => {

featureToggleStore.getProjectId = jest.fn().mockReturnValue(projectId);

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down Expand Up @@ -231,7 +267,11 @@ test('should lookup projectId from data', async () => {
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down Expand Up @@ -266,7 +306,11 @@ test('Does not double check permission if not changing project when updating tog
};
featureToggleStore.getProjectId = jest.fn().mockReturnValue(oldProjectId);

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new User({ username: 'user', id: 1 }),
Expand All @@ -290,7 +334,11 @@ test('UPDATE_TAG_TYPE does not need projectId', async () => {
hasPermission: jest.fn().mockReturnValue(true),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new User({ username: 'user', id: 1 }),
Expand All @@ -314,7 +362,11 @@ test('DELETE_TAG_TYPE does not need projectId', async () => {
hasPermission: jest.fn().mockReturnValue(true),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new User({ username: 'user', id: 1 }),
Expand All @@ -340,7 +392,11 @@ test('should not expect featureName for UPDATE_FEATURE when projectId specified'
hasPermission: jest.fn(),
};

const func = rbacMiddleware(config, { featureToggleStore }, accessService);
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);

const cb = jest.fn();
const req: any = {
Expand Down
Loading

0 comments on commit 3b23719

Please sign in to comment.