[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:
parent
712c4bdde7
commit
3fc3f58c62
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 =
|
export type ResolverAction =
|
||||||
| CameraAction
|
| CameraAction
|
||||||
| DataAction
|
| DataAction
|
||||||
|
| AppReceivedNewExternalProperties
|
||||||
| UserBroughtProcessIntoView
|
| UserBroughtProcessIntoView
|
||||||
| UserFocusedOnResolverNode
|
| UserFocusedOnResolverNode
|
||||||
| UserSelectedResolverNode
|
| UserSelectedResolverNode
|
||||||
|
|
|
@ -60,30 +60,10 @@ interface ServerReturnedRelatedEventData {
|
||||||
readonly payload: ResolverRelatedEvents;
|
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 =
|
export type DataAction =
|
||||||
| ServerReturnedResolverData
|
| ServerReturnedResolverData
|
||||||
| ServerFailedToReturnResolverData
|
| ServerFailedToReturnResolverData
|
||||||
| ServerFailedToReturnRelatedEventData
|
| ServerFailedToReturnRelatedEventData
|
||||||
| ServerReturnedRelatedEventData
|
| ServerReturnedRelatedEventData
|
||||||
| AppReceivedNewExternalProperties
|
|
||||||
| AppRequestedResolverData
|
| AppRequestedResolverData
|
||||||
| AppAbortedResolverDataRequest;
|
| AppAbortedResolverDataRequest;
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
import * as selectors from './selectors';
|
import * as selectors from './selectors';
|
||||||
import { DataState } from '../../types';
|
import { DataState } from '../../types';
|
||||||
|
import { ResolverAction } from '../actions';
|
||||||
import { dataReducer } from './reducer';
|
import { dataReducer } from './reducer';
|
||||||
import { DataAction } from './action';
|
|
||||||
import { createStore } from 'redux';
|
import { createStore } from 'redux';
|
||||||
import {
|
import {
|
||||||
mockTreeWithNoAncestorsAnd2Children,
|
mockTreeWithNoAncestorsAnd2Children,
|
||||||
|
@ -20,7 +20,7 @@ import { uniquePidForProcess } from '../../models/process_event';
|
||||||
import { EndpointEvent } from '../../../../common/endpoint/types';
|
import { EndpointEvent } from '../../../../common/endpoint/types';
|
||||||
|
|
||||||
describe('data state', () => {
|
describe('data state', () => {
|
||||||
let actions: DataAction[] = [];
|
let actions: ResolverAction[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get state, given an ordered collection of actions.
|
* Get state, given an ordered collection of actions.
|
||||||
|
@ -68,7 +68,13 @@ describe('data state', () => {
|
||||||
actions = [
|
actions = [
|
||||||
{
|
{
|
||||||
type: 'appReceivedNewExternalProperties',
|
type: 'appReceivedNewExternalProperties',
|
||||||
payload: { databaseDocumentID, resolverComponentInstanceID },
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
|
||||||
|
// `locationSearch` doesn't matter for this test
|
||||||
|
locationSearch: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@ -120,7 +126,13 @@ describe('data state', () => {
|
||||||
actions = [
|
actions = [
|
||||||
{
|
{
|
||||||
type: 'appReceivedNewExternalProperties',
|
type: 'appReceivedNewExternalProperties',
|
||||||
payload: { databaseDocumentID, resolverComponentInstanceID },
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
|
||||||
|
// `locationSearch` doesn't matter for this test
|
||||||
|
locationSearch: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'appRequestedResolverData',
|
type: 'appRequestedResolverData',
|
||||||
|
@ -182,6 +194,8 @@ describe('data state', () => {
|
||||||
payload: {
|
payload: {
|
||||||
databaseDocumentID: firstDatabaseDocumentID,
|
databaseDocumentID: firstDatabaseDocumentID,
|
||||||
resolverComponentInstanceID: resolverComponentInstanceID1,
|
resolverComponentInstanceID: resolverComponentInstanceID1,
|
||||||
|
// `locationSearch` doesn't matter for this test
|
||||||
|
locationSearch: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// this happens when the middleware starts the request
|
// this happens when the middleware starts the request
|
||||||
|
@ -195,6 +209,8 @@ describe('data state', () => {
|
||||||
payload: {
|
payload: {
|
||||||
databaseDocumentID: secondDatabaseDocumentID,
|
databaseDocumentID: secondDatabaseDocumentID,
|
||||||
resolverComponentInstanceID: resolverComponentInstanceID2,
|
resolverComponentInstanceID: resolverComponentInstanceID2,
|
||||||
|
// `locationSearch` doesn't matter for this test
|
||||||
|
locationSearch: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -48,6 +48,13 @@ const uiReducer: Reducer<ResolverUIState, ResolverAction> = (
|
||||||
selectedNode: nodeID,
|
selectedNode: nodeID,
|
||||||
};
|
};
|
||||||
return next;
|
return next;
|
||||||
|
} else if (action.type === 'appReceivedNewExternalProperties') {
|
||||||
|
const next: ResolverUIState = {
|
||||||
|
...state,
|
||||||
|
locationSearch: action.payload.locationSearch,
|
||||||
|
resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
|
||||||
|
};
|
||||||
|
return next;
|
||||||
} else {
|
} else {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* 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.
|
* concern-specific selector. `selector` should return the concern-specific state.
|
||||||
|
|
|
@ -1,30 +1,50 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { ResolverUIState } from '../../types';
|
import { ResolverUIState } from '../../types';
|
||||||
|
import * as locationSearchModel from '../../models/location_search';
|
||||||
/**
|
|
||||||
* id of the "current" tree node (fake-focused)
|
/**
|
||||||
*/
|
* id of the "current" tree node (fake-focused)
|
||||||
export const ariaActiveDescendant = createSelector(
|
*/
|
||||||
(uiState: ResolverUIState) => uiState,
|
export const ariaActiveDescendant = createSelector(
|
||||||
/* eslint-disable no-shadow */
|
(uiState: ResolverUIState) => uiState,
|
||||||
({ ariaActiveDescendant }) => {
|
/* eslint-disable no-shadow */
|
||||||
return ariaActiveDescendant;
|
({ ariaActiveDescendant }) => {
|
||||||
}
|
return ariaActiveDescendant;
|
||||||
);
|
}
|
||||||
|
);
|
||||||
/**
|
|
||||||
* id of the currently "selected" tree node
|
/**
|
||||||
*/
|
* id of the currently "selected" tree node
|
||||||
export const selectedNode = createSelector(
|
*/
|
||||||
(uiState: ResolverUIState) => uiState,
|
export const selectedNode = createSelector(
|
||||||
/* eslint-disable no-shadow */
|
(uiState: ResolverUIState) => uiState,
|
||||||
({ selectedNode }: ResolverUIState) => {
|
/* eslint-disable no-shadow */
|
||||||
return selectedNode;
|
({ 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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -50,6 +50,16 @@ export interface ResolverUIState {
|
||||||
* `nodeID` of the selected node
|
* `nodeID` of the selected node
|
||||||
*/
|
*/
|
||||||
readonly selectedNode: string | null;
|
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.
|
* The id used for the pending request, if there is one.
|
||||||
*/
|
*/
|
||||||
readonly pendingRequestDatabaseDocumentID?: string;
|
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.
|
* The parameters and response from the last successful request.
|
||||||
|
@ -510,8 +525,9 @@ export interface ResolverProps {
|
||||||
* Used as the origin of the Resolver graph.
|
* Used as the origin of the Resolver graph.
|
||||||
*/
|
*/
|
||||||
databaseDocumentID?: string;
|
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.
|
* Used to prevent collisions in things like query parameters.
|
||||||
*/
|
*/
|
||||||
resolverComponentInstanceID: string;
|
resolverComponentInstanceID: string;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { StyledBreadcrumbs } from './panel_content_utilities';
|
||||||
|
|
||||||
import * as event from '../../../../common/endpoint/models/event';
|
import * as event from '../../../../common/endpoint/models/event';
|
||||||
import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
|
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.
|
* 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({
|
export const EventCountsForProcess = memo(function EventCountsForProcess({
|
||||||
processEvent,
|
processEvent,
|
||||||
pushToQueryParams,
|
|
||||||
relatedStats,
|
relatedStats,
|
||||||
}: {
|
}: {
|
||||||
processEvent: ResolverEvent;
|
processEvent: ResolverEvent;
|
||||||
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
|
|
||||||
relatedStats: ResolverNodeStats;
|
relatedStats: ResolverNodeStats;
|
||||||
}) {
|
}) {
|
||||||
interface EventCountsTableView {
|
interface EventCountsTableView {
|
||||||
|
@ -62,6 +60,7 @@ export const EventCountsForProcess = memo(function EventCountsForProcess({
|
||||||
defaultMessage: 'Events',
|
defaultMessage: 'Events',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
const crumbs = useMemo(() => {
|
const crumbs = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { EventCountsForProcess } from './event_counts_for_process';
|
||||||
import { ProcessDetails } from './process_details';
|
import { ProcessDetails } from './process_details';
|
||||||
import { ProcessListWithCounts } from './process_list_with_counts';
|
import { ProcessListWithCounts } from './process_list_with_counts';
|
||||||
import { RelatedEventDetail } from './related_event_detail';
|
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:
|
* 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 { timestamp } = useContext(SideEffectContext);
|
||||||
|
|
||||||
const { pushToQueryParams, queryParams } = useResolverQueryParams();
|
const queryParams = useSelector(selectors.breadcrumbParameters);
|
||||||
|
|
||||||
const graphableProcesses = useSelector(selectors.graphableProcesses);
|
const graphableProcesses = useSelector(selectors.graphableProcesses);
|
||||||
const graphableProcessEntityIds = useMemo(() => {
|
const graphableProcessEntityIds = useMemo(() => {
|
||||||
|
@ -164,16 +163,13 @@ const PanelContent = memo(function PanelContent() {
|
||||||
|
|
||||||
const panelInstance = useMemo(() => {
|
const panelInstance = useMemo(() => {
|
||||||
if (panelToShow === 'processDetails') {
|
if (panelToShow === 'processDetails') {
|
||||||
return (
|
return <ProcessDetails processEvent={uiSelectedEvent!} />;
|
||||||
<ProcessDetails processEvent={uiSelectedEvent!} pushToQueryParams={pushToQueryParams} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panelToShow === 'eventCountsForProcess') {
|
if (panelToShow === 'eventCountsForProcess') {
|
||||||
return (
|
return (
|
||||||
<EventCountsForProcess
|
<EventCountsForProcess
|
||||||
processEvent={uiSelectedEvent!}
|
processEvent={uiSelectedEvent!}
|
||||||
pushToQueryParams={pushToQueryParams}
|
|
||||||
relatedStats={relatedStatsForIdFromParams!}
|
relatedStats={relatedStatsForIdFromParams!}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -183,7 +179,6 @@ const PanelContent = memo(function PanelContent() {
|
||||||
return (
|
return (
|
||||||
<ProcessEventList
|
<ProcessEventList
|
||||||
processEvent={uiSelectedEvent!}
|
processEvent={uiSelectedEvent!}
|
||||||
pushToQueryParams={pushToQueryParams}
|
|
||||||
relatedStats={relatedStatsForIdFromParams!}
|
relatedStats={relatedStatsForIdFromParams!}
|
||||||
eventType={crumbEvent}
|
eventType={crumbEvent}
|
||||||
/>
|
/>
|
||||||
|
@ -198,21 +193,13 @@ const PanelContent = memo(function PanelContent() {
|
||||||
<RelatedEventDetail
|
<RelatedEventDetail
|
||||||
relatedEventId={crumbId}
|
relatedEventId={crumbId}
|
||||||
parentEvent={uiSelectedEvent!}
|
parentEvent={uiSelectedEvent!}
|
||||||
pushToQueryParams={pushToQueryParams}
|
|
||||||
countForParent={parentCount}
|
countForParent={parentCount}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// The default 'Event List' / 'List of all processes' view
|
// The default 'Event List' / 'List of all processes' view
|
||||||
return <ProcessListWithCounts pushToQueryParams={pushToQueryParams} />;
|
return <ProcessListWithCounts />;
|
||||||
}, [
|
}, [uiSelectedEvent, crumbEvent, crumbId, relatedStatsForIdFromParams, panelToShow]);
|
||||||
uiSelectedEvent,
|
|
||||||
crumbEvent,
|
|
||||||
crumbId,
|
|
||||||
pushToQueryParams,
|
|
||||||
relatedStatsForIdFromParams,
|
|
||||||
panelToShow,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return <>{panelInstance}</>;
|
return <>{panelInstance}</>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,62 +1,61 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
|
import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { StyledBreadcrumbs } from './panel_content_utilities';
|
import { StyledBreadcrumbs } from './panel_content_utilities';
|
||||||
import { CrumbInfo } from '../../types';
|
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.
|
* 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 {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
|
* @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
|
||||||
*/
|
*/
|
||||||
export const PanelContentError = memo(function ({
|
export const PanelContentError = memo(function ({
|
||||||
translatedErrorMessage,
|
translatedErrorMessage,
|
||||||
pushToQueryParams,
|
}: {
|
||||||
}: {
|
translatedErrorMessage: string;
|
||||||
translatedErrorMessage: string;
|
}) {
|
||||||
pushToQueryParams: (arg0: CrumbInfo) => unknown;
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
}) {
|
const crumbs = useMemo(() => {
|
||||||
const crumbs = useMemo(() => {
|
return [
|
||||||
return [
|
{
|
||||||
{
|
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
|
||||||
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
|
defaultMessage: 'Events',
|
||||||
defaultMessage: 'Events',
|
}),
|
||||||
}),
|
onClick: () => {
|
||||||
onClick: () => {
|
pushToQueryParams({ crumbId: '', crumbEvent: '' });
|
||||||
pushToQueryParams({ crumbId: '', crumbEvent: '' });
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
|
||||||
text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
|
defaultMessage: 'Error',
|
||||||
defaultMessage: 'Error',
|
}),
|
||||||
}),
|
onClick: () => {},
|
||||||
onClick: () => {},
|
},
|
||||||
},
|
];
|
||||||
];
|
}, [pushToQueryParams]);
|
||||||
}, [pushToQueryParams]);
|
return (
|
||||||
return (
|
<>
|
||||||
<>
|
<StyledBreadcrumbs breadcrumbs={crumbs} />
|
||||||
<StyledBreadcrumbs breadcrumbs={crumbs} />
|
<EuiSpacer size="l" />
|
||||||
<EuiSpacer size="l" />
|
<EuiText textAlign="center">{translatedErrorMessage}</EuiText>
|
||||||
<EuiText textAlign="center">{translatedErrorMessage}</EuiText>
|
<EuiSpacer size="l" />
|
||||||
<EuiSpacer size="l" />
|
<EuiButtonEmpty
|
||||||
<EuiButtonEmpty
|
onClick={() => {
|
||||||
onClick={() => {
|
pushToQueryParams({ crumbId: '', crumbEvent: '' });
|
||||||
pushToQueryParams({ crumbId: '', crumbEvent: '' });
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
|
||||||
{i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
|
defaultMessage: 'Click this link to return to the list of all processes.',
|
||||||
defaultMessage: 'Click this link to return to the list of all processes.',
|
})}
|
||||||
})}
|
</EuiButtonEmpty>
|
||||||
</EuiButtonEmpty>
|
</>
|
||||||
</>
|
);
|
||||||
);
|
});
|
||||||
});
|
PanelContentError.displayName = 'TableServiceError';
|
||||||
PanelContentError.displayName = 'TableServiceError';
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ import {
|
||||||
import { CubeForProcess } from './cube_for_process';
|
import { CubeForProcess } from './cube_for_process';
|
||||||
import { ResolverEvent } from '../../../../common/endpoint/types';
|
import { ResolverEvent } from '../../../../common/endpoint/types';
|
||||||
import { useResolverTheme } from '../assets';
|
import { useResolverTheme } from '../assets';
|
||||||
import { CrumbInfo, ResolverState } from '../../types';
|
import { ResolverState } from '../../types';
|
||||||
|
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
|
||||||
|
|
||||||
const StyledDescriptionList = styled(EuiDescriptionList)`
|
const StyledDescriptionList = styled(EuiDescriptionList)`
|
||||||
&.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
|
&.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
|
||||||
|
@ -49,10 +50,8 @@ const StyledTitle = styled('h4')`
|
||||||
*/
|
*/
|
||||||
export const ProcessDetails = memo(function ProcessDetails({
|
export const ProcessDetails = memo(function ProcessDetails({
|
||||||
processEvent,
|
processEvent,
|
||||||
pushToQueryParams,
|
|
||||||
}: {
|
}: {
|
||||||
processEvent: ResolverEvent;
|
processEvent: ResolverEvent;
|
||||||
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
|
|
||||||
}) {
|
}) {
|
||||||
const processName = event.eventName(processEvent);
|
const processName = event.eventName(processEvent);
|
||||||
const entityId = event.entityId(processEvent);
|
const entityId = event.entityId(processEvent);
|
||||||
|
@ -127,6 +126,8 @@ export const ProcessDetails = memo(function ProcessDetails({
|
||||||
return processDescriptionListData;
|
return processDescriptionListData;
|
||||||
}, [processEvent]);
|
}, [processEvent]);
|
||||||
|
|
||||||
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
|
|
||||||
const crumbs = useMemo(() => {
|
const crumbs = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/ty
|
||||||
import * as selectors from '../../store/selectors';
|
import * as selectors from '../../store/selectors';
|
||||||
import { useResolverDispatch } from '../use_resolver_dispatch';
|
import { useResolverDispatch } from '../use_resolver_dispatch';
|
||||||
import { RelatedEventLimitWarning } from '../limit_warnings';
|
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.
|
* 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,
|
processEvent,
|
||||||
eventType,
|
eventType,
|
||||||
relatedStats,
|
relatedStats,
|
||||||
pushToQueryParams,
|
|
||||||
}: {
|
}: {
|
||||||
processEvent: ResolverEvent;
|
processEvent: ResolverEvent;
|
||||||
pushToQueryParams: (arg0: CrumbInfo) => unknown;
|
|
||||||
eventType: string;
|
eventType: string;
|
||||||
relatedStats: ResolverNodeStats;
|
relatedStats: ResolverNodeStats;
|
||||||
}) {
|
}) {
|
||||||
|
@ -169,6 +167,8 @@ export const ProcessEventList = memo(function ProcessEventList({
|
||||||
}
|
}
|
||||||
}, [relatedsReady, dispatch, processEntityId]);
|
}, [relatedsReady, dispatch, processEntityId]);
|
||||||
|
|
||||||
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
|
|
||||||
const waitCrumbs = useMemo(() => {
|
const waitCrumbs = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with 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 React, { memo, useContext, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
EuiBasicTableColumn,
|
EuiBasicTableColumn,
|
||||||
|
@ -22,7 +25,7 @@ import { SideEffectContext } from '../side_effect_context';
|
||||||
import { CubeForProcess } from './cube_for_process';
|
import { CubeForProcess } from './cube_for_process';
|
||||||
import { SafeResolverEvent } from '../../../../common/endpoint/types';
|
import { SafeResolverEvent } from '../../../../common/endpoint/types';
|
||||||
import { LimitWarning } from '../limit_warnings';
|
import { LimitWarning } from '../limit_warnings';
|
||||||
import { CrumbInfo } from '../../types';
|
import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
|
||||||
|
|
||||||
const StyledLimitWarning = styled(LimitWarning)`
|
const StyledLimitWarning = styled(LimitWarning)`
|
||||||
flex-flow: row wrap;
|
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.
|
* 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({
|
export const ProcessListWithCounts = memo(() => {
|
||||||
pushToQueryParams,
|
|
||||||
}: {
|
|
||||||
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
|
|
||||||
}) {
|
|
||||||
interface ProcessTableView {
|
interface ProcessTableView {
|
||||||
name?: string;
|
name?: string;
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
|
@ -63,6 +60,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
|
||||||
const dispatch = useResolverDispatch();
|
const dispatch = useResolverDispatch();
|
||||||
const { timestamp } = useContext(SideEffectContext);
|
const { timestamp } = useContext(SideEffectContext);
|
||||||
const isProcessTerminated = useSelector(selectors.isProcessTerminated);
|
const isProcessTerminated = useSelector(selectors.isProcessTerminated);
|
||||||
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
const handleBringIntoViewClick = useCallback(
|
const handleBringIntoViewClick = useCallback(
|
||||||
(processTableViewItem) => {
|
(processTableViewItem) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -16,7 +16,8 @@ import { ResolverEvent } from '../../../../common/endpoint/types';
|
||||||
import * as selectors from '../../store/selectors';
|
import * as selectors from '../../store/selectors';
|
||||||
import { useResolverDispatch } from '../use_resolver_dispatch';
|
import { useResolverDispatch } from '../use_resolver_dispatch';
|
||||||
import { PanelContentError } from './panel_content_error';
|
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
|
// Adding some styles to prevent horizontal scrollbars, per request from UX review
|
||||||
const StyledDescriptionList = memo(styled(EuiDescriptionList)`
|
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"
|
* 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
|
* it appears in the underlying ResolverEvent
|
||||||
*/
|
*/
|
||||||
export const RelatedEventDetail = memo(function RelatedEventDetail({
|
export const RelatedEventDetail = memo(function ({
|
||||||
relatedEventId,
|
relatedEventId,
|
||||||
parentEvent,
|
parentEvent,
|
||||||
pushToQueryParams,
|
|
||||||
countForParent,
|
countForParent,
|
||||||
}: {
|
}: {
|
||||||
relatedEventId: string;
|
relatedEventId: string;
|
||||||
parentEvent: ResolverEvent;
|
parentEvent: ResolverEvent;
|
||||||
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
|
|
||||||
countForParent: number | undefined;
|
countForParent: number | undefined;
|
||||||
}) {
|
}) {
|
||||||
const processName = (parentEvent && event.eventName(parentEvent)) || '*';
|
const processName = (parentEvent && event.eventName(parentEvent)) || '*';
|
||||||
|
@ -130,6 +129,8 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
|
||||||
selectors.relatedEventDisplayInfoByEntityAndSelfId(state)(processEntityId, relatedEventId)
|
selectors.relatedEventDisplayInfoByEntityAndSelfId(state)(processEntityId, relatedEventId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
|
|
||||||
const waitCrumbs = useMemo(() => {
|
const waitCrumbs = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -247,9 +248,7 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
|
||||||
defaultMessage: 'Related event not found.',
|
defaultMessage: 'Related event not found.',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return (
|
return <PanelContentError translatedErrorMessage={errString} />;
|
||||||
<PanelContentError translatedErrorMessage={errString} pushToQueryParams={pushToQueryParams} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types
|
||||||
import { useResolverDispatch } from './use_resolver_dispatch';
|
import { useResolverDispatch } from './use_resolver_dispatch';
|
||||||
import * as eventModel from '../../../common/endpoint/models/event';
|
import * as eventModel from '../../../common/endpoint/models/event';
|
||||||
import * as selectors from '../store/selectors';
|
import * as selectors from '../store/selectors';
|
||||||
import { useResolverQueryParams } from './use_resolver_query_params';
|
import { useReplaceBreadcrumbParameters } from './use_replace_breadcrumb_parameters';
|
||||||
|
|
||||||
interface StyledActionsContainer {
|
interface StyledActionsContainer {
|
||||||
readonly color: string;
|
readonly color: string;
|
||||||
|
@ -242,7 +242,7 @@ const UnstyledProcessEventDot = React.memo(
|
||||||
});
|
});
|
||||||
}, [dispatch, nodeID]);
|
}, [dispatch, nodeID]);
|
||||||
|
|
||||||
const { pushToQueryParams } = useResolverQueryParams();
|
const pushToQueryParams = useReplaceBreadcrumbParameters();
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (animationTarget.current?.beginElement) {
|
if (animationTarget.current?.beginElement) {
|
||||||
|
|
|
@ -4,12 +4,17 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { useQueryStringKeys } from './use_query_string_keys';
|
import { useQueryStringKeys } from './use_query_string_keys';
|
||||||
import { CrumbInfo } from '../types';
|
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
|
* 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.
|
* panel content view to allow them to dispatch transitions to each other.
|
||||||
|
@ -17,7 +22,7 @@ export function useResolverQueryParams() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const urlSearch = useLocation().search;
|
const urlSearch = useLocation().search;
|
||||||
const { idKey, eventKey } = useQueryStringKeys();
|
const { idKey, eventKey } = useQueryStringKeys();
|
||||||
const pushToQueryParams = useCallback(
|
return useCallback(
|
||||||
(queryStringState: CrumbInfo) => {
|
(queryStringState: CrumbInfo) => {
|
||||||
const urlSearchParams = new URLSearchParams(urlSearch);
|
const urlSearchParams = new URLSearchParams(urlSearch);
|
||||||
|
|
||||||
|
@ -39,17 +44,4 @@ export function useResolverQueryParams() {
|
||||||
},
|
},
|
||||||
[history, urlSearch, idKey, eventKey]
|
[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,
|
|
||||||
};
|
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useLayoutEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useResolverDispatch } from './use_resolver_dispatch';
|
import { useResolverDispatch } from './use_resolver_dispatch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,10 +23,11 @@ export function useStateSyncingActions({
|
||||||
resolverComponentInstanceID: string;
|
resolverComponentInstanceID: string;
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useResolverDispatch();
|
const dispatch = useResolverDispatch();
|
||||||
|
const locationSearch = useLocation().search;
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'appReceivedNewExternalProperties',
|
type: 'appReceivedNewExternalProperties',
|
||||||
payload: { databaseDocumentID, resolverComponentInstanceID },
|
payload: { databaseDocumentID, resolverComponentInstanceID, locationSearch },
|
||||||
});
|
});
|
||||||
}, [dispatch, databaseDocumentID, resolverComponentInstanceID]);
|
}, [dispatch, databaseDocumentID, resolverComponentInstanceID, locationSearch]);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue