Skip to content

Commit d756203

Browse files
feat(frontend): add subscriptions to react flow views
1 parent c10c142 commit d756203

File tree

15 files changed

+272
-73
lines changed

15 files changed

+272
-73
lines changed

charts/dashboard/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: dashboard
33
description: A dashboard for Diamond workflows
44
type: application
5-
version: 0.2.2
5+
version: 0.2.3
66
appVersion: 0.1.8
77
dependencies:
88
- name: common

charts/dashboard/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ spec:
6060
value: {{ .Values.configuration.keycloakClient }}
6161
- name: GRAPH_URL
6262
value: {{ .Values.configuration.graphUrl }}
63+
- name: GRAPH_WS_URL
64+
value: {{ .Values.configuration.graphWsUrl }}
6365
- name: SOURCE_DIR
6466
value: {{ .Values.configuration.sourceDir }}
6567
livenessProbe:

charts/dashboard/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ configuration:
33
keycloakRealm: master
44
keycloakClient: workflows-dashboard
55
graphUrl: https://workflows.diamond.ac.uk/graphql
6+
graphWsUrl: wss://workflows.diamond.ac.uk/graphql/ws
67
sourceDir: "/usr/share/nginx/html"
78

89
image:

frontend/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- VITE_KEYCLOAK_REALM
1111
- VITE_KEYCLOAK_CLIENT
1212
- VITE_GRAPH_URL
13+
- VITE_GRAPH_WS_URL
1314
5. yarn dev in frontend/dashboard
1415

1516
## Linting, Formatting, Compiling and Testing

frontend/configure.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ replace_placeholder KEYCLOAK_URL
2424
replace_placeholder KEYCLOAK_REALM
2525
replace_placeholder KEYCLOAK_CLIENT
2626
replace_placeholder GRAPH_URL
27+
replace_placeholder GRAPH_WS_URL
2728

2829
nginx -g 'daemon off;'

frontend/dashboard/.env.production

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL = "{{ KEYCLOAK_URL }}"
22
VITE_KEYCLOAK_REALM = "{{ KEYCLOAK_REALM }}"
33
VITE_KEYCLOAK_CLIENT = "{{ KEYCLOAK_CLIENT }}"
44
VITE_GRAPH_URL = "{{ GRAPH_URL }}"
5+
VITE_GRAPH_WS_URL = "{{ GRAPH_WS_URL }}"

frontend/dashboard/src/RelayEnvironment.ts

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,27 @@ import {
44
RecordSource,
55
Store,
66
FetchFunction,
7+
SubscribeFunction,
8+
GraphQLResponse,
9+
Observable,
710
} from "relay-runtime";
811
import keycloak from "./keycloak";
12+
import { createClient } from "graphql-ws";
913

1014
const HTTP_ENDPOINT = import.meta.env.VITE_GRAPH_URL;
15+
const WS_ENDPOINT = import.meta.env.VITE_GRAPH_WS_URL;
16+
17+
let kcinitPromise: Promise<boolean> | null = null;
18+
19+
// needed to prevent repeated refresh of page when using subscriptions
20+
function ensureKeycloakInit(): Promise<boolean> {
21+
if (!kcinitPromise) {
22+
kcinitPromise = keycloak.init({
23+
onLoad: "login-required",
24+
});
25+
}
26+
return kcinitPromise;
27+
}
1128

1229
keycloak.onTokenExpired = () => {
1330
console.log("JWT expired");
@@ -25,23 +42,9 @@ keycloak.onTokenExpired = () => {
2542
});
2643
};
2744

