diff --git a/src/plugins/discover/public/application/context/context_app_route.tsx b/src/plugins/discover/public/application/context/context_app_route.tsx index a272a032bbe35..7f056f85fe8e9 100644 --- a/src/plugins/discover/public/application/context/context_app_route.tsx +++ b/src/plugins/discover/public/application/context/context_app_route.tsx @@ -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; @@ -47,8 +48,13 @@ export function ContextAppRoute() { const { dataViewId: encodedDataViewId, id } = useParams(); 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 ( @@ -72,9 +78,13 @@ export function ContextAppRoute() { ); } - if (!dataView) { + if (!dataView || rootProfileLoading) { return ; } - return ; + return ( + + + + ); } diff --git a/src/plugins/discover/public/application/context/services/anchor.ts b/src/plugins/discover/public/application/context/services/anchor.ts index 350c292772d87..ee5198a8b4100 100644 --- a/src/plugins/discover/public/application/context/services/anchor.ts +++ b/src/plugins/discover/public/application/context/services/anchor.ts @@ -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'; @@ -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' }, @@ -68,7 +64,7 @@ export async function fetchAnchor( }); return { - anchorRow: profilesManager.resolveDocumentProfile({ + anchorRow: services.profilesManager.resolveDocumentProfile({ record: buildDataTableRecord(doc, dataView, true), }), interceptedWarnings, diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index 432687fdca5e6..16d27857b7ff2 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -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'; @@ -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 { /** @@ -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) => { @@ -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) { + ; + } + return ( - -

- {i18n.translate('discover.doc.pageTitle', { - defaultMessage: 'Single document - #{id}', - values: { id: props.id }, - })} -

- - {reqState === ElasticRequestState.NotFoundDataView && ( - - } - /> - )} - {reqState === ElasticRequestState.NotFound && ( - - } - > - + +

+ {i18n.translate('discover.doc.pageTitle', { + defaultMessage: 'Single document - #{id}', + values: { id: props.id }, + })} +

+ + {reqState === ElasticRequestState.NotFoundDataView && ( + + } /> - - )} - - {reqState === ElasticRequestState.Error && ( - + } + > - } - > - {' '} - + + )} + + {reqState === ElasticRequestState.Error && ( + + } + > - - - )} + id="discover.doc.somethingWentWrongDescription" + defaultMessage="{indexName} is missing." + values={{ indexName: props.index }} + />{' '} + + + +
+ )} - {reqState === ElasticRequestState.Loading && ( - - {' '} - - - )} + {reqState === ElasticRequestState.Loading && ( + + {' '} + + + )} - {reqState === ElasticRequestState.Found && record !== null && dataView && ( -
- -
- )} -
-
+ {reqState === ElasticRequestState.Found && record !== null && dataView && ( +
+ +
+ )} + + + ); } diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index e763a272e45d0..f4b0ec889313c 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -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); @@ -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 ; @@ -354,13 +358,13 @@ export function DiscoverMainRoute({ return ( - <> + {mainContent} - + ); diff --git a/src/plugins/discover/public/components/discover_container/discover_container.tsx b/src/plugins/discover/public/components/discover_container/discover_container.tsx index 4210fd86144b0..92ae1b7c0f4cb 100644 --- a/src/plugins/discover/public/components/discover_container/discover_container.tsx +++ b/src/plugins/discover/public/components/discover_container/discover_container.tsx @@ -45,7 +45,6 @@ const discoverContainerWrapperCss = css` `; const customizationContext: DiscoverCustomizationContext = { - solutionNavId: null, displayMode: 'embedded', inlineTopNav: { enabled: false, diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx index 71904870a9e4f..7121831358e82 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -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, + }); + const GridFlyout = useMemo( + () => getRenderDocViewerFlyout(DefaultDiscoverGridFlyout), + [getRenderDocViewerFlyout] + ); + + return ; +} + +const DefaultDiscoverGridFlyout = ({ hit, hits, dataView, @@ -56,7 +68,7 @@ export function DiscoverGridFlyout({ onRemoveColumn, onAddColumn, setExpandedDoc, -}: DiscoverGridFlyoutProps) { +}: DiscoverGridFlyoutProps) => { const services = useDiscoverServices(); const flyoutCustomization = useDiscoverCustomization('flyout'); const isESQLQuery = isOfAggregateQueryType(query); @@ -114,7 +126,7 @@ export function DiscoverGridFlyout({ setExpandedDoc={setExpandedDoc} /> ); -} +}; // eslint-disable-next-line import/no-default-export export default DiscoverGridFlyout; diff --git a/src/plugins/discover/public/context_awareness/composable_profile.ts b/src/plugins/discover/public/context_awareness/composable_profile.ts index 84c8b6afca0b3..f7891d587c661 100644 --- a/src/plugins/discover/public/context_awareness/composable_profile.ts +++ b/src/plugins/discover/public/context_awareness/composable_profile.ts @@ -14,16 +14,29 @@ import type { Profile } from './types'; */ export type PartialProfile = Partial; +export interface ComposableAccessorParams { + context: TContext; +} + /** * An accessor function that allows retrieving the extension point result from previous profiles */ -export type ComposableAccessor = (getPrevious: T) => T; +type ComposableAccessor = ( + prev: TPrev, + params: ComposableAccessorParams +) => TPrev; /** * A partial profile implementation that supports composition across multiple profiles */ -export type ComposableProfile = { - [TKey in keyof TProfile]?: ComposableAccessor; +export type ComposableProfile = { + [TKey in keyof TProfile]?: ComposableAccessor; +}; + +type AppliedAccessor = (prev: TPrev) => TPrev; + +export type AppliedProfile = { + [TKey in keyof Profile]?: AppliedAccessor; }; /** @@ -34,7 +47,7 @@ export type ComposableProfile = { * @returns The merged extension point accessor function */ export const getMergedAccessor = ( - profiles: ComposableProfile[], + profiles: AppliedProfile[], key: TKey, baseImpl: Profile[TKey] ) => { diff --git a/src/plugins/discover/public/context_awareness/hooks/use_profiles.ts b/src/plugins/discover/public/context_awareness/hooks/use_profiles.ts index 1b2d4fbd52f33..4f8966f3de772 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_profiles.ts +++ b/src/plugins/discover/public/context_awareness/hooks/use_profiles.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import type { GetProfilesOptions } from '../profiles_manager'; @@ -18,6 +18,7 @@ import type { GetProfilesOptions } from '../profiles_manager'; */ export const useProfiles = ({ record }: GetProfilesOptions = {}) => { const { profilesManager } = useDiscoverServices(); + const skipInitialSet = useRef(true); const [profiles, setProfiles] = useState(() => profilesManager.getProfiles({ record })); const profiles$ = useMemo( () => profilesManager.getProfiles$({ record }), @@ -26,11 +27,11 @@ export const useProfiles = ({ record }: GetProfilesOptions = {}) => { useEffect(() => { const subscription = profiles$.subscribe((newProfiles) => { - setProfiles((currentProfiles) => { - return currentProfiles.every((profile, i) => profile === newProfiles[i]) - ? currentProfiles - : newProfiles; - }); + if (!skipInitialSet.current) { + setProfiles(newProfiles); + } + + skipInitialSet.current = false; }); return () => { diff --git a/src/plugins/discover/public/context_awareness/hooks/use_root_profile.ts b/src/plugins/discover/public/context_awareness/hooks/use_root_profile.ts index 2ffccc6d786b2..4aa099fa0c2dd 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_root_profile.ts +++ b/src/plugins/discover/public/context_awareness/hooks/use_root_profile.ts @@ -8,6 +8,7 @@ */ import { useEffect, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; import { useDiscoverServices } from '../../hooks/use_discover_services'; /** @@ -15,8 +16,9 @@ import { useDiscoverServices } from '../../hooks/use_discover_services'; * @param options Options object * @returns If the root profile is loading */ -export const useRootProfile = ({ solutionNavId }: { solutionNavId: string | null }) => { - const { profilesManager } = useDiscoverServices(); +export const useRootProfile = () => { + const { profilesManager, core } = useDiscoverServices(); + const solutionNavId = useObservable(core.chrome.getActiveSolutionNavId$()); const [rootProfileLoading, setRootProfileLoading] = useState(true); useEffect(() => { diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx index 49950368300bf..fb15642bce79c 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx @@ -7,6 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import React, { createContext, useContext } from 'react'; +import { getFieldValue } from '@kbn/discover-utils'; +import { EuiFlyout } from '@elastic/eui'; import { LOG_LEVEL_FIELDS } from '../../../../../../common/data_types/logs/constants'; import { getLogLevelBadgeCell } from '../../../../../components/data_types/logs/log_level_badge_cell'; import type { DataSourceProfileProvider } from '../../../../profiles'; @@ -22,4 +25,36 @@ export const getCellRenderers: DataSourceProfileProvider['profile']['getCellRend }), {} ), + 'error.message': function ErrorMessage(params) { + const errorMessage = getFieldValue(params.row, 'error.message'); + const test = useContext(testContext); + return ( + <> + {test}: {errorMessage} + + ); + }, }); + +const testContext = createContext('test'); + +export const getRenderAppWrapper: DataSourceProfileProvider<{ + foo?: string; +}>['profile']['getRenderAppWrapper'] = + (PrevWrapper, { context }) => + ({ children }) => { + return ( + + {children} + + ); + }; + +export const getRenderDocViewerFlyout: DataSourceProfileProvider['profile']['getRenderDocViewerFlyout'] = + () => (props) => { + return ( + +
{JSON.stringify(props.hit, null, 2)}
+
+ ); + }; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.ts index 7b9d5ef3e5961..3431bd0f4fae2 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.ts @@ -15,12 +15,15 @@ import { getRowAdditionalLeadingControls, } from './accessors'; import { extractIndexPatternFrom } from '../../extract_index_pattern_from'; +import { getRenderAppWrapper, getRenderDocViewerFlyout } from './accessors/get_cell_renderers'; export const createLogsDataSourceProfileProvider = ( services: ProfileProviderServices -): DataSourceProfileProvider => ({ +): DataSourceProfileProvider<{ foo?: string }> => ({ profileId: 'logs-data-source-profile', profile: { + getRenderAppWrapper, + getRenderDocViewerFlyout, getRowIndicatorProvider, getCellRenderers, getRowAdditionalLeadingControls, @@ -34,7 +37,7 @@ export const createLogsDataSourceProfileProvider = ( return { isMatch: true, - context: { category: DataSourceCategory.Logs }, + context: { category: DataSourceCategory.Logs, foo: 'bar' }, }; }, }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example/example_root_pofile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example/example_root_pofile/profile.tsx index ad247eacee666..bf0123e7d023f 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example/example_root_pofile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example/example_root_pofile/profile.tsx @@ -7,33 +7,102 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiBadge } from '@elastic/eui'; +import { EuiBadge, EuiFlyout } from '@elastic/eui'; import { getFieldValue } from '@kbn/discover-utils'; import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import useObservable from 'react-use/lib/useObservable'; import { RootProfileProvider, SolutionType } from '../../../profiles'; -export const createExampleRootProfileProvider = (): RootProfileProvider => ({ - profileId: 'example-root-profile', - isExperimental: true, - profile: { - getCellRenderers: (prev) => () => ({ - ...prev(), - '@timestamp': (props) => { - const timestamp = getFieldValue(props.row, '@timestamp'); +interface ExampleRootProfileContext { + count$: BehaviorSubject; + selectedAgentName$: BehaviorSubject; +} - return ( - - {timestamp} - - ); +export const createExampleRootProfileProvider = + (): RootProfileProvider => ({ + profileId: 'example-root-profile', + isExperimental: true, + profile: { + getRenderAppWrapper: (PrevWrapper, params) => { + return function AppWrapper({ children }) { + const selectedAgentName = useObservable(params.context.selectedAgentName$); + + return ( + + {children} + {selectedAgentName && ( + { + params.context.selectedAgentName$.next(undefined); + }} + > +
agent name: {selectedAgentName}
+
+ )} +
+ ); + }; }, - }), - }, - resolve: (params) => { - if (params.solutionNavId != null) { - return { isMatch: false }; - } + getCellRenderers: + (prev, { context: { count$, selectedAgentName$ } }) => + () => ({ + ...prev(), + '@timestamp': function Timestamp(props) { + const timestamp = getFieldValue(props.row, '@timestamp'); + const count = useObservable(count$, 0); + + return ( + <> +
+ { + count$.next(count + 1); + }} + onClickAriaLabel="Increment count" + data-test-subj="exampleRootProfileTimestamp" + > + {timestamp} + +
+
Count: {count}
+ + ); + }, + 'agent.name': function AgentName(props) { + const agentName = getFieldValue(props.row, 'agent.name'); + + return ( + { + selectedAgentName$.next(agentName + ` (${props.row.id})`); + }} + onClickAriaLabel="Select agent name" + data-test-subj="exampleRootProfileAgentName" + > + {agentName} + + ); + }, + }), + }, + resolve: (params) => { + if (params.solutionNavId != null) { + return { isMatch: false }; + } - return { isMatch: true, context: { solutionType: SolutionType.Default } }; - }, -}); + return { + isMatch: true, + context: { + solutionType: SolutionType.Default, + count$: new BehaviorSubject(0), + selectedAgentName$: new BehaviorSubject(undefined), + }, + }; + }, + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts index 9382101464ec7..9eea569b66217 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts @@ -15,7 +15,7 @@ import type { BaseProfileProvider } from '../profile_service'; * @param extension The extension to apply to the base profile provider * @returns The extended profile provider */ -export const extendProfileProvider = >( +export const extendProfileProvider = >( baseProvider: TProvider, extension: Partial & Pick ): TProvider => ({ diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 9cd65320ac140..069f5f3e11f11 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -80,7 +80,7 @@ export const registerProfileProviders = ({ * @param options Register enabled profile providers options */ export const registerEnabledProfileProviders = < - TProvider extends BaseProfileProvider<{}>, + TProvider extends BaseProfileProvider<{}, {}>, TService extends BaseProfileService >({ profileService, diff --git a/src/plugins/discover/public/context_awareness/profile_service.ts b/src/plugins/discover/public/context_awareness/profile_service.ts index aaeb7664aa5cd..56936e44e3baa 100644 --- a/src/plugins/discover/public/context_awareness/profile_service.ts +++ b/src/plugins/discover/public/context_awareness/profile_service.ts @@ -9,8 +9,13 @@ /* eslint-disable max-classes-per-file */ -import type { ComposableProfile, PartialProfile } from './composable_profile'; -import type { Profile } from './types'; +import { isFunction } from 'lodash'; +import type { + AppliedProfile, + ComposableAccessorParams, + ComposableProfile, + PartialProfile, +} from './composable_profile'; /** * The profile provider resolution result @@ -41,7 +46,7 @@ export type ContextWithProfileId = TContext & { profileId: string }; /** * The base profile provider interface */ -export interface BaseProfileProvider { +export interface BaseProfileProvider { /** * The unique profile ID */ @@ -49,7 +54,7 @@ export interface BaseProfileProvider { /** * The composable profile implementation */ - profile: ComposableProfile; + profile: ComposableProfile; /** * Set the `isExperimental` flag to `true` for any profile which is under development and should not be enabled by default. * @@ -68,7 +73,7 @@ export interface BaseProfileProvider { * A synchronous profile provider interface */ export interface ProfileProvider - extends BaseProfileProvider { + extends BaseProfileProvider { /** * The method responsible for context resolution and determining if the associated profile is a match * @param params Parameters specific to the provider context level @@ -81,7 +86,7 @@ export interface ProfileProvider - extends BaseProfileProvider { + extends BaseProfileProvider { /** * The method responsible for context resolution and determining if the associated profile is a match * @param params Parameters specific to the provider context level @@ -97,7 +102,7 @@ const EMPTY_PROFILE = {}; /** * The base profile service implementation */ -export abstract class BaseProfileService, TContext> { +export abstract class BaseProfileService, TContext> { protected readonly providers: TProvider[] = []; /** @@ -114,13 +119,32 @@ export abstract class BaseProfileService): ComposableProfile { - const provider = this.providers.find((current) => current.profileId === context.profileId); - return provider?.profile ?? EMPTY_PROFILE; + public getProfile( + params: ComposableAccessorParams> + ): AppliedProfile { + const provider = this.providers.find( + (current) => current.profileId === params.context.profileId + ); + + if (!provider?.profile) { + return EMPTY_PROFILE; + } + + return new Proxy(provider.profile, { + get: (target, prop, receiver) => { + const accessor = Reflect.get(target, prop, receiver); + + if (!isFunction(accessor)) { + return accessor; + } + + return (prev: AppliedProfile[keyof AppliedProfile]) => accessor(prev, params); + }, + }); } } diff --git a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts index 807072d777a93..226ff7e182c9c 100644 --- a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts @@ -59,10 +59,10 @@ export interface DataSourceContext { category: DataSourceCategory; } -export type DataSourceProfileProvider = AsyncProfileProvider< +export type DataSourceProfileProvider = AsyncProfileProvider< DataSourceProfile, DataSourceProfileProviderParams, - DataSourceContext + DataSourceContext & TProviderContext >; export class DataSourceProfileService extends AsyncProfileService< diff --git a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts index cd14e9cdec010..89bf601f28200 100644 --- a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts @@ -54,10 +54,10 @@ export interface DocumentContext { type: DocumentType; } -export type DocumentProfileProvider = ProfileProvider< +export type DocumentProfileProvider = ProfileProvider< DocumentProfile, DocumentProfileProviderParams, - DocumentContext + DocumentContext & TProviderContext >; export class DocumentProfileService extends ProfileService< diff --git a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts index 853c93c05cf64..294fea9e266ab 100644 --- a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts @@ -45,10 +45,10 @@ export interface RootContext { solutionType: SolutionType; } -export type RootProfileProvider = AsyncProfileProvider< +export type RootProfileProvider = AsyncProfileProvider< RootProfile, RootProfileProviderParams, - RootContext + RootContext & TProviderContext >; export class RootProfileService extends AsyncProfileService< diff --git a/src/plugins/discover/public/context_awareness/profiles_manager.ts b/src/plugins/discover/public/context_awareness/profiles_manager.ts index 8c24e425147f5..f92e89afe7267 100644 --- a/src/plugins/discover/public/context_awareness/profiles_manager.ts +++ b/src/plugins/discover/public/context_awareness/profiles_manager.ts @@ -176,11 +176,13 @@ export class ProfilesManager { */ public getProfiles({ record }: GetProfilesOptions = {}) { return [ - this.rootProfileService.getProfile(this.rootContext$.getValue()), - this.dataSourceProfileService.getProfile(this.dataSourceContext$.getValue()), - this.documentProfileService.getProfile( - recordHasContext(record) ? record.context : this.documentProfileService.defaultContext - ), + this.rootProfileService.getProfile({ context: this.rootContext$.getValue() }), + this.dataSourceProfileService.getProfile({ context: this.dataSourceContext$.getValue() }), + this.documentProfileService.getProfile({ + context: recordHasContext(record) + ? record.context + : this.documentProfileService.defaultContext, + }), ]; } diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index dacc2edbc218d..634fc26932370 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -16,8 +16,10 @@ import type { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import type { OmitIndexSignature } from 'type-fest'; import type { Trigger } from '@kbn/ui-actions-plugin/public'; +import type { PropsWithChildren, ReactElement } from 'react'; import type { DiscoverDataSource } from '../../common/data_sources'; import type { DiscoverAppState } from '../application/main/state_management/discover_app_state_container'; +import type { DiscoverGridFlyoutProps } from '../components/discover_grid_flyout'; /** * Supports customizing the Discover document viewer flyout @@ -201,6 +203,14 @@ export interface Profile { * Lifecycle */ + /** + * Render a custom wrapper component around the Discover application, + * e.g. to allow using profile specific context providers + * @param props The app wrapper props + * @returns The custom app wrapper component + */ + getRenderAppWrapper: (props: PropsWithChildren<{}>) => ReactElement; + /** * Gets default Discover app state that should be used when the profile is resolved * @param params The default app state extension parameters @@ -254,4 +264,13 @@ export interface Profile { * @returns The doc viewer extension */ getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; + + /** + * Supports fully overriding the Discover document viewer flyout + * with a custom component -- takes precedence over `getDocViewer` + * and is not supported in the single document view + * @param props The doc viewer override props + * @returns The custom doc viewer component + */ + getRenderDocViewerFlyout: (props: DiscoverGridFlyoutProps) => ReactElement; } diff --git a/src/plugins/discover/public/customizations/defaults.ts b/src/plugins/discover/public/customizations/defaults.ts index 600f1501a1d41..d44b6527b3909 100644 --- a/src/plugins/discover/public/customizations/defaults.ts +++ b/src/plugins/discover/public/customizations/defaults.ts @@ -10,7 +10,6 @@ import { DiscoverCustomizationContext } from './types'; export const defaultCustomizationContext: DiscoverCustomizationContext = { - solutionNavId: null, displayMode: 'standalone', inlineTopNav: { enabled: false, diff --git a/src/plugins/discover/public/customizations/types.ts b/src/plugins/discover/public/customizations/types.ts index e72426b00d8a2..bf71fa80148ec 100644 --- a/src/plugins/discover/public/customizations/types.ts +++ b/src/plugins/discover/public/customizations/types.ts @@ -22,10 +22,6 @@ export type CustomizationCallback = ( export type DiscoverDisplayMode = 'embedded' | 'standalone'; export interface DiscoverCustomizationContext { - /** - * The current solution nav ID - */ - solutionNavId: string | null; /* * Display mode in which discover is running */ diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 5139e2342c988..e177db0d67461 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -199,7 +199,6 @@ export class DiscoverPlugin .pipe( map((solutionNavId) => ({ ...defaultCustomizationContext, - solutionNavId, inlineTopNav: this.inlineTopNav.get(solutionNavId) ?? this.inlineTopNav.get(null) ??