Skip to content

Commit 010fafa

Browse files
author
rwalkow
committed
Added UI feature to manage event handlers - create/update
1 parent 9151411 commit 010fafa

File tree

11 files changed

+429
-66
lines changed

11 files changed

+429
-66
lines changed

docker/docker-compose.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
conductor-server:
55
environment:
66
- CONFIG_PROP=config-redis.properties
7-
- JAVA_OPTS=-Dpolyglot.engine.WarnInterpreterOnly=false
7+
- JAVA_OPTS=-Dpolyglot.engine.WarnInterpreterOnly=false -DloadSample=false
88
image: conductor:server
99
container_name: conductor-server
1010
build:
@@ -16,6 +16,7 @@ services:
1616
- internal
1717
ports:
1818
- 8080:8080
19+
- 8090:8090
1920
- 8127:5000
2021
healthcheck:
2122
test: ["CMD", "curl","-I" ,"-XGET", "http://localhost:8080/health"]
@@ -70,6 +71,14 @@ services:
7071
max-size: "1k"
7172
max-file: "3"
7273

74+
redisinsight:
75+
image: redis/redisinsight:2.42
76+
container_name: redisinsight
77+
networks:
78+
- internal
79+
ports:
80+
- "5540:5540"
81+
7382
volumes:
7483
esdata-conductor:
7584
driver: local

docker/server/config/config-redis.properties

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,16 @@ management.endpoints.web.exposure.include=health,prometheus
3131
management.health.redis.enabled=true
3232

3333
# Load sample kitchen sink workflow
34-
loadSample=true
34+
loadSample=false
35+
36+
conductor.event-queues.amqp.enabled=true
37+
conductor.event-queues.amqp.hosts=localhost
38+
conductor.event-queues.amqp.port=5673
39+
conductor.event-queues.amqp.username=points_banks_cyclic_subscriptions_performance_service_user
40+
conductor.event-queues.amqp.password=guest
41+
conductor.event-queues.amqp.virtualhost=conductor.engine
42+
conductor.event-queues.amqp.useSslProtocol=false
43+
#milliseconds
44+
conductor.event-queues.amqp.connectionTimeout=60000
45+
conductor.event-queues.amqp.useExchange=true
46+
conductor.event-queues.amqp.listenerQueuePrefix=

ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"test": "react-scripts test",
5454
"eject": "react-scripts eject",
5555
"prettier": "prettier --write .",
56-
"serve-build": "http-server ./build --port 5000 --proxy http://localhost:8080",
56+
"serve-build": "http-server ./build --port 5001 --proxy http://localhost:8080",
5757
"cypress:open": "cypress open",
5858
"cypress:test": "BROWSER=none start-server-and-test serve-build http-get://localhost:5000 'cypress run'"
5959
},

