Skip to content

Commit 94ab980

Browse files
Persist Query Information betwen closing and opening
1 parent b32551f commit 94ab980

6 files changed

Lines changed: 151 additions & 51 deletions

File tree

web/src/components/Korrel8rPanel.tsx

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,46 +27,60 @@ import { TFunction, useTranslation } from 'react-i18next';
2727
import { CubesIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
2828
import { useURLState } from '../hooks/useURLState';
2929
import { usePluginAvailable } from '../hooks/usePluginAvailable';
30+
import { useDispatch, useSelector } from 'react-redux';
31+
import { State } from '../redux-reducers';
32+
import { Query, QueryType, setPersistedQuery } from '../redux-actions';
3033

3134
type Result = {
3235
graph?: Korrel8rGraphResponse;
3336
message?: string;
3437
title?: string;
3538
};
36-
enum QueryType {
37-
Neighbour,
38-
Goal,
39-
}
4039

4140
export default function Korrel8rPanel() {
4241
const { t } = useTranslation('plugin__troubleshooting-panel-console-plugin');
42+
const persistedQuery = useSelector((state: State) => {
43+
return state.plugins?.tp?.get('persistedQuery');
44+
}) as Query;
45+
const dispatch = useDispatch();
4346

4447
// State
4548
const { korrel8rQueryFromURL } = useURLState();
46-
const [query, setQuery] = React.useState(korrel8rQueryFromURL); // Initial value from URL
49+
// Initial value from persisted query if available (when opened from anywhere but
50+
// the alerts page or if never opened before), otherwise URL
51+
const initialQuery = persistedQuery.query
52+
? persistedQuery
53+
: {
54+
query: korrel8rQueryFromURL,
55+
queryType: QueryType.Neighbour,
56+
depth: 3,
57+
goal: null,
58+
};
59+
const [query, setQuery] = React.useState<Query>(initialQuery);
4760
const [result, setResult] = React.useState<Result | null>(null);
4861
const [showQuery, setShowQuery] = React.useState(false);
49-
const [queryType, setQueryType] = React.useState(QueryType.Neighbour);
50-
const [depth, setDepth] = React.useState(3);
51-
const [goal, setGoal] = React.useState('');
5262

5363
React.useEffect(() => {
5464
// Set result = null to trigger a reload, don't run the query till then.
5565
if (result !== null) {
5666
return;
5767
}
5868
let fetch: CancellableFetch<Korrel8rGraphResponse>;
59-
if (queryType === QueryType.Neighbour) {
60-
fetch = getNeighborsGraph({ query }, depth);
61-
} else if (queryType === QueryType.Goal) {
62-
fetch = getGoalsGraph({ query }, goal);
69+
if (query.queryType === QueryType.Neighbour) {
70+
fetch = getNeighborsGraph(query);
71+
} else if (query.queryType === QueryType.Goal) {
72+
fetch = getGoalsGraph(query);
6373
} else {
6474
return;
6575
}
6676
const { request, abort } = fetch;
6777
request()
6878
.then((response: Korrel8rGraphResponse) => {
6979
setResult({ graph: { nodes: response.nodes, edges: response.edges } });
80+
// Only set the persisted query upon a successful query. It would be a
81+
// poor feeling to create a query that fails, and then be forced to rerun it
82+
// when opening the panel later
83+
dispatch(setPersistedQuery(query));
7084
})
7185
.catch((e: Error) => {
7286
try {
@@ -76,7 +90,7 @@ export default function Korrel8rPanel() {
7690
}
7791
});
7892
return abort;
79-
}, [result, query, depth, goal, queryType, t]);
93+
}, [result, t, dispatch, query]);
8094

8195
const queryToggleID = 'query-toggle';
8296
const queryContentID = 'query-content';
@@ -91,12 +105,17 @@ export default function Korrel8rPanel() {
91105
: cannotFocus;
92106
const minDepth = 1;
93107
const maxDepth = 10;
94-
const runQuery = () => {
95-
if (!goal) setQueryType(QueryType.Neighbour); // If no goal do neighbours.
96-
if (!depth || depth < minDepth) setDepth(minDepth); // Min valid value is 1
97-
if (depth && depth > maxDepth) setDepth(maxDepth); // Max value
98-
setResult(null);
99-
};
108+
const depthBounds = applyBounds(1, 10);
109+
const runQuery = React.useCallback(
110+
(newQuery: Query) => {
111+
newQuery.depth = depthBounds(newQuery.depth);
112+
newQuery.queryType = !newQuery.goal ? QueryType.Neighbour : newQuery.queryType;
113+
114+
setQuery(newQuery);
115+
setResult(null);
116+
},
117+
[setResult, depthBounds],
118+
);
100119

101120
return (
102121
<>
@@ -105,8 +124,12 @@ export default function Korrel8rPanel() {
105124
<Button
106125
isAriaDisabled={!korrel8rQueryFromURL}
107126
onClick={() => {
108-
setQuery(korrel8rQueryFromURL);
109-
runQuery();
127+
runQuery({
128+
query: korrel8rQueryFromURL,
129+
queryType: QueryType.Neighbour,
130+
depth: 3,
131+
goal: null,
132+
});
110133
}}
111134
>
112135
{t('Focus')}
@@ -136,8 +159,13 @@ export default function Korrel8rPanel() {
136159
className="tp-plugin__panel-query-input"
137160
placeholder="domain:class:querydata"
138161
id={queryInputID}
139-
value={query}
140-
onChange={(_event, value) => setQuery(value)}
162+
value={query.query}
163+
onChange={(_event, value) =>
164+
setQuery({
165+
...query,
166+
query: value,
167+
})
168+
}
141169
resizeOrientation="vertical"
142170
/>
143171
</Tooltip>
@@ -147,22 +175,40 @@ export default function Korrel8rPanel() {
147175
label={t('Neighbourhood depth: ')}
148176
name={queryTypeOptions}
149177
id="neighbourhood-option"
150-
isChecked={queryType === QueryType.Neighbour}
178+
isChecked={query.queryType === QueryType.Neighbour}
151179
onChange={(_: React.FormEvent, on: boolean) => {
152-
on && setQueryType(QueryType.Neighbour);
180+
on &&
181+
setQuery({
182+
...query,
183+
queryType: QueryType.Neighbour,
184+
});
153185
}}
154186
/>
155187
</Tooltip>
156188
<NumberInput
157-
value={depth}
189+
value={query.depth}
158190
min={minDepth}
159191
max={maxDepth}
160-
isDisabled={queryType !== QueryType.Neighbour}
161-
onPlus={() => setDepth((depth || 0) + 1)}
162-
onMinus={() => (depth || 0) > minDepth && setDepth(depth - 1)}
192+
isDisabled={query.queryType !== QueryType.Neighbour}
193+
onPlus={() =>
194+
setQuery({
195+
...query,
196+
depth: (query.depth || 0) + 1,
197+
})
198+
}
199+
onMinus={() =>
200+
(query.depth || 0) > minDepth &&
201+
setQuery({
202+
...query,
203+
depth: query.depth - 1,
204+
})
205+
}
163206
onChange={(event: React.FormEvent<HTMLInputElement>) => {
164207
const n = Number((event.target as HTMLInputElement).value);
165-
setDepth(isNaN(n) ? 1 : n);
208+
setQuery({
209+
...query,
210+
depth: isNaN(n) ? 1 : n,
211+
});
166212
}}
167213
/>
168214
</Flex>
@@ -172,24 +218,33 @@ export default function Korrel8rPanel() {
172218
label={t('Goal class: ')}
173219
name={queryTypeOptions}
174220
id="goal-option"
175-
isChecked={queryType === QueryType.Goal}
176-
onChange={(_: React.FormEvent, on: boolean) => on && setQueryType(QueryType.Goal)}
221+
isChecked={query.queryType === QueryType.Goal}
222+
onChange={(_: React.FormEvent, on: boolean) =>
223+
on &&
224+
setQuery({
225+
...query,
226+
queryType: QueryType.Goal,
227+
})
228+
}
177229
/>
178230
</Tooltip>
179231
<FlexItem>
180232
<TextInput
181-
value={goal}
182-
isDisabled={queryType !== QueryType.Goal}
233+
value={query.goal}
234+
isDisabled={query.queryType !== QueryType.Goal}
183235
placeholder="domain:class"
184236
onChange={(event: React.FormEvent<HTMLInputElement>) => {
185-
setGoal((event.target as HTMLInputElement).value);
237+
setQuery({
238+
...query,
239+
goal: (event.target as HTMLInputElement).value,
240+
});
186241
}}
187242
aria-label="Korrel8r Query"
188243
/>
189244
</FlexItem>
190245
</Flex>
191246
</Flex>
192-
<Button isAriaDisabled={!query} onClick={() => runQuery()} variant="secondary">
247+
<Button isAriaDisabled={!query} onClick={() => runQuery(query)} variant="secondary">
193248
{t('Query')}
194249
</Button>
195250
</ExpandableSection>
@@ -204,7 +259,7 @@ export default function Korrel8rPanel() {
204259
interface TopologyProps {
205260
result?: Result;
206261
t: TFunction;
207-
setQuery: (query: string) => void;
262+
setQuery: (query: Query) => void;
208263
}
209264

210265
const Topology: React.FC<TopologyProps> = ({ result, t, setQuery }) => {
@@ -288,3 +343,15 @@ const TopologyInfoState: React.FC<TopologyInfoStateProps> = ({ titleText, text,
288343
</div>
289344
);
290345
};
346+
347+
const applyBounds = (minValue: number, maxValue: number) => {
348+
return (val: number) => {
349+
if (!val || val < minValue) {
350+
return minValue;
351+
} else if (val > maxValue) {
352+
return maxValue;
353+
} else {
354+
return val;
355+
}
356+
};
357+
};

web/src/components/topology/Korrel8rTopology.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { Korrel8rNode } from '../../korrel8r/korrel8r.types';
3333
import { InvalidNode } from '../../korrel8r/invalid';
3434
import { TFunction, useTranslation } from 'react-i18next';
3535
import './korrel8rtopology.css';
36+
import { Query, QueryType } from '../../redux-actions';
3637

3738
interface Korrel8rTopologyNodeProps {
3839
element: Node;
@@ -201,7 +202,7 @@ export const Korrel8rTopology: React.FC<{
201202
queryEdges: Array<QueryEdge>;
202203
loggingAvailable: boolean;
203204
netobserveAvailable: boolean;
204-
setQuery: (query: string) => void;
205+
setQuery: (query: Query) => void;
205206
}> = ({ queryNodes, queryEdges, loggingAvailable, netobserveAvailable, setQuery }) => {
206207
const { t } = useTranslation('plugin__troubleshooting-panel-console-plugin');
207208
const location = useLocation();
@@ -248,7 +249,12 @@ export const Korrel8rTopology: React.FC<{
248249
if (!korrel8rNode) {
249250
return;
250251
}
251-
setQuery(korrel8rNode.toQuery());
252+
setQuery({
253+
query: korrel8rNode.toQuery(),
254+
queryType: QueryType.Neighbour,
255+
depth: 3,
256+
goal: null,
257+
});
252258
history.push('/' + korrel8rNode.toURL());
253259
},
254260
[history, nodes, selectedIds, setQuery],

web/src/hooks/useTroubleshootingPanel.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InfrastructureIcon } from '@patternfly/react-icons';
33
import * as React from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { useDispatch } from 'react-redux';
6-
import { openTP } from '../redux-actions';
6+
import { openTP, QueryType, setPersistedQuery } from '../redux-actions';
77
import { useKorrel8r } from './useKorrel8r';
88
import { useURLState } from './useURLState';
99

@@ -14,6 +14,14 @@ const useTroubleshootingPanel: ExtensionHook<Array<Action>> = () => {
1414
const [perspective] = useActivePerspective();
1515
const dispatch = useDispatch();
1616
const open = React.useCallback(() => {
17+
dispatch(
18+
setPersistedQuery({
19+
query: '',
20+
queryType: QueryType.Neighbour,
21+
depth: 3,
22+
goal: null,
23+
}),
24+
);
1725
dispatch(openTP());
1826
}, [dispatch]);
1927

web/src/korrel8r-client.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { cancellableFetch } from './cancellable-fetch';
22
import { Korrel8rGraphResponse, Korrel8rResponse } from './korrel8r/query.types';
3+
import { Query } from './redux-actions';
34

45
const KORREL8R_ENDPOINT = '/api/proxy/plugin/troubleshooting-panel-console-plugin/korrel8r';
56

@@ -12,14 +13,14 @@ export const listDomains = () => {
1213
);
1314
};
1415

15-
export const getNeighborsGraph = ({ query }: { query?: string }, depth: number) => {
16+
export const getNeighborsGraph = (query: Query) => {
1617
const requestData = {
1718
method: 'POST',
1819
body: JSON.stringify({
1920
start: {
20-
queries: query ? [query.trim()] : [],
21+
queries: query.query ? [query.query.trim()] : [],
2122
},
22-
depth: depth,
23+
depth: query.depth,
2324
}),
2425
};
2526

@@ -29,14 +30,14 @@ export const getNeighborsGraph = ({ query }: { query?: string }, depth: number)
2930
);
3031
};
3132

32-
export const getGoalsGraph = ({ query }: { query?: string }, goal: string) => {
33+
export const getGoalsGraph = (query: Query) => {
3334
const requestData = {
3435
method: 'POST',
3536
body: JSON.stringify({
3637
start: {
37-
queries: query ? [query.trim()] : [],
38+
queries: query.query ? [query.query.trim()] : [],
3839
},
39-
goals: [goal],
40+
goals: [query.goal],
4041
}),
4142
};
4243

web/src/redux-actions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,28 @@ import { action, ActionType as Action } from 'typesafe-actions';
33
export enum ActionType {
44
CloseTroubleshootingPanel = 'closeTroubleshootingPanel',
55
OpenTroubleshootingPanel = 'openTroubleshootingPanel',
6+
SetPersistedQuery = 'setPersistedQuery',
67
}
78

9+
export enum QueryType {
10+
Neighbour,
11+
Goal,
12+
}
13+
export type Query = {
14+
query: string;
15+
queryType: QueryType;
16+
depth: null | number;
17+
goal: null | string;
18+
};
19+
820
export const closeTP = () => action(ActionType.CloseTroubleshootingPanel);
921
export const openTP = () => action(ActionType.OpenTroubleshootingPanel);
22+
export const setPersistedQuery = (query: Query) => action(ActionType.SetPersistedQuery, { query });
1023

1124
const actions = {
1225
closeTP,
1326
openTP,
27+
setPersistedQuery,
1428
};
1529

1630
export type TPAction = Action<typeof actions>;

0 commit comments

Comments
 (0)