Skip to content

Commit 066b14e

Browse files
Admin messages extension (#15)
* Logic for websockets as part of the submodules * Constants and adding application as parameter * Whitelist const improvements * Import fix * Added whitelist for cognition * Admin messages as part of the submodules * Whitelist for chatui * Added scheduled date for display * Admin messages component update * Added prefixes for current page and check if the combination is used correctly * PR comments * PR comments * PR comments * Added gates as part of the websockets * Added welcome screen as part of the submodules * Removed unused code * PR comments * Added same font for admin messages
1 parent 5ea5fae commit 066b14e

File tree

7 files changed

+423
-0
lines changed

7 files changed

+423
-0
lines changed

components/AdminMessages.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AdminMessageLevel, AdminMessagesProps } from "@/submodules/react-components/types/admin-messages";
2+
import { CurrentPage } from "@/submodules/react-components/hooks/web-socket/constants";
3+
import { IconAlertCircle, IconInfoSquare, IconPoint, IconX } from "@tabler/icons-react";
4+
import { useMemo } from "react";
5+
6+
export default function AdminMessages(props: AdminMessagesProps) {
7+
const isOnLabelingPage = useMemo(() => props.currentPage == CurrentPage.LABELING, [props.currentPage]);
8+
9+
function closeMessage(id: string) {
10+
const adminMessagesCopy = [...props.adminMessages];
11+
const index = adminMessagesCopy.findIndex((message) => message.id == id);
12+
adminMessagesCopy[index].visible = false;
13+
props.setActiveAdminMessages(adminMessagesCopy);
14+
}
15+
16+
return (<div className={`pointer-events-none font-dm-sans flex-col right-0 fixed inset-y-0 sm:flex sm:justify-end sm:px-6 sm:pb-5 lg:px-8 ${isOnLabelingPage ? 'bottom-8' : 'bottom-0'}`}>
17+
{props.adminMessages && props.adminMessages.map((activeMessage, index) => (
18+
<div key={activeMessage.id} className={`pointer-events-auto items-center justify-between gap-x-6 py-2.5 mt-2 border px-6 sm:rounded-xl sm:py-3 sm:pr-3.5 sm:pl-4 ${activeMessage.borderColor} ${activeMessage.backgroundColor} ${activeMessage.visible ? 'flex' : 'hidden'}`}
19+
style={{ maxWidth: props.maxWidth ?? 'calc(100vw - 200px)' }}>
20+
<div className={`text-sm leading-6 flex flex-row items-center w-full ${activeMessage.textColor}`}>
21+
{activeMessage.level == AdminMessageLevel.INFO && <IconInfoSquare className="text-blue-700" size={24} />}
22+
{activeMessage.level == AdminMessageLevel.WARNING && <IconAlertCircle className="text-yellow-700" size={24} />}
23+
<strong className="font-semibold uppercase">{activeMessage.level}</strong><IconPoint className="mx-2" size={16} />
24+
<strong className="font-semibold">{activeMessage.text}</strong>
25+
{activeMessage.displayDate && <><IconPoint className="mx-2" size={16} /><div>Scheduled for {activeMessage.displayDate}</div></>}
26+
<button type="button" className="-my-1.5 ml-auto mr-0 flex-none p-1.5" onClick={() => closeMessage(activeMessage.id)}>
27+
<IconX className={`${activeMessage.textColor} cursor-pointer`} size={20} strokeWidth={1.5} />
28+
</button>
29+
</div>
30+
</div >))
31+
}
32+
</div >)
33+
}

helpers/admin-messages-helper.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { AdminMessage, AdminMessageLevel } from "@/submodules/react-components/types/admin-messages";
2+
import { parseUTC } from "@/submodules/javascript-functions/date-parser";
3+
4+
export const adminMessageLevels = {
5+
[AdminMessageLevel.INFO]: { label: 'Info', color: 'blue' },
6+
[AdminMessageLevel.WARNING]: { label: 'Warning', color: 'yellow' }
7+
};
8+
9+
export function postProcessAdminMessages(adminMessages: AdminMessage[]): AdminMessage[] {
10+
return adminMessages.map((message) => {
11+
message = { ...message };
12+
message.displayDate = parseUTC(message.scheduledDate);
13+
const color = adminMessageLevels[message.level].color;
14+
message.textColor = 'text-' + color + '-700';
15+
message.backgroundColor = 'bg-' + color + '-100';
16+
message.borderColor = 'border-' + color + '-400';
17+
message.visible = true;
18+
return message;
19+
});;
20+
}

