[Resolver] model location.search in redux (#76140)

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.
This commit is contained in:
Robert Austin 2020-08-28 08:53:04 -04:00 committed by GitHub
parent 712c4bdde7
commit 3fc3f58c62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 271 additions and 179 deletions

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/**
* The legacy `crumbEvent` and `crumbId` parameters.
* @deprecated
*/
export function breadcrumbParameters(
locationSearch: string,
resolverComponentInstanceID: string
): { crumbEvent: string; crumbId: string } {
const urlSearchParams = new URLSearchParams(locationSearch);
const { eventKey, idKey } = parameterNames(resolverComponentInstanceID);
return {
// Use `''` for backwards compatibility with deprecated code.
crumbEvent: urlSearchParams.get(eventKey) ?? '',
crumbId: urlSearchParams.get(idKey) ?? '',
};
}
/**
* Parameter names based on the `resolverComponentInstanceID`.
*/
function parameterNames(
resolverComponentInstanceID: string
): {
idKey: string;
eventKey: string;
} {
const idKey: string = `resolver-${resolverComponentInstanceID}-id`;
const eventKey: string = `resolver-${resolverComponentInstanceID}-event`;
return {
idKey,
eventKey,
};
}

View file

@ -101,9 +101,37 @@ interface UserSelectedRelatedEventCategory {
};
}
/**
* Used by `useStateSyncingActions` hook.
* This is dispatched when external sources provide new parameters for Resolver.
* When the component receives a new 'databaseDocumentID' prop, this is fired.
*/
interface AppReceivedNewExternalProperties {
type: 'appReceivedNewExternalProperties';
/**
* Defines the externally provided properties that Resolver acknowledges.
*/
payload: {
/**
* the `_id` of an ES document. This defines the origin of the Resolver graph.
*/
databaseDocumentID?: string;
/**
* An ID that uniquely identifies this Resolver instance from other concurrent Resolvers.
*/
resolverComponentInstanceID: string;
/**
* The `search` part of the URL of this page.
*/
locationSearch: string;
};
}
export type ResolverAction =
| CameraAction
| DataAction
| AppReceivedNewExternalProperties
| UserBroughtProcessIntoView
| UserFocusedOnResolverNode
| UserSelectedResolverNode

View file

@ -60,30 +60,10 @@ interface ServerReturnedRelatedEventData {
readonly payload: ResolverRelatedEvents;
}
/**
* Used by `useStateSyncingActions` hook.
* This is dispatched when external sources provide new parameters for Resolver.
* When the component receives a new 'databaseDocumentID' prop, this is fired.
*/
interface AppReceivedNewExternalProperties {
type: 'appReceivedNewExternalProperties';
/**
* Defines the externally provided properties that Resolver acknowledges.
*/
payload: {
/**
* the `_id` of an ES document. This defines the origin of the Resolver graph.
*/
databaseDocumentID?: string;
resolverComponentInstanceID: string;
};
}
export type DataAction =
| ServerReturnedResolverData
| ServerFailedToReturnResolverData
| ServerFailedToReturnRelatedEventData
| ServerReturnedRelatedEventData
| AppReceivedNewExternalProperties
| AppRequestedResolverData
| AppAbortedResolverDataRequest;

View file

