Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discover] Support for Security Solution flyout #192934

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { LoadingIndicator } from '../../components/common/loading_indicator';
import { useDataView } from '../../hooks/use_data_view';
import type { ContextHistoryLocationState } from './services/locator';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import { useProfileAccessor, useRootProfile } from '../../context_awareness';

export interface ContextUrlParams {
dataViewId: string;
Expand Down Expand Up @@ -47,8 +48,13 @@ export function ContextAppRoute() {
const { dataViewId: encodedDataViewId, id } = useParams<ContextUrlParams>();
const dataViewId = decodeURIComponent(encodedDataViewId);
const anchorId = decodeURIComponent(id);

const { dataView, error } = useDataView({ index: locationState?.dataViewSpec || dataViewId });
const { rootProfileLoading } = useRootProfile();
const getRenderAppWrapperAccessor = useProfileAccessor('getRenderAppWrapper');
const AppWrapper = useMemo(
() => getRenderAppWrapperAccessor(({ children }) => <>{children}</>),
[getRenderAppWrapperAccessor]
);

if (error) {
return (
Expand All @@ -72,9 +78,13 @@ export function ContextAppRoute() {
);
}

if (!dataView) {
if (!dataView || rootProfileLoading) {
return <LoadingIndicator />;
}

return <ContextApp anchorId={anchorId} dataView={dataView} referrer={locationState?.referrer} />;
return (
<AppWrapper>
<ContextApp anchorId={anchorId} dataView={dataView} referrer={locationState?.referrer} />
</AppWrapper>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { firstValueFrom, lastValueFrom } from 'rxjs';
import { lastValueFrom } from 'rxjs';
import { i18n } from '@kbn/i18n';
import { ISearchSource, EsQuerySortValue } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
Expand All @@ -29,11 +29,7 @@ export async function fetchAnchor(
anchorRow: DataTableRecord;
interceptedWarnings: SearchResponseWarning[];
}> {
const { core, profilesManager } = services;

const solutionNavId = await firstValueFrom(core.chrome.getActiveSolutionNavId$());
await profilesManager.resolveRootProfile({ solutionNavId });
await profilesManager.resolveDataSourceProfile({
await services.profilesManager.resolveDataSourceProfile({
dataSource: createDataSource({ dataView, query: undefined }),
dataView,
query: { query: '', language: 'kuery' },
Expand Down Expand Up @@ -68,7 +64,7 @@ export async function fetchAnchor(
});

return {
anchorRow: profilesManager.resolveDocumentProfile({
anchorRow: services.profilesManager.resolveDocumentProfile({
record: buildDataTableRecord(doc, dataView, true),
}),
interceptedWarnings,
Expand Down
182 changes: 97 additions & 85 deletions src/plugins/discover/public/application/doc/components/doc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { firstValueFrom } from 'rxjs';
import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPage, EuiPageBody } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ElasticRequestState } from '@kbn/unified-doc-viewer';
Expand All @@ -20,6 +19,8 @@ import { setBreadcrumbs } from '../../../utils/breadcrumbs';
import { useDiscoverServices } from '../../../hooks/use_discover_services';
import { SingleDocViewer } from './single_doc_viewer';
import { createDataViewDataSource } from '../../../../common/data_sources';
import { useProfileAccessor, useRootProfile } from '../../../context_awareness';
import { LoadingIndicator } from '../../../components/common/loading_indicator';

export interface DocProps extends EsDocSearchProps {
/**
Expand All @@ -31,18 +32,16 @@ export interface DocProps extends EsDocSearchProps {
export function Doc(props: DocProps) {
const { dataView } = props;
const services = useDiscoverServices();
const { locator, chrome, docLinks, core, profilesManager } = services;
const { locator, chrome, docLinks, profilesManager } = services;
const indexExistsLink = docLinks.links.apis.indexExists;

const onBeforeFetch = useCallback(async () => {
const solutionNavId = await firstValueFrom(core.chrome.getActiveSolutionNavId$());
await profilesManager.resolveRootProfile({ solutionNavId });
await profilesManager.resolveDataSourceProfile({
dataSource: dataView?.id ? createDataViewDataSource({ dataViewId: dataView.id }) : undefined,
dataView,
query: { query: '', language: 'kuery' },
});
}, [profilesManager, core, dataView]);
}, [profilesManager, dataView]);

const onProcessRecord = useCallback(
(record: DataTableRecord) => {
Expand All @@ -65,91 +64,104 @@ export function Doc(props: DocProps) {
});
}, [chrome, props.referrer, props.index, props.id, dataView, locator, services]);

const { rootProfileLoading } = useRootProfile();
const getRenderAppWrapperAccessor = useProfileAccessor('getRenderAppWrapper');
const AppWrapper = useMemo(
() => getRenderAppWrapperAccessor(({ children }) => <>{children}</>),
[getRenderAppWrapperAccessor]
);

if (rootProfileLoading) {
<LoadingIndicator />;
}

return (
<EuiPage>
<h1
id="singleDocTitle"
className="euiScreenReaderOnly"
data-test-subj="discoverSingleDocTitle"
>
{i18n.translate('discover.doc.pageTitle', {
defaultMessage: 'Single document - #{id}',
values: { id: props.id },
})}
</h1>
<EuiPageBody panelled paddingSize="m" panelProps={{ role: 'main' }}>
{reqState === ElasticRequestState.NotFoundDataView && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-notFoundDataView`}
iconType="warning"
title={
<FormattedMessage
id="discover.doc.failedToLocateDataView"
defaultMessage="No data view matches ID {dataViewId}."
values={{ dataViewId: dataView.id }}
/>
}
/>
)}
{reqState === ElasticRequestState.NotFound && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-notFound`}
iconType="warning"
title={
<FormattedMessage
id="discover.doc.failedToLocateDocumentDescription"
defaultMessage="Cannot find document"
/>
}
>
<FormattedMessage
id="discover.doc.couldNotFindDocumentsDescription"
defaultMessage="No documents match that ID."
<AppWrapper>
<EuiPage>
<h1
id="singleDocTitle"
className="euiScreenReaderOnly"
data-test-subj="discoverSingleDocTitle"
>
{i18n.translate('discover.doc.pageTitle', {
defaultMessage: 'Single document - #{id}',
values: { id: props.id },
})}
</h1>
<EuiPageBody panelled paddingSize="m" panelProps={{ role: 'main' }}>
{reqState === ElasticRequestState.NotFoundDataView && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-notFoundDataView`}
iconType="warning"
title={
<FormattedMessage
id="discover.doc.failedToLocateDataView"
defaultMessage="No data view matches ID {dataViewId}."
values={{ dataViewId: dataView.id }}
/>
}
/>
</EuiCallOut>
)}

{reqState === ElasticRequestState.Error && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-error`}
iconType="warning"
title={
)}
{reqState === ElasticRequestState.NotFound && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-notFound`}
iconType="warning"
title={
<FormattedMessage
id="discover.doc.failedToLocateDocumentDescription"
defaultMessage="Cannot find document"
/>
}
>
<FormattedMessage
id="discover.doc.failedToExecuteQueryDescription"
defaultMessage="Cannot run search"
id="discover.doc.couldNotFindDocumentsDescription"
defaultMessage="No documents match that ID."
/>
}
>
<FormattedMessage
id="discover.doc.somethingWentWrongDescription"
defaultMessage="{indexName} is missing."
values={{ indexName: props.index }}
/>{' '}
<EuiLink href={indexExistsLink} target="_blank">
</EuiCallOut>
)}

{reqState === ElasticRequestState.Error && (
<EuiCallOut
color="danger"
data-test-subj={`doc-msg-error`}
iconType="warning"
title={
<FormattedMessage
id="discover.doc.failedToExecuteQueryDescription"
defaultMessage="Cannot run search"
/>
}
>
<FormattedMessage
id="discover.doc.somethingWentWrongDescriptionAddon"
defaultMessage="Please ensure the index exists."
/>
</EuiLink>
</EuiCallOut>
)}
id="discover.doc.somethingWentWrongDescription"
defaultMessage="{indexName} is missing."
values={{ indexName: props.index }}
/>{' '}
<EuiLink href={indexExistsLink} target="_blank">
<FormattedMessage
id="discover.doc.somethingWentWrongDescriptionAddon"
defaultMessage="Please ensure the index exists."
/>
</EuiLink>
</EuiCallOut>
)}

{reqState === ElasticRequestState.Loading && (
<EuiCallOut data-test-subj={`doc-msg-loading`}>
<EuiLoadingSpinner size="m" />{' '}
<FormattedMessage id="discover.doc.loadingDescription" defaultMessage="Loading…" />
</EuiCallOut>
)}
{reqState === ElasticRequestState.Loading && (
<EuiCallOut data-test-subj={`doc-msg-loading`}>
<EuiLoadingSpinner size="m" />{' '}
<FormattedMessage id="discover.doc.loadingDescription" defaultMessage="Loading…" />
</EuiCallOut>
)}

{reqState === ElasticRequestState.Found && record !== null && dataView && (
<div data-test-subj="doc-hit">
<SingleDocViewer record={record} dataView={dataView} />
</div>
)}
</EuiPageBody>
</EuiPage>
{reqState === ElasticRequestState.Found && record !== null && dataView && (
<div data-test-subj="doc-hit">
<SingleDocViewer record={record} dataView={dataView} />
</div>
)}
</EuiPageBody>
</EuiPage>
</AppWrapper>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
import { DiscoverTopNavInline } from './components/top_nav/discover_topnav_inline';
import { DiscoverStateContainer, LoadParams } from './state_management/discover_state';
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
import { useRootProfile } from '../../context_awareness';
import { useProfileAccessor, useRootProfile } from '../../context_awareness';

const DiscoverMainAppMemoized = memo(DiscoverMainApp);

Expand Down Expand Up @@ -340,8 +340,12 @@ export function DiscoverMainRoute({
stateContainer,
]);

const { solutionNavId } = customizationContext;
const { rootProfileLoading } = useRootProfile({ solutionNavId });
const { rootProfileLoading } = useRootProfile();
const getRenderAppWrapperAccessor = useProfileAccessor('getRenderAppWrapper');
const AppWrapper = useMemo(
() => getRenderAppWrapperAccessor(({ children }) => <>{children}</>),
[getRenderAppWrapperAccessor]
);

if (error) {
return <DiscoverError error={error} />;
Expand All @@ -354,13 +358,13 @@ export function DiscoverMainRoute({
return (
<DiscoverCustomizationProvider value={customizationService}>
<DiscoverMainProvider value={stateContainer}>
<>
<AppWrapper>
<DiscoverTopNavInline
stateContainer={stateContainer}
hideNavMenuItems={loading || showNoDataPage}
/>
{mainContent}
</>
</AppWrapper>
</DiscoverMainProvider>
</DiscoverCustomizationProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const discoverContainerWrapperCss = css`
`;

const customizationContext: DiscoverCustomizationContext = {
solutionNavId: null,
displayMode: 'embedded',
inlineTopNav: {
enabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@ export interface DiscoverGridFlyoutProps {
/**
* Flyout displaying an expanded Elasticsearch document
*/
export function DiscoverGridFlyout({
export function DiscoverGridFlyout(props: DiscoverGridFlyoutProps) {
const getRenderDocViewerFlyout = useProfileAccessor('getRenderDocViewerFlyout', {
record: props.hit,
});
Copy link
Contributor

@logeekal logeekal Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @davismcphee for creating this extension point. Althought this extension point works to display our flyout. I am still working on that. What it does not do is gives any handle of onClose event to the users of extension point.

For example, consider 2 use cases:

  1. Users click close button of the flyout, in that case we want to effect the expand icon change in the Discover DataGrid as seen below:

  2. User clicking on expand button toggles the flyout but the extension point does not have access to know when user has clicked expand icon.

I think giving access to setExpandedDoc should be good enough and may also be more powerful than getRenderDocViewerFlyout.

What do you think?

const GridFlyout = useMemo(
() => getRenderDocViewerFlyout(DefaultDiscoverGridFlyout),
[getRenderDocViewerFlyout]
);

return <GridFlyout {...props} />;
}

const DefaultDiscoverGridFlyout = ({
hit,
hits,
dataView,
Expand All @@ -56,7 +68,7 @@ export function DiscoverGridFlyout({
onRemoveColumn,
onAddColumn,
setExpandedDoc,
}: DiscoverGridFlyoutProps) {
}: DiscoverGridFlyoutProps) => {
const services = useDiscoverServices();
const flyoutCustomization = useDiscoverCustomization('flyout');
const isESQLQuery = isOfAggregateQueryType(query);
Expand Down Expand Up @@ -114,7 +126,7 @@ export function DiscoverGridFlyout({
setExpandedDoc={setExpandedDoc}
/>
);
}
};

// eslint-disable-next-line import/no-default-export
export default DiscoverGridFlyout;
Loading