hooks/web-socket/WebSocketsService.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { timer } from "rxjs";
2+
import { webSocket } from "rxjs/webSocket";
3+
import { NotificationScope, NotificationSubscription, getStableWebsocketPageKey } from "./web-sockets-helper";
4+
import { CurrentPage, CurrentPageSubKey } from "./constants";
5+
6+
export class WebSocketsService {
7+
8+
private static wsConnection: any;
9+
private static timeOutIteration: number = 0;
10+
private static registeredNotificationListeners: Map<NotificationScope, NotificationSubscription> = new Map<NotificationScope, NotificationSubscription>();
11+
private static connectionOpened: boolean = false;
12+
13+
public static getConnectionOpened() {
14+
return WebSocketsService.connectionOpened;
15+
}
16+
17+
public static setConnectionOpened(value: boolean) {
18+
WebSocketsService.connectionOpened = value;
19+
}
20+
21+
public static updateFunctionPointer(projectId: string, page: CurrentPage, funcPointer: (msg: string[]) => void, subKey?: CurrentPageSubKey) {
22+
if (!projectId) projectId = "GLOBAL";
23+
const scope = getStableWebsocketPageKey(projectId, page, subKey)
24+
25+
if (!WebSocketsService.registeredNotificationListeners.has(scope)) {
26+
console.warn("Nothing registered for scope: " + scope);
27+
return;
28+
}
29+
WebSocketsService.registeredNotificationListeners.get(scope).func = funcPointer;
30+
}
31+
32+
public static initWsNotifications() {
33+
const address = WebSocketsService.findWebsocketAddress();
34+
if (address) {
35+
WebSocketsService.wsConnection = webSocket({
36+
url: address,
37+
deserializer: msg => msg.data,
38+
openObserver: {
39+
next: () => {
40+
if (WebSocketsService.timeOutIteration) console.log("Websocket connected");
41+
WebSocketsService.timeOutIteration = 0;
42+
}
43+
},
44+
closeObserver: {
45+
next: (closeEvent) => {
46+
const timeout = WebSocketsService.getTimeout(WebSocketsService.timeOutIteration);
47+
timer(timeout).subscribe(() => { WebSocketsService.timeOutIteration++; WebSocketsService.initWsNotifications(); })
48+
}
49+
}
50+
});
51+
WebSocketsService.wsConnection.subscribe(
52+
msg => WebSocketsService.handleWebsocketNotificationMessage(msg),
53+
err => WebSocketsService.handleError(err),
54+
() => WebSocketsService.handleWsClosed()
55+
);
56+
}
57+
}
58+
59+
private static getTimeout(iteration: number) {
60+
if (iteration <= 0) return 1000;
61+
else {
62+
switch (iteration) {
63+
case 1: return 2000;
64+
case 2: return 5000;
65+
case 3: return 15000;
66+
case 4: return 30000;
67+
case 5: return 60000;
68+
default:
69+
return 60 * 5 * 1000; //5 min
70+
}
71+
}
72+
}
73+
74+
private static findWebsocketAddress() {
75+
let address = window.location.protocol == 'https:' ? 'wss:' : 'ws:';
76+
address += '//' + window.location.host + '/notify/ws';
77+
return address; //'ws://localhost:4455/notify/ws'
78+
}
79+
80+
81+
private static handleError(err) {
82+
console.log("error", err)
83+
}
84+
85+
private static handleWsClosed() {
86+
console.log('ws closed')
87+
}
88+
89+
90+
private static handleWebsocketNotificationMessage(msg: string) {
91+
if (WebSocketsService.registeredNotificationListeners.size == 0) return;
92+
if (msg.includes("\n")) {
93+
msg.split("\n").forEach(element => WebSocketsService.handleWebsocketNotificationMessage(element));
94+
return;
95+
}
96+
const scopes = WebSocketsService.registeredNotificationListeners.keys();
97+
98+
const msgParts = msg.split(":");
99+
const projectId = msgParts[0];// uuid | "GLOBAL";
100+
101+
for (let scope of scopes) {
102+
if (scope.projectId != projectId) continue;
103+
const sub = WebSocketsService.registeredNotificationListeners.get(scope);
104+
if (sub.whitelist.includes(msgParts[1])) {
105+
sub.func(msgParts);
106+
}
107+
}
108+
}
109+
110+
public static subscribeToNotification(key: CurrentPage, params: NotificationSubscription, subKey?: CurrentPageSubKey) {
111+
if (!params.projectId) params.projectId = "GLOBAL";
112+
113+
const scope = getStableWebsocketPageKey(params.projectId, key, subKey)
114+
115+
WebSocketsService.registeredNotificationListeners.set(scope, params);
116+
}
117+
118+
public static unsubscribeFromNotification(key: CurrentPage, projectId: string = null, subKey?: CurrentPageSubKey) {
119+
if (!projectId) projectId = "GLOBAL";
120+
const scope = getStableWebsocketPageKey(projectId, key, subKey)
121+
122+
if (WebSocketsService.registeredNotificationListeners.has(scope)) {
123+
WebSocketsService.registeredNotificationListeners.delete(scope);
124+
}
125+
}
126+
}
127+

