@@ -27,46 +27,60 @@ import { TFunction, useTranslation } from 'react-i18next';
2727import { CubesIcon , ExclamationCircleIcon } from '@patternfly/react-icons' ;
2828import { useURLState } from '../hooks/useURLState' ;
2929import { 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
3134type Result = {
3235 graph ?: Korrel8rGraphResponse ;
3336 message ?: string ;
3437 title ?: string ;
3538} ;
36- enum QueryType {
37- Neighbour ,
38- Goal ,
39- }
4039
4140export 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() {
204259interface TopologyProps {
205260 result ?: Result ;
206261 t : TFunction ;
207- setQuery : ( query : string ) => void ;
262+ setQuery : ( query : Query ) => void ;
208263}
209264
210265const 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+ } ;
0 commit comments