From c5d1bf865e54db15dff77d1fb2dd0d8793d152a6 Mon Sep 17 00:00:00 2001 From: Leonardo Christino <leomilho@gmail.com> Date: Sat, 2 Mar 2024 20:36:05 +0100 Subject: [PATCH] feat: update deps, add sync ws api and fixes --- apps/web/package.json | 1 + apps/web/src/app/app.tsx | 50 +++--- .../dbConnectionSelector.tsx | 5 +- .../DatabaseManagement/forms/settings.tsx | 89 ++++------ apps/web/tsconfig.json | 3 +- libs/shared/lib/data-access/api/eventBus.tsx | 146 +++++++++------- libs/shared/lib/data-access/broker/broker.tsx | 61 ++++--- libs/shared/lib/data-access/broker/index.ts | 1 + libs/shared/lib/data-access/broker/types.ts | 2 + libs/shared/lib/data-access/broker/wsQuery.ts | 18 ++ .../shared/lib/data-access/broker/wsSchema.ts | 8 + .../shared/lib/data-access/broker/wsState.tsx | 157 ++++++++++++------ .../lib/data-access/store/sessionSlice.ts | 6 + libs/shared/package.json | 1 + libs/shared/tsconfig.json | 3 +- pnpm-lock.yaml | 7 +- 16 files changed, 346 insertions(+), 212 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 10a840e83..739dd1408 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -50,6 +50,7 @@ "react-is": "^18.2.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", + "reselect": "^5.1.0", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", "vite": "^5.1.4", diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 8998b609c..11413ebde 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -52,32 +52,36 @@ export function App(props: App) { setAuthCheck(true); }} /> - <Onboarding /> - <DashboardAlerts /> - <div className={'h-screen w-screen ' + (!auth.authorized ? 'blur-sm pointer-events-none ' : '')}> - <div className="flex flex-col h-screen max-h-screen relative"> - <aside className="h-auto w-auto"> - <Navbar /> - </aside> - <main className="flex w-screen h-[calc(100%-4.2rem)]"> - <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}> - <div className="h-full w-full panel"> - <Schema auth={authCheck} /> - </div> - <div className="h-full w-full"> - <Resizable divisorSize={3} horizontal={false}> - <div className="w-full h-full panel"> - <VisualizationPanel /> + {authCheck && ( + <> + <Onboarding /> + <DashboardAlerts /> + <div className={'h-screen w-screen ' + (!auth.authorized ? 'blur-sm pointer-events-none ' : '')}> + <div className="flex flex-col h-screen max-h-screen relative"> + <aside className="h-auto w-auto"> + <Navbar /> + </aside> + <main className="flex w-screen h-[calc(100%-4.2rem)]"> + <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}> + <div className="h-full w-full panel"> + <Schema auth={authCheck} /> </div> - <div className="w-full h-full panel"> - <QueryBuilder onRunQuery={runQuery} /> + <div className="h-full w-full"> + <Resizable divisorSize={3} horizontal={false}> + <div className="w-full h-full panel"> + <VisualizationPanel /> + </div> + <div className="w-full h-full panel"> + <QueryBuilder onRunQuery={runQuery} /> + </div> + </Resizable> </div> </Resizable> - </div> - </Resizable> - </main> - </div> - </div> + </main> + </div> + </div> + </> + )} </div> ); } diff --git a/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx b/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx index 6ca365292..e15109c95 100644 --- a/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx +++ b/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx @@ -25,7 +25,10 @@ export default function DatabaseSelector({}) { useEffect(() => { if ( - (session.saveStates && Object.keys(session.saveStates).length === 0 && settingsMenuOpen === undefined) || + (!session.fetchingSaveStates && + session.saveStates && + Object.keys(session.saveStates).length === 0 && + settingsMenuOpen === undefined) || session.currentSaveState === nilUUID ) { setSettingsMenuOpen('create'); diff --git a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx index c9f35f7b4..fe477e31a 100644 --- a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx +++ b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx @@ -7,7 +7,6 @@ import { wsUpdateState, DatabaseStatus, wsTestDatabaseConnection, - TestDatabaseConnectionResponse, wsCreateState, nilUUID, DatabaseType, @@ -47,19 +46,32 @@ export const SettingsForm = (props: { status: null, verified: null, }); - const formDataRef = useRef<SaveStateI | null>(null); const formTitle = props.open === 'create' ? 'Create' : 'Update'; - const refImperativeHandles = useRef<any>(null); - useImperativeHandle(refImperativeHandles, () => ({ - processDbTested: (data: TestDatabaseConnectionResponse) => { - if (!formDataRef.current) { - console.error('formDataRef.current is null'); + useEffect(() => { + if (props.saveState && props.open === 'update') { + setFormData(props.saveState); + setSampleDataPanel(null); + } else { + setSampleDataPanel(false); + } + }, [props.saveState]); + + async function handleSubmit() { + setConnection(() => ({ + updating: true, + status: formTitle.slice(0, -1) + 'ing database credentials', + verified: null, + })); + + wsTestDatabaseConnection(formData.db, (data) => { + if (!formData) { + console.error('formData is null'); return; } - if (formDataRef.current.user_id !== auth.userID && auth.userID) { - console.error('formDataRef.current.user_id is not equal to auth.userID'); - formDataRef.current.user_id = auth.userID; + if (formData.user_id !== auth.userID && auth.userID) { + console.error('formData.user_id is not equal to auth.userID'); + formData.user_id = auth.userID; } if (data && data.status === 'success') { setConnection((prevState) => ({ @@ -68,9 +80,17 @@ export const SettingsForm = (props: { verified: true, })); if (props.open === 'create') { - wsCreateState(formDataRef.current); + wsCreateState(formData, (_data) => { + dispatch(addSaveState(_data)); + dispatch(testedSaveState(_data.id)); + closeDialog(); + }); } else { - wsUpdateState(formDataRef.current); + dispatch(testedSaveState(data.saveStateID)); + wsUpdateState(formData, (_data) => { + dispatch(addSaveState(_data)); + closeDialog(); + }); } } else { setConnection((prevState) => ({ @@ -79,50 +99,7 @@ export const SettingsForm = (props: { verified: false, })); } - }, - processStateUpdated: (data: SaveStateI) => { - let _data = JSON.parse(JSON.stringify(data)); - _data.db.status = DatabaseStatus.tested; - dispatch(addSaveState(_data)); - dispatch(testedSaveState(_data.id)); - formDataRef.current = null; - closeDialog(); - }, - })); - - useEffect(() => { - Broker.instance().subscribe(refImperativeHandles.current.processDbTested, 'tested_connection'); - Broker.instance().subscribe(refImperativeHandles.current.processStateUpdated, 'updated_save_state'); - Broker.instance().subscribe(refImperativeHandles.current.processStateUpdated, 'save_state'); - - return () => { - Broker.instance().unSubscribeAll('tested_connection'); - Broker.instance().unSubscribeAll('updated_save_state'); - Broker.instance().unSubscribeAll('save_state'); - }; - }, []); - - useEffect(() => { - if (props.saveState && props.open === 'update') { - setFormData(props.saveState); - setSampleDataPanel(null); - } else { - setSampleDataPanel(false); - } - }, [props.saveState]); - - useEffect(() => { - formDataRef.current = formData; - }, [formData]); - - async function handleSubmit() { - setConnection(() => ({ - updating: true, - status: formTitle.slice(0, -1) + 'ing database credentials', - verified: null, - })); - - wsTestDatabaseConnection(formData.db); + }); } function handlePortChanged(port: string): void { diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 1fac97092..b3699e023 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -27,7 +27,8 @@ "@graphpolaris/config/*": ["../../libs/config/src/*"], "redux": ["./node_modules/redux"], "@storybook/types": ["./node_modules/@storybook/types"], - "redux-thunk": ["./node_modules/redux-thunk"] + "redux-thunk": ["./node_modules/redux-thunk"], + "reselect": ["./node_modules/reselect"] } }, "exclude": ["node_modules", "public", "dist", "build"], diff --git a/libs/shared/lib/data-access/api/eventBus.tsx b/libs/shared/lib/data-access/api/eventBus.tsx index b94a648b2..0c7038aa2 100644 --- a/libs/shared/lib/data-access/api/eventBus.tsx +++ b/libs/shared/lib/data-access/api/eventBus.tsx @@ -13,8 +13,10 @@ import { assignNewGraphQueryResult, useQuerybuilder, useVisualizationState, + wsSchemaRequest, + wsSchemaSubscription, } from '@graphpolaris/shared/lib/data-access'; -import { Broker } from '@graphpolaris/shared/lib/data-access/broker'; +import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker'; import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { GraphQueryResultFromBackendPayload, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice'; @@ -23,15 +25,25 @@ import { SchemaFromBackend } from '@graphpolaris/shared/lib/schema'; import { useEffect } from 'react'; import { SaveStateI, - TestDatabaseConnectionResponse, wsGetState, wsGetStates, wsUpdateState, wsSelectState, nilUUID, + wsGetStatesSubscription, + wsDeleteStateSubscription, + wsGetStateSubscription, + wsSelectStateSubscription, + wsTestSaveStateConnectionSubscription, } from '../broker/wsState'; -import { wsSchemaRequest } from '../broker/wsSchema'; -import { addSaveState, testedSaveState, selectSaveState, updateSaveStateList, updateSelectedSaveState } from '../store/sessionSlice'; +import { + addSaveState, + testedSaveState, + selectSaveState, + updateSaveStateList, + updateSelectedSaveState, + setFetchingSaveStates, +} from '../store/sessionSlice'; import { URLParams, getParam, deleteParam } from './url'; import { setVisualizationState } from '../store/visualizationSlice'; import { isEqual } from 'lodash-es'; @@ -49,7 +61,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } function loadSaveState(saveStateID: string | undefined, saveStates: Record<string, SaveStateI>) { if (saveStateID && saveStates && saveStateID in saveStates) { - console.debug('Setting state from database', saveStateID, saveStates); + console.debug('Setting state fetched from database', saveStateID, saveStates); const state = saveStates[saveStateID]; if (state) { dispatch(setQuerybuilderNodes(state.queryBuilder)); @@ -59,73 +71,79 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } } useEffect(() => { - Broker.instance().subscribe((data: SchemaFromBackend) => { - dispatch(readInSchemaFromBackend(data)); - dispatch(addInfo('Schema graph updated')); - }, 'schema_result'); - - Broker.instance().subscribe((data: GraphQueryResultFromBackendPayload) => { - dispatch(assignNewGraphQueryResult(data)); - dispatch(addInfo('Query Executed!')); - }, 'query_result'); + const unsubs: Function[] = []; + + unsubs.push( + wsSchemaSubscription((data) => { + dispatch(readInSchemaFromBackend(data)); + dispatch(addInfo('Schema graph updated')); + }), + ); + + unsubs.push( + wsQuerySubscription((data) => { + dispatch(assignNewGraphQueryResult(data)); + dispatch(addInfo('Query Executed!')); + }), + ); + + unsubs.push( + wsTestSaveStateConnectionSubscription((data) => { + if (data.status === 'success' && data.saveStateID) dispatch(testedSaveState(data.saveStateID)); + }), + ); // Broker.instance().subscribe((data: QueryBuilderState) => dispatch(setQuerybuilderNodes(data)), 'query_builder_state'); allMLTypes.forEach((mlType) => { - Broker.instance().subscribe((data: LinkPredictionInstance[]) => dispatch(setMLResult({ type: mlType, result: data })), mlType); + const id = Broker.instance().subscribe( + (data: LinkPredictionInstance[]) => dispatch(setMLResult({ type: mlType, result: data })), + mlType, + ); + unsubs.push(() => Broker.instance().unSubscribe(mlType, id)); }); - Broker.instance().subscribe((data: SaveStateI[]) => { - console.debug('Save States updated', data); - dispatch(updateSaveStateList(data)); - const d = Object.fromEntries(data.map((x) => [x.id, x])); - loadSaveState(session.currentSaveState, d); - // useEffect(() => { - - // }, [session.currentSaveState, session.saveStates]); - }, 'save_states'); - - Broker.instance().subscribe((data: any) => {}, 'save_state_status'); - - Broker.instance().subscribe((data: SaveStateI) => { - if (data.id !== nilUUID) { - dispatch(addSaveState(data)); - dispatch(selectSaveState(data.id)); - loadSaveState(data.id, session.saveStates); - } - }, 'save_state'); - - Broker.instance().subscribe((data: SaveStateI) => {}, 'delete_save_state'); - - Broker.instance().subscribe((data: { saveStateID: string; success: boolean }) => {}, 'save_state_selected'); - - Broker.instance().subscribe((response: TestDatabaseConnectionResponse) => { - if (response && response.status === 'success') dispatch(testedSaveState(response.saveStateID)); - }, 'tested_connection'); - - Broker.instance().subscribe((response: QueryBuilderText) => { - if (response && response.result) { - dispatch(setQueryText(response)); - } - }, 'query_translation_result'); + unsubs.push( + wsGetStatesSubscription((data) => { + console.debug('Save States updated', data); + dispatch(updateSaveStateList(data)); + const d = Object.fromEntries(data.map((x) => [x.id, x])); + loadSaveState(session.currentSaveState, d); + }), + ); + + unsubs.push( + wsGetStateSubscription((data) => { + if (data.id !== nilUUID) { + dispatch(addSaveState(data)); + dispatch(selectSaveState(data.id)); + loadSaveState(data.id, session.saveStates); + } + }), + ); + + unsubs.push(wsDeleteStateSubscription((data) => {})); + unsubs.push(wsSelectStateSubscription((data) => {})); + + // Broker.instance().subscribe((response: TestDatabaseConnectionResponse) => { + // if (response && response.status === 'success') dispatch(testedSaveState(response.saveStateID)); + // }, 'tested_connection'); + + unsubs.push( + wsQueryTranslationSubscription((response) => { + if (response && response.result) { + dispatch(setQueryText(response)); + } + }), + ); login(); // Setup cleanup return () => { - Broker.instance().unSubscribeAll('schema_result'); - Broker.instance().unSubscribeAll('query_result'); - - Broker.instance().unSubscribeAll('save_states'); - Broker.instance().unSubscribeAll('save_state'); - Broker.instance().unSubscribeAll('save_state_status'); - Broker.instance().unSubscribeAll('delete_save_state'); - Broker.instance().unSubscribeAll('tested_connection'); - Broker.instance().unSubscribeAll('save_state_selected'); - Broker.instance().unSubscribeAll('query_translation_result'); - // Broker.instance().unSubscribeAll('query_builder_state'); - allMLTypes.forEach((mlType) => { - Broker.instance().unSubscribeAll(mlType); + // clear callback subscriptions + unsubs.forEach((unsub) => { + unsub(); }); }; }, []); @@ -179,7 +197,11 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } wsGetState(paramSaveState); } - wsGetStates(); + dispatch(setFetchingSaveStates(true)); + wsGetStates((data) => { + dispatch(setFetchingSaveStates(false)); + return true; + }); // Broker.instance().sendMessage({ //TODO!! // sessionID: auth?.sessionID || '', diff --git a/libs/shared/lib/data-access/broker/broker.tsx b/libs/shared/lib/data-access/broker/broker.tsx index 5b1031cfc..9be76a06b 100644 --- a/libs/shared/lib/data-access/broker/broker.tsx +++ b/libs/shared/lib/data-access/broker/broker.tsx @@ -25,6 +25,7 @@ export class Broker { private listeners: Record<string, Record<string, Function>> = {}; private catchAllListener: ((data: Record<string, any>, routingKey: string) => void) | undefined; + private callbackListeners: Record<string, Function> = {}; private webSocket: WebSocket | undefined; private url: string; @@ -138,26 +139,11 @@ export class Broker { this.connected = true; onOpen(); }; - this.webSocket.onmessage = this.onWebSocketMessage; + this.webSocket.onmessage = this.receiveMessage; this.webSocket.onerror = this.onError; this.webSocket.onclose = this.onClose; } - public sendMessage(message: SendMessageI): void { - console.debug('%cSending WS message: ', 'background: #222; color: #bada55', message); - let fullMessage = message as SendMessageWithSessionI; - fullMessage.sessionID = this.authHeader?.sessionID ?? ''; - if (message.body && typeof message.body !== 'string') { - fullMessage.body = JSON.stringify(message.body); - } - - if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage)); - else - this.connect(() => { - if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage)); - }); - } - /** Closes the current websocket connection. */ public close = (): void => { if (this.webSocket) this.webSocket.close(); @@ -194,18 +180,55 @@ export class Broker { setTimeout(() => Broker.instance().attemptReconnect(), 5000); } + public sendMessage(message: SendMessageI, callback?: Function): void { + console.debug('%cSending WS message: ', 'background: #222; color: #bada55', message); + let fullMessage = message as SendMessageWithSessionI; + + const uuid = (Date.now() + Math.floor(Math.random() * 100)).toString(); + fullMessage.callID = uuid; + + if (callback) { + this.callbackListeners[uuid] = callback; + } + + fullMessage.sessionID = this.authHeader?.sessionID ?? ''; + if (message.body && typeof message.body !== 'string') { + fullMessage.body = JSON.stringify(message.body); + } + + if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage)); + else + this.connect(() => { + if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage)); + }); + } + /** * Websocket connection message event handler. Called if a new message is received through the socket. * @param {any} event Contains the event data. */ - public onWebSocketMessage = (event: MessageEvent<any>) => { + public receiveMessage = (event: MessageEvent<any>) => { let jsonObject: ReceiveMessageI = JSON.parse(event.data); const routingKey = jsonObject.type; const data = jsonObject.value; + const uuid = jsonObject.callID; + let stop = false; // check in case there is a specific callback listener and, if its response is true, also call the subscriptions this.mostRecentMessages[routingKey] = data; + if (uuid in this.callbackListeners) { + stop = this.callbackListeners[uuid](data) === true; + console.debug( + '%c' + routingKey + ` WS response WITH CALLBACK`, + 'background: #222; color: #DBAB2F', + data, + this.callbackListeners, + 'stop=', + stop, + ); + delete this.callbackListeners[uuid]; + } - if (this.listeners[routingKey] && Object.keys(this.listeners[routingKey]).length != 0) { + if (!stop && this.listeners[routingKey] && Object.keys(this.listeners[routingKey]).length != 0) { if (this.catchAllListener) { this.catchAllListener(data, routingKey); } @@ -213,7 +236,7 @@ export class Broker { console.debug('%c' + routingKey + ` WS response`, 'background: #222; color: #DBAB2F', data); } // If there are no listeners, log the message - else { + else if (!stop) { if (this.catchAllListener) { this.catchAllListener(data, routingKey); console.debug(routingKey, `catch all used for message with routing key`, data); diff --git a/libs/shared/lib/data-access/broker/index.ts b/libs/shared/lib/data-access/broker/index.ts index 1ca07b467..e29a86394 100644 --- a/libs/shared/lib/data-access/broker/index.ts +++ b/libs/shared/lib/data-access/broker/index.ts @@ -2,3 +2,4 @@ export * from './broker'; export * from './wsState'; export * from './wsQuery'; export * from './wsSchema'; +export * from './types'; diff --git a/libs/shared/lib/data-access/broker/types.ts b/libs/shared/lib/data-access/broker/types.ts index ce9f967eb..804715a37 100644 --- a/libs/shared/lib/data-access/broker/types.ts +++ b/libs/shared/lib/data-access/broker/types.ts @@ -1,4 +1,5 @@ export type ReceiveMessageI = { + callID: string; type: string; status: string; value: Record<string, any>; @@ -45,6 +46,7 @@ export type SendMessageI = { }; export type SendMessageWithSessionI = SendMessageI & { + callID: string; sessionID: string; body?: string; }; diff --git a/libs/shared/lib/data-access/broker/wsQuery.ts b/libs/shared/lib/data-access/broker/wsQuery.ts index 9a2117171..77025d916 100644 --- a/libs/shared/lib/data-access/broker/wsQuery.ts +++ b/libs/shared/lib/data-access/broker/wsQuery.ts @@ -5,6 +5,8 @@ import { useAuth } from '../authorization'; import { useSessionCache } from '../store'; import { BackendQueryFormat } from '../../querybuilder'; import { Broker } from './broker'; +import { QueryBuilderText } from '../store/querybuilderSlice'; +import { GraphQueryResultFromBackendPayload } from '../store/graphQueryResultSlice'; export function wsQueryRequest(query: BackendQueryFormat) { if (query.cached === undefined) query.cached = false; @@ -14,3 +16,19 @@ export function wsQueryRequest(query: BackendQueryFormat) { body: query, }); } + +type QueryTranslationResponse = (data: QueryBuilderText) => void; +export function wsQueryTranslationSubscription(callback: QueryTranslationResponse) { + const id = Broker.instance().subscribe(callback, 'query_translation_result'); + return () => { + Broker.instance().unSubscribe('query_translation_result', id); + }; +} + +type QueryResultResponse = (data: GraphQueryResultFromBackendPayload) => void; +export function wsQuerySubscription(callback: QueryResultResponse) { + const id = Broker.instance().subscribe(callback, 'query_result'); + return () => { + Broker.instance().unSubscribe('query_result', id); + }; +} diff --git a/libs/shared/lib/data-access/broker/wsSchema.ts b/libs/shared/lib/data-access/broker/wsSchema.ts index 9a6eebc0b..81944b7f8 100644 --- a/libs/shared/lib/data-access/broker/wsSchema.ts +++ b/libs/shared/lib/data-access/broker/wsSchema.ts @@ -1,5 +1,6 @@ // All database related API calls +import { SchemaFromBackend } from '../../schema'; import { Broker } from './broker'; export function wsSchemaRequest(saveStateID: string) { @@ -12,3 +13,10 @@ export function wsSchemaRequest(saveStateID: string) { }, }); } +type SchemaResponse = (data: SchemaFromBackend) => void; +export function wsSchemaSubscription(callback: SchemaResponse) { + const id = Broker.instance().subscribe(callback, 'schema_result'); + return () => { + Broker.instance().unSubscribe('schema_result', id); + }; +} diff --git a/libs/shared/lib/data-access/broker/wsState.tsx b/libs/shared/lib/data-access/broker/wsState.tsx index 160706213..4a375ef39 100644 --- a/libs/shared/lib/data-access/broker/wsState.tsx +++ b/libs/shared/lib/data-access/broker/wsState.tsx @@ -2,6 +2,7 @@ import { QueryBuilderState } from '../store/querybuilderSlice'; import { URLParams, setParam } from '../api/url'; import { Broker } from './broker'; import { VisState } from '../store/visualizationSlice'; +import { DateStringStatement } from '../../querybuilder/model/logic/general'; // export function wsGetState() { // Broker.instance().subscribe((data) => dispatch(readInSchemaFromBackend(data)), 'schema_result'); @@ -45,70 +46,130 @@ export type SaveStateI = { share_state: any; }; -export function wsGetState(saveStateId: string) { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'get', - body: { saveStateId: saveStateId }, //messageTypeGetSaveState - }); +type GetStateResponse = (data: SaveStateI) => void; +export function wsGetState(saveStateId: string, callback?: GetStateResponse) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'get', + body: { saveStateId: saveStateId }, //messageTypeGetSaveState + }, + callback, + ); +} +export function wsGetStateSubscription(callback: GetStateResponse) { + const id = Broker.instance().subscribe(callback, 'save_state'); + return () => { + Broker.instance().unSubscribe('save_state', id); + }; } -export function wsGetStates() { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'getAll', - }); +type GetStatesResponse = (data: SaveStateI[]) => void | boolean; +export function wsGetStates(callback?: GetStatesResponse) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'getAll', + }, + callback, + ); +} +export function wsGetStatesSubscription(callback: GetStatesResponse) { + const id = Broker.instance().subscribe(callback, 'save_states'); + return () => { + Broker.instance().unSubscribe('save_states', id); + }; } -export function wsSelectState(saveStateId: string | undefined) { +type SelectStateResponse = (data: { saveStateID: string; success: boolean }) => void; +export function wsSelectState(saveStateId: string | undefined, callback?: SelectStateResponse) { if (saveStateId === undefined) saveStateId = ''; - Broker.instance().sendMessage({ - key: 'state', - subKey: 'select', - body: { saveStateId: saveStateId }, //messageTypeGetSaveState - }); + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'select', + body: { saveStateId: saveStateId }, //messageTypeGetSaveState + }, + callback, + ); Broker.instance().useSaveStateID(saveStateId); setParam(URLParams.saveState, saveStateId); } +export function wsSelectStateSubscription(callback: SelectStateResponse) { + const id = Broker.instance().subscribe(callback, 'save_state_selected'); + return () => { + Broker.instance().unSubscribe('save_state_selected', id); + }; +} -export function wsCreateState(request: SaveStateI) { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'create', - body: request, //SaveStateModel - }); +export function wsCreateState(request: SaveStateI, callback?: GetStateResponse) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'create', + body: request, //SaveStateModel + }, + callback, + ); + // Also returns save_state } -export function wsDeleteState(id: string) { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'delete', - body: { saveStateId: id }, //messageTypeGetSaveState - }); +type DeleteStateResponse = (status: 'deleted' | DateStringStatement) => void; +export function wsDeleteState(id: string, callback?: Function) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'delete', + body: { saveStateId: id }, //messageTypeGetSaveState + }, + callback, + ); +} +export function wsDeleteStateSubscription(callback: DeleteStateResponse) { + const id = Broker.instance().subscribe(callback, 'delete_save_state'); + return () => { + Broker.instance().unSubscribe('delete_save_state', id); + }; } -export function wsTestSaveStateConnection(id: string) { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'testConnection', - body: { saveStateId: id }, //messageTypeGetSaveState - }); +type TestSaveStateConnectionResponse = (data: { status: 'success' | 'fail'; saveStateID: string }) => void; +export function wsTestSaveStateConnection(id: string, callback?: Function) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'testConnection', + body: { saveStateId: id }, //messageTypeGetSaveState + }, + callback, + ); +} +export function wsTestSaveStateConnectionSubscription(callback: TestSaveStateConnectionResponse) { + const id = Broker.instance().subscribe(callback, 'tested_connection'); + return () => { + Broker.instance().unSubscribe('tested_connection', id); + }; } -export function wsUpdateState(request: SaveStateI) { - Broker.instance().sendMessage({ - key: 'state', - subKey: 'update', - body: request, //SaveStateModel - }); +export function wsUpdateState(request: SaveStateI, callback?: GetStateResponse) { + Broker.instance().sendMessage( + { + key: 'state', + subKey: 'update', + body: request, //SaveStateModel + }, + callback, + ); + // Also returns save_state } -export type TestDatabaseConnectionResponse = { status: 'success' | 'fail'; saveStateID: string }; -export function wsTestDatabaseConnection(dbConnection: DatabaseInfo) { - Broker.instance().sendMessage({ - key: 'dbConnection', - subKey: 'testConnection', - body: dbConnection, //DBConnectionModel - }); +export function wsTestDatabaseConnection(dbConnection: DatabaseInfo, callback?: TestSaveStateConnectionResponse) { + Broker.instance().sendMessage( + { + key: 'dbConnection', + subKey: 'testConnection', + body: dbConnection, //DBConnectionModel + }, + callback, + ); } diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts index 045558b8b..a161f0d19 100644 --- a/libs/shared/lib/data-access/store/sessionSlice.ts +++ b/libs/shared/lib/data-access/store/sessionSlice.ts @@ -13,6 +13,7 @@ export type ErrorMessage = { export type SessionCacheI = { currentSaveState?: string; // id of the current save state saveStates: Record<string, SaveStateI>; + fetchingSaveStates: boolean; testedSaveState: Record<string, DatabaseStatus>; }; @@ -20,6 +21,7 @@ export type SessionCacheI = { export const initialState: SessionCacheI = { currentSaveState: undefined, saveStates: {}, + fetchingSaveStates: true, // default to true to prevent flashing of the UI testedSaveState: {}, }; @@ -28,6 +30,9 @@ export const sessionSlice = createSlice({ name: 'session', initialState: initialState, reducers: { + setFetchingSaveStates: (state: SessionCacheI, action: PayloadAction<boolean>) => { + state.fetchingSaveStates = action.payload; + }, selectSaveState: (state: SessionCacheI, action: PayloadAction<string | undefined>) => { if (action.payload && action.payload in state.saveStates) { state.currentSaveState = action.payload; @@ -95,6 +100,7 @@ export const { setSaveStateList, addSaveState, testedSaveState, + setFetchingSaveStates, updateSelectedSaveState, } = sessionSlice.actions; diff --git a/libs/shared/package.json b/libs/shared/package.json index f40bd4dfc..c59615c5a 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -135,6 +135,7 @@ "redux": "^5.0.1", "redux-thunk": "^3.1.0", "require-from-string": "^2.0.2", + "reselect": "^5.1.0", "tailwindcss": "^3.4.1", "ts-node": "10.9.2", "typescript": "^5.3.3", diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json index fb21f80da..42051d225 100644 --- a/libs/shared/tsconfig.json +++ b/libs/shared/tsconfig.json @@ -28,7 +28,8 @@ "@graphpolaris/config/*": ["../../libs/config/src/*"], "redux": ["./node_modules/redux"], "@storybook/types": ["./node_modules/@storybook/types"], - "redux-thunk": ["./node_modules/redux-thunk"] + "redux-thunk": ["./node_modules/redux-thunk"], + "reselect": ["./node_modules/reselect"] }, "types": ["node", "vite/client"] }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4e1616f8..a8312fdf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,9 @@ importers: redux-thunk: specifier: ^3.1.0 version: 3.1.0(redux@5.0.1) + reselect: + specifier: ^5.1.0 + version: 5.1.0 tailwindcss: specifier: ^3.4.1 version: 3.4.1(ts-node@10.9.2) @@ -548,6 +551,9 @@ importers: require-from-string: specifier: ^2.0.2 version: 2.0.2 + reselect: + specifier: ^5.1.0 + version: 5.1.0 tailwindcss: specifier: ^3.4.1 version: 3.4.1(ts-node@10.9.2) @@ -18488,7 +18494,6 @@ packages: /reselect@5.1.0: resolution: {integrity: sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==} - dev: false /resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} -- GitLab