28-
const kcinit = keycloak
29-
.init({
30-
onLoad: "login-required",
31-
})
32-
.then(
33-
(auth) => {
34-
console.info("Authenticated");
35-
console.log("auth", auth);
36-
},
37-
() => {
38-
console.error("Authentication failed");
39-
},
40-
);
41-
4245
const fetchFn: FetchFunction = async (request, variables) => {
4346
if (!keycloak.authenticated) {
44-
await kcinit;
47+
await ensureKeycloakInit();
4548
}
4649

4750
if (keycloak.token) {
@@ -66,11 +69,55 @@ const fetchFn: FetchFunction = async (request, variables) => {
6669
}
6770
};
6871

69-
function createRelayEnvironment() {
70-
return new Environment({
71-
network: Network.create(fetchFn),
72-
store: new Store(new RecordSource()),
72+
const wsClient = createClient({
73+
url: WS_ENDPOINT,
74+
connectionParams: async () => {
75+
if (!keycloak.authenticated) {
76+
await ensureKeycloakInit();
77+
}
78+
return {
79+
Authorization: `Bearer ${keycloak.token ?? ""}`,
80+
};
81+
},
82+
});
83+
84+
const subscribeFn: SubscribeFunction = (operation, variables) => {
85+
return Observable.create((sink) => {
86+
const cleanup = wsClient.subscribe(
87+
{
88+
operationName: operation.name,
89+
query: operation.text ?? "",
90+
variables,
91+
},
92+
{
93+
next: (response) => {
94+
const data = response.data;
95+
if (data) {
96+
sink.next({ data } as GraphQLResponse);
97+
} else if (data == null) {
98+
console.warn("Data is null:", response);
99+
} else {
100+
console.error("Subscription error response:", response);
101+
sink.error(new Error("Subscription response missing data"));
102+
}
103+
},
104+
error: sink.error.bind(sink),
105+
complete: sink.complete.bind(sink),
106+
},
107+
);
108+
return cleanup;
73109
});
74-
}
110+
};
75111

76-
export const RelayEnvironment = createRelayEnvironment();
112+
let RelayEnvironment: Environment | null = null;
113+
114+
export async function getRelayEnvironment(): Promise<Environment> {
115+
if (!RelayEnvironment) {
116+
await ensureKeycloakInit();
117+
RelayEnvironment = new Environment({
118+
network: Network.create(fetchFn, subscribeFn),
119+
store: new Store(new RecordSource()),
120+
});
121+
}
122+
return RelayEnvironment;
123+
}

frontend/dashboard/src/main.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RelayEnvironmentProvider } from "react-relay";
2-
import { RelayEnvironment } from "./RelayEnvironment";
2+
import { getRelayEnvironment } from "./RelayEnvironment";
33
import { StrictMode } from "react";
44
import { createRoot } from "react-dom/client";
55
import { createBrowserRouter, RouterProvider } from "react-router-dom";
@@ -47,12 +47,16 @@ const router = createBrowserRouter([
4747
},
4848
]);
4949

50-
createRoot(document.getElementById("root") as Element).render(
51-
<RelayEnvironmentProvider environment={RelayEnvironment}>
52-
<StrictMode>
53-
<ThemeProvider theme={DiamondTheme} defaultMode="light">
54-
<RouterProvider router={router} />
55-
</ThemeProvider>
56-
</StrictMode>
57-
</RelayEnvironmentProvider>,
58-
);
50+
const root = createRoot(document.getElementById("root") as Element);
51+
52+
void getRelayEnvironment().then((environment) => {
53+
root.render(
54+
<RelayEnvironmentProvider environment={environment}>
55+
<StrictMode>
56+
<ThemeProvider theme={DiamondTheme} defaultMode="light">
57+
<RouterProvider router={router} />
58+
</ThemeProvider>
59+
</StrictMode>
60+
</RelayEnvironmentProvider>,
61+
);
62+
});

frontend/dashboard/src/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface ImportMetaEnv {
55
readonly VITE_KEYCLOAK_REALM: string;
66
readonly VITE_KEYCLOAK_CLIENT: string;
77
readonly VITE_GRAPH_URL: string;
8+
readonly VITE_GRAPH_WS_URL: string;
89
}
910

1011
interface importMeta {

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"@xyflow/react": "^12.8.2",
2828
"ajv": "^8.17.1",
2929
"react": "^18.3.1",
30-
"react-dom": "^18.3.1"
30+
"react-dom": "^18.3.1",
31+
"graphql-ws": "^6.0.6"
3132
},
3233
"devDependencies": {
3334
"@eslint/js": "^9.33.0",

0 commit comments

Comments
 (0)