hooks/web-socket/constants.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
export enum Application {
2+
REFINERY = 'REFINERY',
3+
COGNITION = 'COGNITION',
4+
GATES = 'GATES',
5+
WELCOME_SCREEN = 'WELCOME_SCREEN',
6+
}
7+
8+
export enum CurrentPage {
9+
PROJECTS = 'PROJECTS',
10+
LOOKUP_LISTS_OVERVIEW = 'LOOKUP_LISTS_OVERVIEW',
11+
NEW_PROJECT = 'NEW_PROJECT',
12+
PROJECT_OVERVIEW = 'PROJECT_OVERVIEW',
13+
DATA_BROWSER = 'DATA_BROWSER',
14+
LABELING = 'LABELING',
15+
HEURISTICS = 'HEURISTICS',
16+
ADMIN_PAGE = 'ADMIN_PAGE',
17+
USERS = 'USERS',
18+
UPLOAD_RECORDS = 'UPLOAD_RECORDS',
19+
PROJECT_SETTINGS = "PROJECT_SETTINGS",
20+
MODELS_DOWNLOAD = "MODELS_DOWNLOAD",
21+
ATTRIBUTE_CALCULATION = "ATTRIBUTE_CALCULATION",
22+
LOOKUP_LISTS_DETAILS = "LOOKUP_LISTS_DETAILS",
23+
MODEL_CALLBACKS = "MODEL_CALLBACKS",
24+
LABELING_FUNCTION = "LABELING_FUNCTION",
25+
ACTIVE_LEARNING = "ACTIVE_LEARNING",
26+
ZERO_SHOT = "ZERO_SHOT",
27+
CROWD_LABELER = "CROWD_LABELER",
28+
RECORD_IDE = "RECORD_IDE",
29+
EDIT_RECORDS = "EDIT_RECORDS",
30+
NOTIFICATION_CENTER = "NOTIFICATION_CENTER",
31+
EXPORT = "EXPORT",
32+
COMMENTS = "COMMENTS",
33+
BRICKS_INTEGRATOR = "BRICKS_INTEGRATOR",
34+
CONFIG = "CONFIG",
35+
COGNITION_LAYOUT = "COGNITION_LAYOUT",
36+
GATES_LAYOUT = "GATES_LAYOUT",
37+
WELCOME_SCREEN_LAYOUT = "WELCOME_SCREEN_LAYOUT",
38+
}
39+
40+
export enum CurrentPageSubKey {
41+
EMBEDDINGS = 'EMBEDDING',
42+
NONE = 'NONE',
43+
BUTTONS_CONTAINER = 'BUTTONS_CONTAINER',
44+
VARIABLE_SELECTION = 'VARIABLE_SELECTION',
45+
SNAPSHOT_EXPORT = 'SNAPSHOT_EXPORT',
46+
GLOBAL = 'GLOBAL',
47+
FILE_UPLOAD = 'FILE_UPLOAD',
48+
}
49+
50+
export const WHITELIST_LOOKUP_REFINERY = {
51+
[CurrentPage.PROJECTS]: {
52+
[CurrentPageSubKey.NONE]: ['project_created', 'project_deleted', 'project_update', 'file_upload'],
53+
[CurrentPageSubKey.BUTTONS_CONTAINER]: ['bad_password'],
54+
[CurrentPageSubKey.FILE_UPLOAD]: ['file_upload']
55+
},
56+
[CurrentPage.UPLOAD_RECORDS]: {
57+
[CurrentPageSubKey.NONE]: ['file_upload']
58+
},
59+
[CurrentPage.MODELS_DOWNLOAD]: {
60+
[CurrentPageSubKey.NONE]: ['model_provider_download']
61+
},
62+
[CurrentPage.COMMENTS]: {
63+
[CurrentPageSubKey.NONE]: ['label_created', 'label_deleted', 'attributes_updated', 'calculate_attribute', 'embedding_deleted', 'embedding', 'labeling_task_updated', 'labeling_task_deleted', 'labeling_task_created', 'data_slice_created', 'data_slice_updated', 'data_slice_deleted', 'information_source_created', 'information_source_updated', 'information_source_deleted', 'knowledge_base_created', 'knowledge_base_updated', 'knowledge_base_deleted'],
64+
[CurrentPageSubKey.GLOBAL]: ['comment_created', 'comment_updated', 'comment_deleted', 'project_created', 'project_deleted']
65+
},
66+
[CurrentPage.BRICKS_INTEGRATOR]: {
67+
[CurrentPageSubKey.VARIABLE_SELECTION]: ['attributes_updated', 'calculate_attribute', 'label_created', 'label_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created', 'embedding', 'embedding_deleted', 'knowledge_base_deleted', 'knowledge_base_created']
68+
},
69+
[CurrentPage.PROJECT_OVERVIEW]: {
70+
[CurrentPageSubKey.NONE]: ['label_created', 'label_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created', 'weak_supervision_finished', 'data_slice_created', 'data_slice_updated', 'data_slice_deleted']
71+
},
72+
[CurrentPage.PROJECT_SETTINGS]: {
73+
[CurrentPageSubKey.NONE]: ['project_update', 'tokenization', 'calculate_attribute', 'embedding', 'attributes_updated', 'gates_integration', 'information_source_deleted', 'information_source_updated', 'embedding_deleted', 'embedding_updated', 'upload_embedding_payload', 'label_created', 'label_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created'],
74+
[CurrentPageSubKey.SNAPSHOT_EXPORT]: ['project_updated', 'project_export'],
75+
[CurrentPageSubKey.EMBEDDINGS]: ['embedding_updated', 'upload_embedding_payload']
76+
77+
},
78+
[CurrentPage.EXPORT]: {
79+
[CurrentPageSubKey.NONE]: ['record_export', 'calculate_attribute', 'labeling_task_deleted', 'labeling_task_created', 'data_slice_created', 'data_slice_deleted', 'information_source_created', 'information_source_deleted']
80+
},
81+
[CurrentPage.MODEL_CALLBACKS]: {
82+
[CurrentPageSubKey.NONE]: ['information_source_created', 'information_source_updated', 'information_source_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created', 'model_callback_update_statistics']
83+
},
84+
[CurrentPage.LOOKUP_LISTS_DETAILS]: {
85+
[CurrentPageSubKey.NONE]: ['knowledge_base_updated', 'knowledge_base_deleted', 'knowledge_base_term_updated']
86+
},
87+
[CurrentPage.LOOKUP_LISTS_OVERVIEW]: {
88+
[CurrentPageSubKey.NONE]: ['knowledge_base_updated', 'knowledge_base_deleted', 'knowledge_base_created']
89+
},
90+
[CurrentPage.LABELING]: {
91+
[CurrentPageSubKey.NONE]: ['attributes_updated', 'calculate_attribute', 'payload_finished', 'weak_supervision_finished', 'record_deleted', 'rla_created', 'rla_deleted', 'access_link_changed', 'access_link_removed', 'label_created', 'label_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created'],
92+
93+
},
94+
[CurrentPage.ZERO_SHOT]: {
95+
[CurrentPageSubKey.NONE]: ['labeling_task_updated', 'labeling_task_created', 'label_created', 'label_deleted', 'labeling_task_deleted', 'information_source_deleted', 'information_source_updated', 'payload_update_statistics', 'payload_finished', 'payload_failed', 'payload_created', 'zero-shot', 'zero_shot_download']
96+
},
97+
[CurrentPage.LABELING_FUNCTION]: {
98+
[CurrentPageSubKey.NONE]: ['labeling_task_updated', 'labeling_task_created', 'label_created', 'label_deleted', 'labeling_task_deleted', 'information_source_deleted', 'information_source_updated', 'model_callback_update_statistics', 'payload_progress', 'payload_finished', 'payload_failed', 'payload_created', 'payload_update_statistics'],
99+
},
100+
[CurrentPage.CROWD_LABELER]: {
101+
[CurrentPageSubKey.NONE]: ['labeling_task_updated', 'labeling_task_created', 'label_created', 'label_deleted', 'labeling_task_deleted', 'information_source_deleted', 'information_source_updated', 'model_callback_update_statistics']
102+
},
103+
[CurrentPage.ACTIVE_LEARNING]: {
104+
[CurrentPageSubKey.NONE]: ['labeling_task_updated', 'labeling_task_created', 'label_created', 'label_deleted', 'labeling_task_deleted', 'information_source_deleted', 'information_source_updated', 'model_callback_update_statistics', 'embedding_deleted', 'embedding', 'payload_finished', 'payload_failed', 'payload_created', 'payload_update_statistics']
105+
},
106+
[CurrentPage.HEURISTICS]: {
107+
[CurrentPageSubKey.NONE]: ['labeling_task_updated', 'labeling_task_created', 'labeling_task_deleted', 'information_source_created', 'information_source_updated', 'information_source_deleted', 'payload_finished', 'payload_failed', 'payload_created', 'payload_update_statistics', 'embedding_deleted'],
108+
109+
},
110+
[CurrentPage.EDIT_RECORDS]: {
111+
[CurrentPageSubKey.NONE]: ['calculate_attribute']
112+
},
113+
[CurrentPage.DATA_BROWSER]: {
114+
[CurrentPageSubKey.NONE]: ['data_slice_created', 'data_slice_updated', 'data_slice_deleted', 'label_created', 'label_deleted', 'labeling_task_deleted', 'labeling_task_updated', 'labeling_task_created', 'information_source_created', 'information_source_updated', 'information_source_deleted', 'attributes_updated', 'calculate_attribute', 'embedding', 'embedding_deleted'],
115+
116+
},
117+
[CurrentPage.ATTRIBUTE_CALCULATION]: {
118+
[CurrentPageSubKey.NONE]: ['attributes_updated', 'calculate_attribute', 'tokenization', 'knowledge_base_updated', 'knowledge_base_deleted', 'knowledge_base_created'],
119+
120+
},
121+
[CurrentPage.ADMIN_PAGE]: {
122+
[CurrentPageSubKey.NONE]: ['pat']
123+
},
124+
[CurrentPage.NOTIFICATION_CENTER]: {
125+
[CurrentPageSubKey.NONE]: ['notification_created', 'project_deleted', 'config_updated', 'admin_message'],
126+
127+
}
128+
}
129+
130+
export const WHITELIST_LOOKUP_COGNITION = {
131+
[CurrentPage.COGNITION_LAYOUT]: {
132+
[CurrentPageSubKey.NONE]: ['admin_message']
133+
}
134+
}
135+
136+
export const WHITELIST_LOOKUP_GATES = {
137+
[CurrentPage.GATES_LAYOUT]: {
138+
[CurrentPageSubKey.NONE]: ['admin_message']
139+
}
140+
}
141+
142+
export const WHITE_LIST_LOOKUP_WELCOME_SCREEN = {
143+
[CurrentPage.WELCOME_SCREEN_LAYOUT]: {
144+
[CurrentPageSubKey.NONE]: ['admin_message']
145+
}
146+
}

