Skip to content

Commit e6d6440

Browse files
Typing permissions in frontend. (#24511)
* Typing permissions. * Improving type for permissions. * Improving typing for `IfPermitted`, cleaning up. * Improving typing for `usePermissions`, cleaning up. * Improving types for permission helpers. * Fixing typing fallout. * Fixing typing fallout pt. II. * Fixing permission that was previously wrong. * Adding missing entities/actions. * Typing more attributes. * Relaxing setter. --------- Co-authored-by: Mohamed OULD HOCINE <106236152+gally47@users.noreply.github.com>
1 parent 4ce2c41 commit e6d6440

File tree

37 files changed

+353
-302
lines changed

37 files changed

+353
-302
lines changed

graylog2-web-interface/src/@types/graylog-web-plugin/index.d.ts

Lines changed: 180 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -28,39 +28,10 @@ import type { StepType } from 'components/common/Wizard';
2828
import type { InputSetupWizardStep } from 'components/inputs/InputSetupWizard';
2929
import type { TelemetryEventType } from 'logic/telemetry/TelemetryContext';
3030

31-
interface PluginRoute {
32-
path: string;
33-
component: React.ComponentType;
34-
parentComponent?: React.ComponentType | null;
35-
permissions?: string | Array<string>;
36-
requiredFeatureFlag?: string;
37-
}
38-
39-
interface PluginNavigationDropdownItem {
40-
description: string;
41-
path: QualifiedUrl<string>;
42-
permissions?: string | Array<string>;
43-
requiredFeatureFlag?: string;
44-
}
45-
4631
type PluginNavigationLink = {
4732
path: QualifiedUrl<string>;
4833
};
4934

50-
type PluginNavigationDropdown = {
51-
children: Array<PluginNavigationDropdownItem>;
52-
};
53-
54-
type PluginNavigation = {
55-
description: string;
56-
requiredFeatureFlag?: string;
57-
perspective?: string;
58-
BadgeComponent?: React.ComponentType<{ text: string }>;
59-
position?: { last: true } | { after: string } | undefined;
60-
permissions?: string | Array<string>;
61-
useCondition?: () => boolean;
62-
} & (PluginNavigationLink | PluginNavigationDropdown);
63-
6435
interface PluginNavigationItems {
6536
key: string;
6637
component: React.ComponentType<{ smallScreen?: boolean }>;
@@ -210,79 +181,11 @@ export type FieldValueProvider = {
210181
requiredFields: string[];
211182
};
212183

213-
interface PluginDataLake {
214-
StreamDataLake: React.ComponentType<{
215-
permissions: Immutable.List<string>;
216-
}>;
217-
DataLakeStatus: React.ComponentType<{
218-
dataLakeEnabled: boolean;
219-
}>;
220-
DataLakeJournal: React.ComponentType<{
221-
nodeId: string;
222-
}>;
223-
DataLakeJobs: React.ComponentType<{
224-
permissions: Immutable.List<string>;
225-
streamId: string;
226-
}>;
227-
StreamIlluminateProcessingSection: React.ComponentType<{
228-
stream: Stream;
229-
}>;
230-
StreamIndexSetDataLakeWarning: React.ComponentType<{ streamId: string; isArchivingEnabled: boolean }>;
231-
fetchStreamDataLakeStatus: (streamId: string) => Promise<{
232-
id: string;
233-
archive_name: string;
234-
enabled: boolean;
235-
stream_id: string;
236-
retention_time: number;
237-
}>;
238-
fetchStreamDataLake: (streamId: string) => Promise<{
239-
id: string;
240-
archive_config_id: string;
241-
message_count: number;
242-
archive_name: string;
243-
timestamp_from: string;
244-
timestamp_to: string;
245-
restore_history: Array<{ id: string }>;
246-
}>;
247-
getStreamDataLakeTableElements: (permission: Immutable.List<string>) => {
248-
attributeName: string;
249-
attributes: Array<{ id: string; title: string }>;
250-
columnRenderer: { data_lake: ColumnRenderer<Stream> };
251-
};
252-
DataLakeStreamDeleteWarning: React.ComponentType;
253-
}
254-
255-
interface PageNavigation {
256-
description: string;
257-
perspective?: string;
258-
children: Array<{
259-
description: string;
260-
position?: PluginNavigation['position'];
261-
permissions?: string | Array<string>;
262-
useCondition?: () => boolean;
263-
requiredFeatureFlag?: string;
264-
path: QualifiedUrl<string>;
265-
exactPathMatch?: boolean;
266-
}>;
267-
}
268-
269184
type CreatorTelemetryEvent = {
270185
type: TelemetryEventType;
271186
section: string;
272187
actionValue: string;
273188
};
274-
interface EntityCreator {
275-
id: string;
276-
title: string;
277-
path: QualifiedUrl<string>;
278-
permissions?: string | Array<string>;
279-
telemetryEvent?: CreatorTelemetryEvent;
280-
}
281-
282-
type HelpMenuItem = {
283-
description: string;
284-
permissions?: string | Array<string>;
285-
} & ({ externalLink?: string } | { action?: (args: { showHotkeysModal: () => void }) => void });
286189

287190
type RouteGenerator = (id: string, type: string) => QualifiedUrl<string>;
288191

@@ -296,6 +199,186 @@ type IndexRetentionConfig = {
296199
};
297200

298201
declare module 'graylog-web-plugin/plugin' {
202+
type Id = string;
203+
type Wildcard = '*';
204+
type Permission =
205+
| Wildcard
206+
| {
207+
[Entity in keyof EntityActions]:
208+
| `${Entity}:${Wildcard}`
209+
| `${Entity}:${EntityActions[Entity]}`
210+
| `${Entity}:${EntityActions[Entity]}:${Id}`;
211+
}[keyof EntityActions];
212+
type Permissions = Permission | Array<Permission>;
213+
214+
interface EntityCreator {
215+
id: string;
216+
title: string;
217+
path: QualifiedUrl<string>;
218+
permissions?: Permissions;
219+
telemetryEvent?: CreatorTelemetryEvent;
220+
}
221+
222+
interface EntityActions {
223+
alerts: 'create';
224+
api_browser: 'read';
225+
authentication: 'edit';
226+
buffers: 'read';
227+
// Do we need both of the following?
228+
clusterconfig: 'read';
229+
clusterconfigentry: 'read' | 'edit';
230+
clusterconfiguration: 'read';
231+
contentpack: 'read';
232+
dashboards: 'create' | 'edit' | 'read';
233+
datanode: 'start';
234+
decorators: 'create' | 'edit' | 'read';
235+
eventdefinitions: 'create' | 'delete' | 'edit' | 'read';
236+
eventnotifications: 'create' | 'delete' | 'edit' | 'read';
237+
fieldnames: 'read';
238+
grok_pattern: 'read';
239+
indexercluster: 'read';
240+
indexranges: 'rebuild';
241+
indexset_templates: 'create' | 'edit' | 'read';
242+
indexsets: 'create' | 'edit' | 'read';
243+
indexsets_field_restrictions: 'edit';
244+
indices: 'read' | 'changestate' | 'failures';
245+
input_types: 'create';
246+
inputs: 'create' | 'edit' | 'read' | 'terminate';
247+
journal: 'read';
248+
jvmstats: 'read';
249+
lbstatus: 'change';
250+
licenseinfos: 'read';
251+
licenses: 'read';
252+
loggers: 'read';
253+
loggersmessages: 'read';
254+
lookuptables: 'read';
255+
mappingprofiles: 'read';
256+
metrics: 'read';
257+
messagecount: 'read';
258+
messages: 'analyze' | 'read';
259+
node: 'shutdown';
260+
notifications: 'read';
261+
outputs: 'create' | 'edit' | 'read' | 'terminate';
262+
pipeline: 'create' | 'delete' | 'edit' | 'read';
263+
pipeline_connection: 'edit' | 'read';
264+
processbuffer: 'dump';
265+
processing: 'changestate';
266+
roles: 'delete' | 'edit' | 'read';
267+
searches: 'relative';
268+
sidecars: 'read';
269+
stream_outputs: 'create' | 'delete' | 'read';
270+
streams: 'create' | 'delete' | 'edit' | 'read' | 'changestate';
271+
system: 'read';
272+
systemjobs: 'read';
273+
systemmessages: 'read';
274+
team: 'edit';
275+
threads: 'dump';
276+
throughput: 'read';
277+
typemappings: 'edit';
278+
urlallowlist: 'read' | 'write';
279+
users:
280+
| 'create'
281+
| 'edit'
282+
| 'read'
283+
| 'tokenlist'
284+
| 'tokencreate'
285+
| 'tokenremove'
286+
| 'passwordchange'
287+
| 'rolesedit'
288+
| 'list';
289+
view: 'edit' | 'read';
290+
}
291+
292+
interface PluginDataLake {
293+
StreamDataLake: React.ComponentType<{
294+
permissions: Immutable.List<Permission>;
295+
}>;
296+
DataLakeStatus: React.ComponentType<{
297+
dataLakeEnabled: boolean;
298+
}>;
299+
DataLakeJournal: React.ComponentType<{
300+
nodeId: string;
301+
}>;
302+
DataLakeJobs: React.ComponentType<{
303+
permissions: Immutable.List<Permission>;
304+
streamId: string;
305+
}>;
306+
StreamIlluminateProcessingSection: React.ComponentType<{
307+
stream: Stream;
308+
}>;
309+
StreamIndexSetDataLakeWarning: React.ComponentType<{ streamId: string; isArchivingEnabled: boolean }>;
310+
fetchStreamDataLakeStatus: (streamId: string) => Promise<{
311+
id: string;
312+
archive_name: string;
313+
enabled: boolean;
314+
stream_id: string;
315+
retention_time: number;
316+
}>;
317+
fetchStreamDataLake: (streamId: string) => Promise<{
318+
id: string;
319+
archive_config_id: string;
320+
message_count: number;
321+
archive_name: string;
322+
timestamp_from: string;
323+
timestamp_to: string;
324+
restore_history: Array<{ id: string }>;
325+
}>;
326+
getStreamDataLakeTableElements: (permission: Immutable.List<string>) => {
327+
attributeName: string;
328+
attributes: Array<{ id: string; title: string }>;
329+
columnRenderer: { data_lake: ColumnRenderer<Stream> };
330+
};
331+
DataLakeStreamDeleteWarning: React.ComponentType;
332+
}
333+
334+
type HelpMenuItem = {
335+
description: string;
336+
permissions?: Permission | Array<Permission>;
337+
} & ({ externalLink?: string } | { action?: (args: { showHotkeysModal: () => void }) => void });
338+
339+
interface PageNavigation {
340+
description: string;
341+
perspective?: string;
342+
children: Array<{
343+
description: string;
344+
position?: PluginNavigation['position'];
345+
permissions?: Permission | Array<Permission>;
346+
useCondition?: () => boolean;
347+
requiredFeatureFlag?: string;
348+
path: QualifiedUrl<string>;
349+
exactPathMatch?: boolean;
350+
}>;
351+
}
352+
353+
interface PluginRoute {
354+
path: string;
355+
component: React.ComponentType;
356+
parentComponent?: React.ComponentType | null;
357+
permissions?: Permission | Array<Permission>;
358+
requiredFeatureFlag?: string;
359+
}
360+
361+
interface PluginNavigationDropdownItem {
362+
description: string;
363+
path: QualifiedUrl<string>;
364+
permissions?: Permission | Array<Permission>;
365+
requiredFeatureFlag?: string;
366+
}
367+
368+
type PluginNavigationDropdown = {
369+
children: Array<PluginNavigationDropdownItem>;
370+
};
371+
372+
type PluginNavigation = {
373+
description: string;
374+
requiredFeatureFlag?: string;
375+
perspective?: string;
376+
BadgeComponent?: React.ComponentType<{ text: string }>;
377+
position?: { last: true } | { after: string } | undefined;
378+
permissions?: Permission | Array<Permission>;
379+
useCondition?: () => boolean;
380+
} & (PluginNavigationLink | PluginNavigationDropdown);
381+
299382
interface PluginExports {
300383
navigation?: Array<PluginNavigation>;
301384
/**

graylog2-web-interface/src/components/cluster/GraylogClusterOverview.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import React from 'react';
1818
import * as Immutable from 'immutable';
1919
import userEvent from '@testing-library/user-event';
2020
import { render, screen, waitFor } from 'wrappedTestingLibrary';
21+
import type { Permission } from 'graylog-web-plugin/plugin';
2122

2223
import { adminUser } from 'fixtures/users';
2324
import MockStore from 'helpers/mocking/StoreMock';
@@ -98,7 +99,7 @@ describe('GraylogClusterOverview', () => {
9899
beforeEach(() => {
99100
const currentUserWithPermissions = adminUser
100101
.toBuilder()
101-
.permissions(Immutable.List(['licenses:read']))
102+
.permissions(Immutable.List<Permission>(['licenses:read']))
102103
.build();
103104

104105
asMock(useCurrentUser).mockReturnValue(currentUserWithPermissions);

graylog2-web-interface/src/components/common/EntityDataTable/EntityDataTable.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ import EntityDataTable from './EntityDataTable';
3030

3131
jest.mock('hooks/useCurrentUser');
3232

33+
declare module 'graylog-web-plugin/plugin' {
34+
interface EntityActions {
35+
status: 'read';
36+
}
37+
}
38+
3339
describe('<EntityDataTable />', () => {
3440
beforeEach(() => {
3541
asMock(useCurrentUser).mockReturnValue(defaultUser);

0 commit comments

Comments
 (0)