Skip to content

Commit 5c305c4

Browse files
Created validation for ksqlDB URL as well as duration in the event a user updates settings
1 parent 02fa9ec commit 5c305c4

File tree

4 files changed

+225
-33
lines changed

4 files changed

+225
-33
lines changed

ksqLight/server/server.js

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,18 @@ const RealTimeType = new GraphQLObjectType({
3535
resolve: (parent, args, context) => parent[1]
3636
}
3737
})
38+
});
39+
40+
const ValidationType = new GraphQLObjectType({
41+
name: 'inputValidation',
42+
description: 'Object indicating input validity status and error message',
43+
fields: () => ({
44+
isValid: { type: GraphQLBoolean },
45+
error: { type: GraphQLString }
46+
})
3847
})
3948

4049
//---------------Root Query Types----------------
41-
// todo: change the Number(val) implementation
4250
const RootQueryType = new GraphQLObjectType({
4351
name: 'Query',
4452
description: 'Root Query',
@@ -127,17 +135,62 @@ const RootQueryType = new GraphQLObjectType({
127135
}
128136
},
129137
isValidPrometheusURL: {
130-
type: GraphQLBoolean,
131-
description: 'Boolean value representing whether provided Prometheus URL points to valid Prometheus server',
138+
type: ValidationType,
139+
description: 'Object representing whether provided Prometheus URL points to valid Prometheus server and any errors',
132140
args: {
133141
prometheusURL: { type: GraphQLNonNull(GraphQLString)}
134142
},
135143
resolve: async (parent, { prometheusURL }) => {
136144
if (prometheusURL[prometheusURL.length] === '/') prometheusURL = prometheusURL.slice(0, prometheusURL.length - 1);
137145

138146
return axios.get(`${prometheusURL}/api/v1/status/buildinfo`)
139-
.then(res => res.status === 200)
140-
.catch(error => error);
147+
.then(res => ({
148+
isValid: true,
149+
error: null
150+
}))
151+
.catch(error => ({
152+
isValid: false,
153+
error: error.message
154+
}));
155+
}
156+
},
157+
isValidKsqlDBURL: {
158+
type: ValidationType,
159+
description: 'Object representing whether provided ksqlDB URL points to valid Prometheus server and any errors',
160+
args: {
161+
ksqlDBURL: { type: GraphQLNonNull(GraphQLString)}
162+
},
163+
resolve: (parent, { ksqlDBURL }) => {
164+
if (ksqlDBURL[ksqlDBURL.length] === '/') ksqlDBURL = ksqlDBURL.slice(0, ksqlDBURL.length - 1);
165+
166+
return axios.get(`${ksqlDBURL}/clusterStatus`)
167+
.then(res => ({
168+
isValid: true,
169+
error: null
170+
}))
171+
.catch(error => ({
172+
isValid: false,
173+
error: error.message
174+
}));
175+
}
176+
},
177+
isValidDuration: {
178+
type: GraphQLBoolean,
179+
description: 'Boolean representing whether Prometheus server accepts user duration.',
180+
args: {
181+
metric: { type: GraphQLNonNull(GraphQLString)},
182+
start: { type: GraphQLNonNull(GraphQLInt)},
183+
end: { type: GraphQLNonNull(GraphQLInt)},
184+
resolution: { type: GraphQLNonNull(GraphQLInt)},
185+
prometheusURL: { type: GraphQLNonNull(GraphQLString)}
186+
},
187+
resolve: (parent, { start, end, resolution, metric, prometheusURL }) => {
188+
if (prometheusURL[prometheusURL.length] === '/') prometheusURL = prometheusURL.slice(0, prometheusURL.length - 1);
189+
190+
191+
return axios.get(`${prometheusURL}/api/v1/query_range?step=${resolution}s&end=${end}&start=${start}&query=${queryTypes[metric]}`)
192+
.then(res => true)
193+
.catch(error => false);
141194
}
142195
}
143196
})

ksqLight/src/components/LineChart.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function LineChart({ metric, description, metricsState }) {
3737

3838
initialData = client.query({
3939
query: gql`
40-
query testQuery {
40+
query fetchMetric {
4141
ksqlDBMetrics(prometheusURL: "${metricsState.prometheusURL}" metric: "${metric}", resolution: ${metricsState.refreshRate}, start: ${unixStart}, end: ${unixEnd}) {
4242
x,
4343
y

ksqLight/src/components/SettingsSidebar.js

Lines changed: 162 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//-----------Import External Modules-----------
12
import React, { useEffect } from "react";
23
import { useState } from "react";
34
import { TextField, Typography, MenuItem, Select, Drawer, IconButton, Grid, Button, Stack } from "@mui/material"
@@ -9,20 +10,25 @@ import {
910
gql,
1011
} from "@apollo/client";
1112

13+
//-----------Import Internal Modules-----------
14+
import {getUnixRange, getDuration, validateDuration} from "../utils/utilityFunctions.js";
15+
1216
const client = new ApolloClient({
1317
uri: "http://localhost:5001/graphql",
1418
cache: new InMemoryCache(),
1519
});
1620

1721
export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, setMetricsState }) => {
22+
const [invalidPrometheusMessage, setInvalidPrometheusMessage] = useState(null);
23+
const [invalidKsqlDBMessage, setInvalidKsqlDBMessage] = useState(null);
24+
const [invalidDuration, setInvalidDuration] = useState(false);
25+
const [showSubmissionConfirmation, setShowSubmissionConfirmation] = useState(false);
1826
const [localMetricsState, setLocalMetricsState] = useState({
1927
prometheusURL: metricsState.prometheusURL,
2028
ksqlDBURL: metricsState.ksqlDBURL,
2129
duration: metricsState.duration,
2230
refreshRate: metricsState.refreshRate
2331
});
24-
const [invalidPrometheusMessage, setInvalidPrometheusMessage] = useState(null);
25-
const [showSubmissionConfirmation, setShowSubmissionConfirmation] = useState(false);
2632

2733
const handleLocalMetrics = (event) => {
2834
switch(event.target.name) {
@@ -80,15 +86,59 @@ export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, s
8086
event.preventDefault();
8187

8288
// Verify Prometheus URL
83-
client.query({
84-
query: gql`
85-
query validatePrometheusURL{
86-
isValidPrometheusURL(prometheusURL: "${event.target[1].value}")
87-
}
88-
`
89-
})
90-
.then(res => {
91-
// update state
89+
try {
90+
// Validate Prometheus URL points to live server
91+
const {data: {isValidPrometheusURL: {isValid: prometheusValid, error: prometheusError}}} = await client.query({
92+
query: gql`
93+
query validatePrometheusURL{
94+
isValidPrometheusURL(prometheusURL: "${event.target[1].value}") {
95+
isValid,
96+
error
97+
}
98+
}
99+
`
100+
});
101+
102+
if (prometheusError) {
103+
setInvalidPrometheusMessage(prometheusError);
104+
return;
105+
} else if (prometheusValid) {
106+
setInvalidPrometheusMessage(null);
107+
}
108+
109+
// Validate ksqlDB URL points to live server (if provided)
110+
if (localMetricsState.ksqlDBURL) {
111+
const {data: {isValidKsqlDBURL: {isValid: ksqlDBValid, error: ksqlDBError}}} = await client.query({
112+
query: gql`
113+
query isValidKsqlDBURL{
114+
isValidKsqlDBURL(ksqlDBURL: "${event.target[3].value}") {
115+
isValid,
116+
error
117+
}
118+
}
119+
`
120+
});
121+
122+
if (ksqlDBError) {
123+
setInvalidKsqlDBMessage(ksqlDBError);
124+
return;
125+
} else if (ksqlDBValid) {
126+
setInvalidKsqlDBMessage(null);
127+
}
128+
} else {
129+
setInvalidKsqlDBMessage(null);
130+
}
131+
132+
// Validate Prometheus accepts user's duration input
133+
const duration = getDuration(localMetricsState.duration.days, localMetricsState.duration.hours, localMetricsState.duration.minutes);
134+
if (!validateDuration(duration, localMetricsState.refreshRate)) {
135+
setInvalidDuration(true);
136+
return;
137+
} else {
138+
setInvalidDuration(false);
139+
}
140+
141+
// Update state with user's input values
92142
setMetricsState({
93143
prometheusURL: localMetricsState.prometheusURL,
94144
ksqlDBURL: localMetricsState.ksqlDBURL,
@@ -99,13 +149,11 @@ export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, s
99149
},
100150
refreshRate: localMetricsState.refreshRate
101151
});
102-
setInvalidPrometheusMessage(null);
103152
setShowSubmissionConfirmation(true);
104153
setTimeout(() => setShowSubmissionConfirmation(false), 3000);
105-
})
106-
.catch(error => {
107-
setInvalidPrometheusMessage(error.message);
108-
});
154+
} catch (error) {
155+
console.log(error);
156+
}
109157
}
110158

111159
return(
@@ -144,18 +192,106 @@ export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, s
144192
<hr className="w-full invisible mb-2 mt-2"></hr>
145193
<Typography variant="h6" sx={{color: "#333"}}>ksqlDB Connection</Typography>
146194
<hr className="w-full mb-3 mt-1"></hr>
147-
<TextField
148-
fullWidth
149-
variant="outlined"
150-
label="URL"
151-
name="ksqldb-url"
152-
onChange={handleLocalMetrics}
153-
value={localMetricsState.ksqlDBURL}
154-
/>
195+
{invalidKsqlDBMessage ? (
196+
<>
197+
<TextField
198+
error
199+
fullWidth
200+
variant="outlined"
201+
label="URL"
202+
name="ksqldb-url"
203+
onChange={handleLocalMetrics}
204+
value={localMetricsState.ksqlDBURL}
205+
/>
206+
<Typography variant="h8" sx={{color: "red"}}>{invalidKsqlDBMessage}</Typography>
207+
</>
208+
) : (
209+
<TextField
210+
fullWidth
211+
variant="outlined"
212+
label="URL"
213+
name="ksqldb-url"
214+
onChange={handleLocalMetrics}
215+
value={localMetricsState.ksqlDBURL}
216+
/>
217+
)}
155218
<hr className="w-full invisible mb-2 mt-2"></hr>
156219
<Typography variant="h6" sx={{color: "#333"}}>Duration</Typography>
157220
<hr className="w-full mb-3 mt-1"></hr>
158-
<Grid spacing={2} container justifyContent="flex-start" alignItems="center">
221+
{ invalidDuration ? (
222+
<>
223+
<Grid spacing={2} container justifyContent="flex-start" alignItems="center">
224+
<Grid item xs={4}>
225+
<TextField
226+
error
227+
variant="outlined"
228+
label="Days"
229+
name="duration-days"
230+
onChange={handleLocalMetrics}
231+
value={localMetricsState.duration.days}
232+
type="number"
233+
/>
234+
</Grid>
235+
<Grid item xs={4}>
236+
<TextField
237+
error
238+
variant="outlined"
239+
label="Hours"
240+
name="duration-hours"
241+
onChange={handleLocalMetrics}
242+
value={localMetricsState.duration.hours}
243+
type="number"
244+
/>
245+
</Grid>
246+
<Grid item xs={4}>
247+
<TextField
248+
error
249+
variant="outlined"
250+
label="Minutes"
251+
name="duration-minutes"
252+
onChange={handleLocalMetrics}
253+
value={localMetricsState.duration.minutes}
254+
type="number"
255+
/>
256+
</Grid>
257+
</Grid>
258+
<Typography variant="h8" sx={{color: "red"}}>Duration must not include more than 11,000 data points</Typography>
259+
</>
260+
) : (
261+
<Grid spacing={2} container justifyContent="flex-start" alignItems="center">
262+
<Grid item xs={4}>
263+
<TextField
264+
variant="outlined"
265+
label="Days"
266+
name="duration-days"
267+
onChange={handleLocalMetrics}
268+
value={localMetricsState.duration.days}
269+
type="number"
270+
/>
271+
</Grid>
272+
<Grid item xs={4}>
273+
<TextField
274+
variant="outlined"
275+
label="Hours"
276+
name="duration-hours"
277+
onChange={handleLocalMetrics}
278+
value={localMetricsState.duration.hours}
279+
type="number"
280+
/>
281+
</Grid>
282+
<Grid item xs={4}>
283+
<TextField
284+
variant="outlined"
285+
label="Minutes"
286+
name="duration-minutes"
287+
onChange={handleLocalMetrics}
288+
value={localMetricsState.duration.minutes}
289+
type="number"
290+
/>
291+
</Grid>
292+
</Grid>
293+
)}
294+
{/* <Grid spacing={2} container justifyContent="flex-start" alignItems="center">
159295
<Grid item xs={4}>
160296
<TextField
161297
variant="outlined"
@@ -164,7 +300,6 @@ export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, s
164300
onChange={handleLocalMetrics}
165301
value={localMetricsState.duration.days}
166302
type="number"
167-
168303
/>
169304
</Grid>
170305
<Grid item xs={4}>
@@ -187,7 +322,7 @@ export const SettingsSidebar = ({ showSettings, setShowSettings, metricsState, s
187322
type="number"
188323
/>
189324
</Grid>
190-
</Grid>
325+
</Grid> */}
191326
<hr className="w-full invisible mb-2 mt-2"></hr>
192327
<Typography variant="h6">Refresh Rate</Typography>
193328
<hr className="w-full mb-3 mt-1"></hr>

ksqLight/src/utils/utilityFunctions.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ utilityFunctions.getDuration = (days, hours, minutes) => {
1010
return (minutes * 60 + hours * 60 * 60 + days * 60 * 60 * 24) * 1000;
1111
};
1212

13+
utilityFunctions.validateDuration = (duration, resolution) => {
14+
return (duration / 1000) / resolution < 11000;
15+
}
16+
1317
module.exports = utilityFunctions;

0 commit comments

Comments
 (0)