Skip to content

Commit c42c45e

Browse files
Robert Austinelasticmachine
andauthored
[Resolver] model location.search in redux (#76140) (#76228)
Read location.search from the redux store instead of a hook so that the entire view has a single (synchronized) source of truth. Also, no longer pass `pushToQueryParams` function to various components. Co-authored-by: Elastic Machine <[email protected]>
1 parent df44cb7 commit c42c45e

18 files changed

+271
-179
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/**
8+
* The legacy `crumbEvent` and `crumbId` parameters.
9+
* @deprecated
10+
*/
11+
export function breadcrumbParameters(
12+
locationSearch: string,
13+
resolverComponentInstanceID: string
14+
): { crumbEvent: string; crumbId: string } {
15+
const urlSearchParams = new URLSearchParams(locationSearch);
16+
const { eventKey, idKey } = parameterNames(resolverComponentInstanceID);
17+
return {
18+
// Use `''` for backwards compatibility with deprecated code.
19+
crumbEvent: urlSearchParams.get(eventKey) ?? '',
20+
crumbId: urlSearchParams.get(idKey) ?? '',
21+
};
22+
}
23+
24+
/**
25+
* Parameter names based on the `resolverComponentInstanceID`.
26+
*/
27+
function parameterNames(
28+
resolverComponentInstanceID: string
29+
): {
30+
idKey: string;
31+
eventKey: string;
32+
} {
33+
const idKey: string = `resolver-${resolverComponentInstanceID}-id`;
34+
const eventKey: string = `resolver-${resolverComponentInstanceID}-event`;
35+
return {
36+
idKey,
37+
eventKey,
38+
};
39+
}

x-pack/plugins/security_solution/public/resolver/store/actions.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,37 @@ interface UserSelectedRelatedEventCategory {
101101
};
102102
}
103103

104+
/**
105+
* Used by `useStateSyncingActions` hook.
106+
* This is dispatched when external sources provide new parameters for Resolver.
107+
* When the component receives a new 'databaseDocumentID' prop, this is fired.
108+
*/
109+
interface AppReceivedNewExternalProperties {
110+
type: 'appReceivedNewExternalProperties';
111+
/**
112+
* Defines the externally provided properties that Resolver acknowledges.
113+
*/
114+
payload: {
115+
/**
116+
* the `_id` of an ES document. This defines the origin of the Resolver graph.
117+
*/
118+
databaseDocumentID?: string;
119+
/**
120+
* An ID that uniquely identifies this Resolver instance from other concurrent Resolvers.
121+
*/
122+
resolverComponentInstanceID: string;
123+
124+
/**
125+
* The `search` part of the URL of this page.
126+
*/
127+
locationSearch: string;
128+
};
129+
}
130+
104131
export type ResolverAction =
105132
| CameraAction
106133
| DataAction
134+
| AppReceivedNewExternalProperties
107135
| UserBroughtProcessIntoView
108136
| UserFocusedOnResolverNode
109137
| UserSelectedResolverNode

x-pack/plugins/security_solution/public/resolver/store/data/action.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,10 @@ interface ServerReturnedRelatedEventData {
6060
readonly payload: ResolverRelatedEvents;
6161
}
6262

63-
/**
64-
* Used by `useStateSyncingActions` hook.
65-
* This is dispatched when external sources provide new parameters for Resolver.
66-
* When the component receives a new 'databaseDocumentID' prop, this is fired.
67-
*/
68-
interface AppReceivedNewExternalProperties {
69-
type: 'appReceivedNewExternalProperties';
70-
/**
71-
* Defines the externally provided properties that Resolver acknowledges.
72-
*/
73-
payload: {
74-
/**
75-
* the `_id` of an ES document. This defines the origin of the Resolver graph.
76-
*/
77-
databaseDocumentID?: string;
78-
resolverComponentInstanceID: string;
79-
};
80-
}
81-
8263
export type DataAction =
8364
| ServerReturnedResolverData
8465
| ServerFailedToReturnResolverData
8566
| ServerFailedToReturnRelatedEventData
8667
| ServerReturnedRelatedEventData
87-
| AppReceivedNewExternalProperties
8868
| AppRequestedResolverData
8969
| AppAbortedResolverDataRequest;

x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
import * as selectors from './selectors';
88
import { DataState } from '../../types';
9+
import { ResolverAction } from '../actions';
910
import { dataReducer } from './reducer';
10-
import { DataAction } from './action';
1111
import { createStore } from 'redux';
1212
import {
1313
mockTreeWithNoAncestorsAnd2Children,
@@ -20,7 +20,7 @@ import { uniquePidForProcess } from '../../models/process_event';
2020
import { EndpointEvent } from '../../../../common/endpoint/types';
2121

2222
describe('data state', () => {
23-
let actions: DataAction[] = [];
23+
let actions: ResolverAction[] = [];
2424

2525
/**
2626
* Get state, given an ordered collection of actions.
@@ -68,7 +68,13 @@ describe('data state', () => {
6868
actions = [
6969
{
7070
type: 'appReceivedNewExternalProperties',
71-
payload: { databaseDocumentID, resolverComponentInstanceID },
71+
payload: {
72+
databaseDocumentID,
73+
resolverComponentInstanceID,
74+
75+
// `locationSearch` doesn't matter for this test
76+
locationSearch: '',
77+
},
7278
},
7379
];
7480
});
@@ -120,7 +126,13 @@ describe('data state', () => {
120126
actions = [
121127
{
122128
type: 'appReceivedNewExternalProperties',
123-
payload: { databaseDocumentID, resolverComponentInstanceID },
129+
payload: {
130+
databaseDocumentID,
131+
resolverComponentInstanceID,
132+
133+
// `locationSearch` doesn't matter for this test
134+
locationSearch: '',
135+
},
124136
},
125137
{
126138
type: 'appRequestedResolverData',
@@ -182,6 +194,8 @@ describe('data state', () => {
182194
payload: {
183195
databaseDocumentID: firstDatabaseDocumentID,
184196
resolverComponentInstanceID: resolverComponentInstanceID1,
197+
// `locationSearch` doesn't matter for this test
198+
locationSearch: '',
185199
},
186200
},
187201
// this happens when the middleware starts the request
@@ -195,6 +209,8 @@ describe('data state', () => {
195209
payload: {
196210
databaseDocumentID: secondDatabaseDocumentID,
197211
resolverComponentInstanceID: resolverComponentInstanceID2,
212+
// `locationSearch` doesn't matter for this test
213+
locationSearch: '',
198214
},
199215
},
200216
];

x-pack/plugins/security_solution/public/resolver/store/reducer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ const uiReducer: Reducer<ResolverUIState, ResolverAction> = (
4848
selectedNode: nodeID,
4949
};
5050
return next;
51+
} else if (action.type === 'appReceivedNewExternalProperties') {
52+
const next: ResolverUIState = {
53+
...state,
54+
locationSearch: action.payload.locationSearch,
55+
resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
56+
};
57+
return next;
5158
} else {
5259
return state;
5360
}

x-pack/plugins/security_solution/public/resolver/store/selectors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,15 @@ export const ariaFlowtoNodeID: (
301301
}
302302
);
303303

304+
/**
305+
* The legacy `crumbEvent` and `crumbId` parameters.
306+
* @deprecated
307+
*/
308+
export const breadcrumbParameters = composeSelectors(
309+
uiStateSelector,
310+
uiSelectors.breadcrumbParameters
311+
);
312+
304313
/**
305314
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
306315
* concern-specific selector. `selector` should return the concern-specific state.
Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,50 @@
1-
/*
2-
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3-
* or more contributor license agreements. Licensed under the Elastic License;
4-
* you may not use this file except in compliance with the Elastic License.
5-
*/
6-
7-
import { createSelector } from 'reselect';
8-
import { ResolverUIState } from '../../types';
9-
10-
/**
11-
* id of the "current" tree node (fake-focused)
12-
*/
13-
export const ariaActiveDescendant = createSelector(
14-
(uiState: ResolverUIState) => uiState,
15-
/* eslint-disable no-shadow */
16-
({ ariaActiveDescendant }) => {
17-
return ariaActiveDescendant;
18-
}
19-
);
20-
21-
/**
22-
* id of the currently "selected" tree node
23-
*/
24-
export const selectedNode = createSelector(
25-
(uiState: ResolverUIState) => uiState,
26-
/* eslint-disable no-shadow */
27-
({ selectedNode }: ResolverUIState) => {
28-
return selectedNode;
29-
}
30-
);
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { createSelector } from 'reselect';
8+
import { ResolverUIState } from '../../types';
9+
import * as locationSearchModel from '../../models/location_search';
10+
11+
/**
12+
* id of the "current" tree node (fake-focused)
13+
*/
14+
export const ariaActiveDescendant = createSelector(
15+
(uiState: ResolverUIState) => uiState,
16+
/* eslint-disable no-shadow */
17+
({ ariaActiveDescendant }) => {
18+
return ariaActiveDescendant;
19+
}
20+
);
21+
22+
/**
23+
* id of the currently "selected" tree node
24+
*/
25+
export const selectedNode = createSelector(
26+
(uiState: ResolverUIState) => uiState,
27+
/* eslint-disable no-shadow */
28+
({ selectedNode }: ResolverUIState) => {
29+
return selectedNode;
30+
}
31+
);
32+
33+
/**
34+
* The legacy `crumbEvent` and `crumbId` parameters.
35+
* @deprecated
36+
*/
37+
export const breadcrumbParameters = createSelector(
38+
(state: ResolverUIState) => state.locationSearch,
39+
(state: ResolverUIState) => state.resolverComponentInstanceID,
40+
(locationSearch, resolverComponentInstanceID) => {
41+
if (locationSearch === undefined || resolverComponentInstanceID === undefined) {
42+
// Equivalent to `null`
43+
return {
44+
crumbId: '',
45+
crumbEvent: '',
46+
};
47+
}
48+
return locationSearchModel.breadcrumbParameters(locationSearch, resolverComponentInstanceID);
49+
}
50+
);

x-pack/plugins/security_solution/public/resolver/types.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ export interface ResolverUIState {
5050
* `nodeID` of the selected node
5151
*/
5252
readonly selectedNode: string | null;
53+
54+
/**
55+
* The `search` part of the URL.
56+
*/
57+
readonly locationSearch?: string;
58+
59+
/**
60+
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
61+
*/
62+
readonly resolverComponentInstanceID?: string;
5363
}
5464

5565
/**
@@ -198,7 +208,12 @@ export interface DataState {
198208
* The id used for the pending request, if there is one.
199209
*/
200210
readonly pendingRequestDatabaseDocumentID?: string;
201-
readonly resolverComponentInstanceID: string | undefined;
211+
212+
/**
213+
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
214+
* Used to prevent collisions in things like query parameters.
215+
*/
216+
readonly resolverComponentInstanceID?: string;
202217

203218
/**
204219
* The parameters and response from the last successful request.
@@ -510,8 +525,9 @@ export interface ResolverProps {
510525
* Used as the origin of the Resolver graph.
511526
*/
512527
databaseDocumentID?: string;
528+
513529
/**
514-
* A string literal describing where in the application resolver is located.
530+
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
515531
* Used to prevent collisions in things like query parameters.
516532
*/
517533
resolverComponentInstanceID: string;

x-pack/plugins/security_solution/public/resolver/view/panels/event_counts_for_process.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { StyledBreadcrumbs } from './panel_content_utilities';
1212

1313
import * as event from '../../../../common/endpoint/models/event';
1414
import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
15-
import { CrumbInfo } from '../../types';
15+
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
1616

1717
/**
1818
* This view gives counts for all the related events of a process grouped by related event type.
@@ -27,11 +27,9 @@ import { CrumbInfo } from '../../types';
2727
*/
2828
export const EventCountsForProcess = memo(function EventCountsForProcess({
2929
processEvent,
30-
pushToQueryParams,
3130
relatedStats,
3231
}: {
3332
processEvent: ResolverEvent;
34-
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
3533
relatedStats: ResolverNodeStats;
3634
}) {
3735
interface EventCountsTableView {
@@ -62,6 +60,7 @@ export const EventCountsForProcess = memo(function EventCountsForProcess({
6260
defaultMessage: 'Events',
6361
}
6462
);
63+
const pushToQueryParams = useReplaceBreadcrumbParameters();
6564
const crumbs = useMemo(() => {
6665
return [
6766
{

0 commit comments

Comments
 (0)