ui/src/App.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import WorkflowDefinition from "./pages/definition/WorkflowDefinition";
1616
import TaskDefinitions from "./pages/definitions/Task";
1717
import TaskDefinition from "./pages/definition/TaskDefinition";
1818
import EventHandlerDefinitions from "./pages/definitions/EventHandler";
19-
import EventHandlerDefinition from "./pages/definition/EventHandler";
19+
import EventHandlerDefinition from "./pages/definition/EventHandlerDefinition";
2020
import TaskQueue from "./pages/misc/TaskQueue";
2121
import KitchenSink from "./pages/kitchensink/KitchenSink";
2222
import DiagramTest from "./pages/kitchensink/DiagramTest";
@@ -122,10 +122,10 @@ export default function App() {
122122
<Route exact path="/taskDef/:name?">
123123
<TaskDefinition />
124124
</Route>
125-
<Route exact path="/eventHandlerDef">
125+
<Route exact path="/eventHandlerDefs">
126126
<EventHandlerDefinitions />
127127
</Route>
128-
<Route exact path="/eventHandlerDef/:name">
128+
<Route exact path="/eventHandlerDef/:event?/:name?">
129129
<EventHandlerDefinition />
130130
</Route>
131131
<Route exact path="/taskQueue/:name?">

ui/src/data/eventHandler.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useFetch } from "./common";
2+
import { useMemo } from "react";
3+
import { fetchWithContext, useFetchContext } from "../plugins/fetch";
4+
import { useMutation } from "react-query";
5+
import Path from "../utils/path";
6+
7+
export function useEventHandler(eventHandlerEvent, eventHandlerName, defaultEventHandler) {
8+
let path;
9+
if (eventHandlerEvent) {
10+
path = new Path(`/event/${eventHandlerEvent}`);
11+
path.search.append('activeOnly', false)
12+
}
13+
14+
const query = useFetch(["eventHandlerDef", eventHandlerEvent], path);
15+
16+
const eventHandler = useMemo(
17+
() => {
18+
const data = query.data;
19+
20+
if (!data || data.leading < 1) {
21+
return defaultEventHandler;
22+
}
23+
24+
return data ? data.find((row) => row.name === eventHandlerName) : defaultEventHandler
25+
},
26+
[query.data, eventHandlerName, defaultEventHandler]
27+
);
28+
29+
return {
30+
...query,
31+
eventHandler
32+
}
33+
}
34+
35+
export function useEventHandlerDefs() {
36+
return useFetch(["eventHandlerDefs"], "/event");
37+
}
38+
39+
export function useEventHandlerNames() {
40+
const { data } = useEventHandlerDefs();
41+
return useMemo(
42+
() => (data ? Array.from(new Set(data.map((def) => def.name))).sort() : []),
43+
[data]
44+
);
45+
}
46+
47+
export function useSaveEventHandler(callbacks) {
48+
const path = "/event";
49+
const fetchContext = useFetchContext();
50+
51+
return useMutation(({ body, isNew }) => {
52+
console.log(isNew);
53+
return fetchWithContext(path, fetchContext, {
54+
method: isNew ? "post" : "put",
55+
headers: {
56+
"Content-Type": "application/json",
57+
},
58+
body: JSON.stringify(body),
59+
});
60+
}, callbacks);
61+
}

ui/src/pages/definition/EventHandler.jsx

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import React, { useMemo, useRef, useState } from "react";
2+
import { useRouteMatch } from "react-router-dom";
3+
import { makeStyles } from "@material-ui/styles";
4+
import { Helmet } from "react-helmet";
5+
import { Button, LinearProgress, Pill, Text } from "../../components";
6+
import _ from "lodash";
7+
import Editor from "@monaco-editor/react";
8+
import { Toolbar } from "@material-ui/core";
9+
import { configureMonaco, NEW_EVENT_HANDLER_TEMPLATE } from "../../schema/eventHandler";
10+
import { useEventHandler } from "../../data/eventHandler";
11+
import ResetConfirmationDialog from "./ResetConfirmationDialog";
12+
import { usePushHistory } from "../../components/NavLink";
13+
import SaveEventHandlerDialog from "./SaveEventHandlerDialog";
14+
15+
const useStyles = makeStyles({
16+
wrapper: {
17+
display: "flex",
18+
height: "100%",
19+
alignItems: "stretch",
20+
flexDirection: "column",
21+
},
22+
name: {
23+
fontWeight: "bold",
24+
},
25+
rightButtons: {
26+
display: "flex",
27+
flexGrow: 1,
28+
justifyContent: "flex-end",
29+
gap: 8,
30+
},
31+
});
32+
33+
export default function EventHandlerDefinition() {
34+
const classes = useStyles();
35+
const match = useRouteMatch();
36+
const navigate = usePushHistory();
37+
38+
const [isModified, setIsModified] = useState(false);
39+
const [jsonErrors, setJsonErrors] = useState([]);
40+
const [resetDialog, setResetDialog] = useState(false);
41+
const [saveDialog, setSaveDialog] = useState(null);
42+
43+
const editorRef = useRef();
44+
const eventHandlerName = _.get(match, "params.name");
45+
const eventHandlerEvent = _.get(match, "params.event");
46+
47+
const {
48+
eventHandler: eventHandlerDef,
49+
isFetching,
50+
refetch,
51+
} = useEventHandler(eventHandlerEvent, eventHandlerName, NEW_EVENT_HANDLER_TEMPLATE);
52+
53+
console.log(eventHandlerDef);
54+
55+
const eventHandlerJson = useMemo(
56+
() => (eventHandlerDef ? JSON.stringify(eventHandlerDef, null, 2) : ""),
57+
[eventHandlerDef]
58+
);
59+
60+
const handleOpenSave = () => {
61+
setSaveDialog({
62+
original: eventHandlerName ? eventHandlerJson : "",
63+
originalObj: eventHandlerName ? eventHandlerDef : null,
64+
modified: editorRef.current.getModel().getValue(),
65+
});
66+
};
67+
68+
const handleSaveSuccess = (event, name) => {
69+
setSaveDialog(null);
70+
setIsModified(false);
71+
72+
if (name === eventHandlerName) {
73+
refetch();
74+
} else {
75+
navigate(`/eventHandlerDef/${event}/${name}`);
76+
}
77+
}
78+
79+
// Reset
80+
const doReset = () => {
81+
editorRef.current.getModel().setValue(eventHandlerJson);
82+
83+
setResetDialog(false);
84+
setIsModified(false);
85+
};
86+
87+
// Monaco Handlers
88+
const handleEditorWillMount = (monaco) => {
89+
configureMonaco(monaco);
90+
};
91+
92+
const handleEditorDidMount = (editor) => {
93+
editorRef.current = editor;
94+
};
95+
96+
const handleValidate = (markers) => {
97+
setJsonErrors(markers);
98+
};
99+
100+
const handleChange = (v) => {
101+
setIsModified(v !== eventHandlerJson);
102+
};
103+
104+
return (
105+
<>
106+
<Helmet>
107+
<title>
108+
Conductor UI - Event Handler Definition - {eventHandlerName || "New Event Handler"}
109+
</title>
110+
</Helmet>
111+
112+
<SaveEventHandlerDialog
113+
document={saveDialog}
114+
onCancel={() => setSaveDialog(null)}
115+
onSuccess={handleSaveSuccess}
116+
/>
117+
118+
<ResetConfirmationDialog
119+
version={resetDialog}
120+
onConfirm={doReset}
121+
onClose={() => setResetDialog(false)}
122+
/>
123+
124+
{isFetching && <LinearProgress />}
125+
<div className={classes.wrapper}>
126+
<Toolbar>
127+
<Text className={classes.name}>{eventHandlerName || "NEW"}</Text>
128+
129+
{isModified ? (
130+
<Pill color="yellow" label="Modified" />
131+
) : (
132+
<Pill label="Unmodified" />
133+
)}
134+
{!_.isEmpty(jsonErrors) && <Pill color="red" label="Validation" />}
135+
136+
<div className={classes.rightButtons}>
137+
<Button
138+
disabled={!_.isEmpty(jsonErrors) || !isModified}
139+
onClick={handleOpenSave}
140+
>
141+
Save
142+
</Button>
143+
<Button
144+
disabled={!isModified}
145+
onClick={() => setResetDialog(true)}
146+
variant="secondary"
147+
>
148+
Reset
149+
</Button>
150+
</div>
151+
</Toolbar>
152+
153+
<Editor
154+
height="100%"
155+
width="100%"
156+
theme="vs-light"
157+
language="json"
158+
value={eventHandlerJson}
159+
autoIndent={true}
160+
beforeMount={handleEditorWillMount}
161+
onMount={handleEditorDidMount}
162+
onValidate={handleValidate}
163+
onChange={handleChange}
164+
options={{
165+
selectOnLineNumbers: true,
166+
minimap: {
167+
enabled: false,
168+
},
169+
}}
170+
/>
171+
</div>
172+
</>
173+
);
174+
}

0 commit comments

Comments
 (0)