hooks/web-socket/useWebsocket.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useEffect, useMemo, } from 'react';
2+
import { WebSocketsService } from './WebSocketsService';
3+
import { NotificationSubscription, getConstWhitelist } from './web-sockets-helper';
4+
import { Application, CurrentPage, CurrentPageSubKey } from './constants';
5+
6+
export function useWebsocket(application: Application, currentPage: CurrentPage, handleFunction: (msgParts: string[]) => void, projectId?: string, subKey?: CurrentPageSubKey) {
7+
8+
const _projectId = useMemo(() => projectId || "GLOBAL", [projectId]);
9+
const _subKey = useMemo(() => subKey || CurrentPageSubKey.NONE, [subKey]);
10+
const __whitelist = useMemo(() => getConstWhitelist(application), [application]);
11+
12+
useEffect(() => {
13+
if (!__whitelist[currentPage]) {
14+
console.error(`The combination of ${currentPage} and ${application} does not exist in the whitelist`);
15+
return;
16+
}
17+
const nos: NotificationSubscription = {
18+
whitelist: __whitelist[currentPage][_subKey],
19+
func: handleFunction,
20+
projectId: _projectId
21+
}
22+
23+
WebSocketsService.subscribeToNotification(currentPage, nos, _subKey);
24+
25+
return () => WebSocketsService.unsubscribeFromNotification(currentPage, projectId, _subKey);
26+
}, [_projectId, _subKey, application]);
27+
28+
useEffect(() => {
29+
WebSocketsService.updateFunctionPointer(projectId, currentPage, handleFunction, subKey)
30+
}, [handleFunction]);
31+
32+
33+
}

0 commit comments

Comments
 (0)