@ -6,8 +6,8 @@
import * as selectors from './selectors';
import { DataState } from '../../types';
import { ResolverAction } from '../actions';
import { dataReducer } from './reducer';
import { DataAction } from './action';
import { createStore } from 'redux';
import {
mockTreeWithNoAncestorsAnd2Children,
@ -20,7 +20,7 @@ import { uniquePidForProcess } from '../../models/process_event';
import { EndpointEvent } from '../../../../common/endpoint/types';
describe('data state', () => {
let actions: DataAction[] = [];
let actions: ResolverAction[] = [];
/**
* Get state, given an ordered collection of actions.
@ -68,7 +68,13 @@ describe('data state', () => {
actions = [
{
type: 'appReceivedNewExternalProperties',
payload: { databaseDocumentID, resolverComponentInstanceID },
payload: {
databaseDocumentID,
resolverComponentInstanceID,
// `locationSearch` doesn't matter for this test
locationSearch: '',
},
},
];
});
@ -120,7 +126,13 @@ describe('data state', () => {
actions = [
{
type: 'appReceivedNewExternalProperties',
payload: { databaseDocumentID, resolverComponentInstanceID },
payload: {
databaseDocumentID,
resolverComponentInstanceID,
// `locationSearch` doesn't matter for this test
locationSearch: '',
},
},
{
type: 'appRequestedResolverData',
@ -182,6 +194,8 @@ describe('data state', () => {
payload: {
databaseDocumentID: firstDatabaseDocumentID,
resolverComponentInstanceID: resolverComponentInstanceID1,
// `locationSearch` doesn't matter for this test
locationSearch: '',
},
},
// this happens when the middleware starts the request
@ -195,6 +209,8 @@ describe('data state', () => {
payload: {
databaseDocumentID: secondDatabaseDocumentID,
resolverComponentInstanceID: resolverComponentInstanceID2,
// `locationSearch` doesn't matter for this test
locationSearch: '',
},
},
];

View file

@ -48,6 +48,13 @@ const uiReducer: Reducer<ResolverUIState, ResolverAction> = (
selectedNode: nodeID,
};
return next;
} else if (action.type === 'appReceivedNewExternalProperties') {
const next: ResolverUIState = {
...state,
locationSearch: action.payload.locationSearch,
resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
};
return next;
} else {
return state;
}

View file

@ -301,6 +301,15 @@ export const ariaFlowtoNodeID: (
}
);
/**
* The legacy `crumbEvent` and `crumbId` parameters.
* @deprecated
*/
export const breadcrumbParameters = composeSelectors(
uiStateSelector,
uiSelectors.breadcrumbParameters
);
/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.

View file

@ -1,30 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createSelector } from 'reselect';
import { ResolverUIState } from '../../types';
/**
* id of the "current" tree node (fake-focused)
*/
export const ariaActiveDescendant = createSelector(
(uiState: ResolverUIState) => uiState,
/* eslint-disable no-shadow */
({ ariaActiveDescendant }) => {
return ariaActiveDescendant;
}
);
/**
* id of the currently "selected" tree node
*/
export const selectedNode = createSelector(
(uiState: ResolverUIState) => uiState,
/* eslint-disable no-shadow */
({ selectedNode }: ResolverUIState) => {
return selectedNode;
}
);
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createSelector } from 'reselect';
import { ResolverUIState } from '../../types';
import * as locationSearchModel from '../../models/location_search';
/**
* id of the "current" tree node (fake-focused)
*/
export const ariaActiveDescendant = createSelector(
(uiState: ResolverUIState) => uiState,
/* eslint-disable no-shadow */
({ ariaActiveDescendant }) => {
return ariaActiveDescendant;
}
);
/**
* id of the currently "selected" tree node
*/
export const selectedNode = createSelector(
(uiState: ResolverUIState) => uiState,
/* eslint-disable no-shadow */
({ selectedNode }: ResolverUIState) => {
return selectedNode;
}
);
/**
* The legacy `crumbEvent` and `crumbId` parameters.
* @deprecated
*/
export const breadcrumbParameters = createSelector(
(state: ResolverUIState) => state.locationSearch,
(state: ResolverUIState) => state.resolverComponentInstanceID,
(locationSearch, resolverComponentInstanceID) => {
if (locationSearch === undefined || resolverComponentInstanceID === undefined) {
// Equivalent to `null`
return {
crumbId: '',
crumbEvent: '',
};
}
return locationSearchModel.breadcrumbParameters(locationSearch, resolverComponentInstanceID);
}
);

View file

@ -50,6 +50,16 @@ export interface ResolverUIState {
* `nodeID` of the selected node
*/
readonly selectedNode: string | null;
/**
* The `search` part of the URL.
*/
readonly locationSearch?: string;
/**
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
*/
readonly resolverComponentInstanceID?: string;
}
/**
@ -198,7 +208,12 @@ export interface DataState {
* The id used for the pending request, if there is one.
*/
readonly pendingRequestDatabaseDocumentID?: string;
readonly resolverComponentInstanceID: string | undefined;
/**
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
* Used to prevent collisions in things like query parameters.
*/
readonly resolverComponentInstanceID?: string;
/**
* The parameters and response from the last successful request.
@ -510,8 +525,9 @@ export interface ResolverProps {
* Used as the origin of the Resolver graph.
*/
databaseDocumentID?: string;
/**
* A string literal describing where in the application resolver is located.
* An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
* Used to prevent collisions in things like query parameters.
*/
resolverComponentInstanceID: string;

View file

@ -12,7 +12,7 @@ import { StyledBreadcrumbs } from './panel_content_utilities';
import * as event from '../../../../common/endpoint/models/event';
import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
import { CrumbInfo } from '../../types';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
/**
* 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';
*/
export const EventCountsForProcess = memo(function EventCountsForProcess({
processEvent,
pushToQueryParams,
relatedStats,
}: {
processEvent: ResolverEvent;
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
relatedStats: ResolverNodeStats;
}) {
interface EventCountsTableView {
@ -62,6 +60,7 @@ export const EventCountsForProcess = memo(function EventCountsForProcess({
defaultMessage: 'Events',
}
);
const pushToQueryParams = useReplaceBreadcrumbParameters();
const crumbs = useMemo(() => {
return [
{

View file

@ -17,7 +17,6 @@ import { EventCountsForProcess } from './event_counts_for_process';
import { ProcessDetails } from './process_details';
import { ProcessListWithCounts } from './process_list_with_counts';
import { RelatedEventDetail } from './related_event_detail';
import { useResolverQueryParams } from '../use_resolver_query_params';
/**
* The team decided to use this table to determine which breadcrumbs/view to display:
@ -39,7 +38,7 @@ const PanelContent = memo(function PanelContent() {
const { timestamp } = useContext(SideEffectContext);
const { pushToQueryParams, queryParams } = useResolverQueryParams();
const queryParams = useSelector(selectors.breadcrumbParameters);
const graphableProcesses = useSelector(selectors.graphableProcesses);
const graphableProcessEntityIds = useMemo(() => {
@ -164,16 +163,13 @@ const PanelContent = memo(function PanelContent() {
const panelInstance = useMemo(() => {
if (panelToShow === 'processDetails') {
return (
<ProcessDetails processEvent={uiSelectedEvent!} pushToQueryParams={pushToQueryParams} />
);
return <ProcessDetails processEvent={uiSelectedEvent!} />;
}
if (panelToShow === 'eventCountsForProcess') {
return (
<EventCountsForProcess
processEvent={uiSelectedEvent!}
pushToQueryParams={pushToQueryParams}
relatedStats={relatedStatsForIdFromParams!}
/>
);
@ -183,7 +179,6 @@ const PanelContent = memo(function PanelContent() {
return (
<ProcessEventList
processEvent={uiSelectedEvent!}
pushToQueryParams={pushToQueryParams}
relatedStats={relatedStatsForIdFromParams!}
eventType={crumbEvent}
/>
@ -198,21 +193,13 @@ const PanelContent = memo(function PanelContent() {
<RelatedEventDetail
relatedEventId={crumbId}
parentEvent={uiSelectedEvent!}
pushToQueryParams={pushToQueryParams}
countForParent={parentCount}
/>
);
}
// The default 'Event List' / 'List of all processes' view
return <ProcessListWithCounts pushToQueryParams={pushToQueryParams} />;
}, [
uiSelectedEvent,
crumbEvent,
crumbId,
pushToQueryParams,
relatedStatsForIdFromParams,
panelToShow,
]);
return <ProcessListWithCounts />;
}, [uiSelectedEvent, crumbEvent, crumbId, relatedStatsForIdFromParams, panelToShow]);
return <>{panelInstance}</>;
});

View file

@ -1,62 +1,61 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import { StyledBreadcrumbs } from './panel_content_utilities';
import { CrumbInfo } from '../../types';
/**
* Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state.
*
* @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
* @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
*/
export const PanelContentError = memo(function ({
translatedErrorMessage,
pushToQueryParams,
}: {
translatedErrorMessage: string;
pushToQueryParams: (arg0: CrumbInfo) => unknown;
}) {
const crumbs = useMemo(() => {
return [
{
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
defaultMessage: 'Events',
}),
onClick: () => {
pushToQueryParams({ crumbId: '', crumbEvent: '' });
},
},
{
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
defaultMessage: 'Error',
}),
onClick: () => {},
},
];
}, [pushToQueryParams]);
return (
<>
<StyledBreadcrumbs breadcrumbs={crumbs} />
<EuiSpacer size="l" />
<EuiText textAlign="center">{translatedErrorMessage}</EuiText>
<EuiSpacer size="l" />
<EuiButtonEmpty
onClick={() => {
pushToQueryParams({ crumbId: '', crumbEvent: '' });
}}
>
{i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
defaultMessage: 'Click this link to return to the list of all processes.',
})}
</EuiButtonEmpty>
</>
);
});
PanelContentError.displayName = 'TableServiceError';
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import { StyledBreadcrumbs } from './panel_content_utilities';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
/**
* Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state.
*
* @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
* @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
*/
export const PanelContentError = memo(function ({
translatedErrorMessage,
}: {
translatedErrorMessage: string;
}) {
const pushToQueryParams = useReplaceBreadcrumbParameters();
const crumbs = useMemo(() => {
return [
{
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
defaultMessage: 'Events',
}),
onClick: () => {
pushToQueryParams({ crumbId: '', crumbEvent: '' });
},
},
{
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
defaultMessage: 'Error',
}),
onClick: () => {},
},
];
}, [pushToQueryParams]);
return (
<>
<StyledBreadcrumbs breadcrumbs={crumbs} />
<EuiSpacer size="l" />
<EuiText textAlign="center">{translatedErrorMessage}</EuiText>
<EuiSpacer size="l" />
<EuiButtonEmpty
onClick={() => {
pushToQueryParams({ crumbId: '', crumbEvent: '' });
}}
>
{i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
defaultMessage: 'Click this link to return to the list of all processes.',
})}
</EuiButtonEmpty>
</>
);
});
PanelContentError.displayName = 'TableServiceError';

