Skip to content

Commit 8c9cb0e

Browse files
authored
Merge pull request #737 from codeSafari10/eventBus
Implement permissions flow
2 parents 6cde861 + b2f0696 commit 8c9cb0e

File tree

5 files changed

+162
-8
lines changed

5 files changed

+162
-8
lines changed

package-lock.json

Lines changed: 7 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actors/eventBus.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { Observable, Subject, filter } from 'rxjs';
3+
4+
// Generic Event interface
5+
export interface EventBusEvent<Type extends string, Data = unknown> {
6+
type: Type;
7+
data: Data;
8+
}
9+
10+
// Helper type to extract event types from a union
11+
export type EventType<T> = T extends EventBusEvent<infer Type, any> ? Type : never;
12+
13+
// Helper type to extract payload type for a given event type
14+
export type DataType<T, Type extends string> = T extends EventBusEvent<Type, infer Data>
15+
? Data
16+
: never;
17+
18+
// Generic EventBus class
19+
export class EventBus<T extends EventBusEvent<string, any>> {
20+
private eventSubject: Subject<T>;
21+
private eventObservable: Observable<T>;
22+
23+
constructor() {
24+
this.eventSubject = new Subject<T>();
25+
this.eventObservable = this.eventSubject.asObservable();
26+
}
27+
28+
// Method to publish an event
29+
publish<E extends T>(event: E): void {
30+
this.eventSubject.next(event);
31+
}
32+
33+
// Method to subscribe to a specific event type
34+
on(eventType: EventType<T>): Observable<T> {
35+
return this.eventObservable.pipe(filter((event): event is T => event.type === eventType));
36+
}
37+
38+
// Method to subscribe to all events
39+
onAny(): Observable<T> {
40+
return this.eventObservable;
41+
}
42+
}

src/actors/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ export {
3737
sendToActor,
3838
sendToActors
3939
} from './utils';
40+
41+
export * from './eventBus';

src/custom/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,4 @@ export type {
122122
};
123123

124124
export * from './Dialog';
125+
export * from './permissions';

src/custom/permissions.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// import { CAN, getCapabilitiesRegistry, getMesheryEventBus } from '@/globals/mesherySdk';
2+
import React from 'react';
3+
import { EventBus } from '../actors/eventBus';
4+
5+
export interface Key {
6+
subject: string;
7+
action: string;
8+
}
9+
10+
export type InvertAction = 'disable' | 'hide';
11+
12+
export type MissingPermissionReason = {
13+
type: 'MISSING_PERMISSION';
14+
data: {
15+
keyId: string;
16+
};
17+
};
18+
19+
export type MissingCapabilityReason = {
20+
type: 'MISSING_CAPABILITY';
21+
data: {
22+
capabilityId: string;
23+
};
24+
};
25+
26+
export type ReasonEvent = MissingPermissionReason | MissingCapabilityReason;
27+
28+
export interface HasKeyProps<ReasonEvent> {
29+
Key?: Key;
30+
predicate?: (capabilitiesRegistry: unknown) => [boolean, ReasonEvent]; // returns a boolean and an event if the user does not have the permission
31+
children: React.ReactNode;
32+
notifyOnclick?: boolean;
33+
invert_action?: InvertAction[];
34+
}
35+
36+
// returns the children if the user has the permission to view the component or if a key is not provided
37+
// if the user does not have the permission to view the component, it will return null or a disabled version of the component specified by the invert_action prop
38+
export const createCanShow = (
39+
getCapabilitiesRegistry = () => {},
40+
CAN: (action: string, subject: string) => boolean,
41+
eventBus: () => EventBus<ReasonEvent>
42+
) => {
43+
return ({
44+
Key,
45+
children,
46+
notifyOnclick = true,
47+
predicate,
48+
invert_action = ['disable']
49+
}: HasKeyProps<ReasonEvent>) => {
50+
if (!children) {
51+
return null;
52+
}
53+
54+
const hasKey = Key?.subject ? CAN(Key?.action, Key?.subject) : true;
55+
const predicateRes = predicate && predicate(getCapabilitiesRegistry());
56+
57+
const can = predicateRes ? predicateRes[0] && hasKey : hasKey;
58+
59+
const reason = predicateRes?.[1] || {
60+
type: 'MISSING_PERMISSION',
61+
data: {
62+
keyId: Key?.action as string
63+
}
64+
};
65+
66+
if (can) {
67+
return children;
68+
}
69+
70+
if (invert_action.includes('hide')) {
71+
return null;
72+
}
73+
74+
const pointerEvents = notifyOnclick ? 'auto' : 'none';
75+
76+
const onClick = notifyOnclick
77+
? (e: React.MouseEvent<HTMLDivElement | HTMLElement>) => {
78+
e.stopPropagation();
79+
console.log('cant perform action : reason', reason, eventBus);
80+
const mesheryEventBus = eventBus();
81+
mesheryEventBus.publish(reason);
82+
}
83+
: () => {};
84+
85+
const opacity = invert_action.includes('disable') ? 0.5 : 1;
86+
87+
return (
88+
<div
89+
style={{
90+
cursor: 'pointer',
91+
pointerEvents,
92+
opacity: opacity
93+
}}
94+
onClick={onClick}
95+
>
96+
{React.cloneElement(children as React.ReactElement, {
97+
style: {
98+
...((children as React.ReactElement).props.style as React.CSSProperties),
99+
cursor: 'pointer',
100+
pointerEvents,
101+
opacity: opacity
102+
},
103+
onClick: onClick
104+
})}
105+
</div>
106+
);
107+
108+
return null;
109+
};
110+
};

0 commit comments

Comments
 (0)