View file

@ -31,7 +31,8 @@ import {
import { CubeForProcess } from './cube_for_process';
import { ResolverEvent } from '../../../../common/endpoint/types';
import { useResolverTheme } from '../assets';
import { CrumbInfo, ResolverState } from '../../types';
import { ResolverState } from '../../types';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
const StyledDescriptionList = styled(EuiDescriptionList)`
&.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
@ -49,10 +50,8 @@ const StyledTitle = styled('h4')`
*/
export const ProcessDetails = memo(function ProcessDetails({
processEvent,
pushToQueryParams,
}: {
processEvent: ResolverEvent;
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
}) {
const processName = event.eventName(processEvent);
const entityId = event.entityId(processEvent);
@ -127,6 +126,8 @@ export const ProcessDetails = memo(function ProcessDetails({
return processDescriptionListData;
}, [processEvent]);
const pushToQueryParams = useReplaceBreadcrumbParameters();
const crumbs = useMemo(() => {
return [
{

View file

@ -16,7 +16,7 @@ import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/ty
import * as selectors from '../../store/selectors';
import { useResolverDispatch } from '../use_resolver_dispatch';
import { RelatedEventLimitWarning } from '../limit_warnings';
import { CrumbInfo } from '../../types';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
/**
* This view presents a list of related events of a given type for a given process.
@ -129,10 +129,8 @@ export const ProcessEventList = memo(function ProcessEventList({
processEvent,
eventType,
relatedStats,
pushToQueryParams,
}: {
processEvent: ResolverEvent;
pushToQueryParams: (arg0: CrumbInfo) => unknown;
eventType: string;
relatedStats: ResolverNodeStats;
}) {
@ -169,6 +167,8 @@ export const ProcessEventList = memo(function ProcessEventList({
}
}, [relatedsReady, dispatch, processEntityId]);
const pushToQueryParams = useReplaceBreadcrumbParameters();
const waitCrumbs = useMemo(() => {
return [
{

View file

@ -3,6 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React, { memo, useContext, useCallback, useMemo } from 'react';
import {
EuiBasicTableColumn,
@ -22,7 +25,7 @@ import { SideEffectContext } from '../side_effect_context';
import { CubeForProcess } from './cube_for_process';
import { SafeResolverEvent } from '../../../../common/endpoint/types';
import { LimitWarning } from '../limit_warnings';
import { CrumbInfo } from '../../types';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
const StyledLimitWarning = styled(LimitWarning)`
flex-flow: row wrap;
@ -46,14 +49,8 @@ const StyledLimitWarning = styled(LimitWarning)`
/**
* The "default" view for the panel: A list of all the processes currently in the graph.
*
* @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
*/
export const ProcessListWithCounts = memo(function ProcessListWithCounts({
pushToQueryParams,
}: {
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
}) {
export const ProcessListWithCounts = memo(() => {
interface ProcessTableView {
name?: string;
timestamp?: Date;
@ -63,6 +60,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
const dispatch = useResolverDispatch();
const { timestamp } = useContext(SideEffectContext);
const isProcessTerminated = useSelector(selectors.isProcessTerminated);
const pushToQueryParams = useReplaceBreadcrumbParameters();
const handleBringIntoViewClick = useCallback(
(processTableViewItem) => {
dispatch({

View file

@ -16,7 +16,8 @@ import { ResolverEvent } from '../../../../common/endpoint/types';
import * as selectors from '../../store/selectors';
import { useResolverDispatch } from '../use_resolver_dispatch';
import { PanelContentError } from './panel_content_error';
import { CrumbInfo, ResolverState } from '../../types';
import { ResolverState } from '../../types';
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
// Adding some styles to prevent horizontal scrollbars, per request from UX review
const StyledDescriptionList = memo(styled(EuiDescriptionList)`
@ -76,15 +77,13 @@ function entriesForDisplay(entries: Array<{ title: string; description: string }
* This view presents a detailed view of all the available data for a related event, split and titled by the "section"
* it appears in the underlying ResolverEvent
*/
export const RelatedEventDetail = memo(function RelatedEventDetail({
export const RelatedEventDetail = memo(function ({
relatedEventId,
parentEvent,
pushToQueryParams,
countForParent,
}: {
relatedEventId: string;
parentEvent: ResolverEvent;
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
countForParent: number | undefined;
}) {
const processName = (parentEvent && event.eventName(parentEvent)) || '*';
@ -130,6 +129,8 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
selectors.relatedEventDisplayInfoByEntityAndSelfId(state)(processEntityId, relatedEventId)
);
const pushToQueryParams = useReplaceBreadcrumbParameters();
const waitCrumbs = useMemo(() => {
return [
{
@ -247,9 +248,7 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
defaultMessage: 'Related event not found.',
}
);
return (
<PanelContentError translatedErrorMessage={errString} pushToQueryParams={pushToQueryParams} />
);
return <PanelContentError translatedErrorMessage={errString} />;
}
return (

View file

@ -18,7 +18,7 @@ import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types
import { useResolverDispatch } from './use_resolver_dispatch';
import * as eventModel from '../../../common/endpoint/models/event';
import * as selectors from '../store/selectors';
import { useResolverQueryParams } from './use_resolver_query_params';
import { useReplaceBreadcrumbParameters } from './use_replace_breadcrumb_parameters';
interface StyledActionsContainer {
readonly color: string;
@ -242,7 +242,7 @@ const UnstyledProcessEventDot = React.memo(
});
}, [dispatch, nodeID]);
const { pushToQueryParams } = useResolverQueryParams();
const pushToQueryParams = useReplaceBreadcrumbParameters();
const handleClick = useCallback(() => {
if (animationTarget.current?.beginElement) {

View file

@ -4,12 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useQueryStringKeys } from './use_query_string_keys';
import { CrumbInfo } from '../types';
export function useResolverQueryParams() {
/**
* @deprecated
* Update the browser's `search` with data from `queryStringState`. The URL search parameter names
* will include Resolver's `resolverComponentInstanceID`.
*/
export function useReplaceBreadcrumbParameters(): (queryStringState: CrumbInfo) => void {
/**
* This updates the breadcrumb nav and the panel view. It's supplied to each
* panel content view to allow them to dispatch transitions to each other.
@ -17,7 +22,7 @@ export function useResolverQueryParams() {
const history = useHistory();
const urlSearch = useLocation().search;
const { idKey, eventKey } = useQueryStringKeys();
const pushToQueryParams = useCallback(
return useCallback(
(queryStringState: CrumbInfo) => {
const urlSearchParams = new URLSearchParams(urlSearch);
@ -39,17 +44,4 @@ export function useResolverQueryParams() {
},
[history, urlSearch, idKey, eventKey]
);
const queryParams: CrumbInfo = useMemo(() => {
const urlSearchParams = new URLSearchParams(urlSearch);
return {
// Use `''` for backwards compatibility with deprecated code.
crumbEvent: urlSearchParams.get(eventKey) ?? '',
crumbId: urlSearchParams.get(idKey) ?? '',
};
}, [urlSearch, idKey, eventKey]);
return {
pushToQueryParams,
queryParams,
};
}

View file

@ -5,6 +5,7 @@
*/
import { useLayoutEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useResolverDispatch } from './use_resolver_dispatch';
/**
@ -22,10 +23,11 @@ export function useStateSyncingActions({
resolverComponentInstanceID: string;
}) {
const dispatch = useResolverDispatch();
const locationSearch = useLocation().search;
useLayoutEffect(() => {
dispatch({
type: 'appReceivedNewExternalProperties',
payload: { databaseDocumentID, resolverComponentInstanceID },
payload: { databaseDocumentID, resolverComponentInstanceID, locationSearch },
});
}, [dispatch, databaseDocumentID, resolverComponentInstanceID]);
}, [dispatch, databaseDocumentID, resolverComponentInstanceID, locationSearch]);
}