Skip to content

Commit

Permalink
[Log Explorer] Add Discover fallback link (elastic#165464)
Browse files Browse the repository at this point in the history
## 📓 Summary

Closes elastic#165220 

This PR introduces a new fallback link from the Log Explorer application
to Discover.

To correctly retrieve the details required to correctly navigate to
Discover with the used data view and filters, the LogExplorer component
accepts now a new `state$` behaviour subject as a property that can be
used to notify the consumers of any change from the internal state.


https://github.com/elastic/kibana/assets/34506779/c8176ef2-7a3b-4c7e-860a-450ba677412a

## 🧪 Test suite

```
↳ Observability Log Explorer
  ↳ Header menu
    ↳ should inject the app header menu on the top navbar
    ↳ Discover fallback link
      ↳ should render a button link
      ↳ should navigate to discover keeping the current columns/filters/query/time/data view
```

---------

Co-authored-by: Marco Antonio Ghiani <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Sep 6, 2023
1 parent 55936a8 commit 1c7ac5d
Show file tree
Hide file tree
Showing 21 changed files with 432 additions and 54 deletions.
1 change: 1 addition & 0 deletions src/plugins/discover/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function plugin(initializerContext: PluginInitializerContext) {
}

export type { ISearchEmbeddable, SearchInput } from './embeddable';
export type { DiscoverAppState } from './application/main/services/discover_app_state_container';
export type { DiscoverStateContainer } from './application/main/services/discover_state';
export type { DiscoverContainerProps } from './components/discover_container';
export type {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/log_explorer/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
export const LOG_EXPLORER_PROFILE_ID = 'log-explorer';

// Fields constants
export const TIMESTAMP_FIELD = '@timestamp';
export const MESSAGE_FIELD = 'message';
2 changes: 2 additions & 0 deletions x-pack/plugins/log_explorer/common/datasets/models/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { IconType } from '@elastic/eui';
import { DataViewSpec } from '@kbn/data-views-plugin/common';
import { IndexPattern } from '@kbn/io-ts-utils';
import { TIMESTAMP_FIELD } from '../../constants';
import { DatasetId, DatasetType, IntegrationType } from '../types';

type IntegrationBase = Pick<IntegrationType, 'name' | 'title' | 'icons' | 'version'>;
Expand Down Expand Up @@ -53,6 +54,7 @@ export class Dataset {
return {
id: this.id,
name: this.getFullTitle(),
timeFieldName: TIMESTAMP_FIELD,
title: this.name as string,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,47 @@
* 2.0.
*/

import React, { useMemo } from 'react';
import { ScopedHistory } from '@kbn/core-application-browser';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import React from 'react';
import { DiscoverAppState, DiscoverStart } from '@kbn/discover-plugin/public';
import type { BehaviorSubject } from 'rxjs';
import {
createLogExplorerProfileCustomizations,
CreateLogExplorerProfileCustomizationsDeps,
} from '../../customizations/log_explorer_profile';
import { createPropertyGetProxy } from '../../utils/proxies';
import { LogExplorerProfileContext } from '../../state_machines/log_explorer_profile';

export interface CreateLogExplorerArgs extends CreateLogExplorerProfileCustomizationsDeps {
discover: DiscoverStart;
}

export interface LogExplorerStateContainer {
appState?: DiscoverAppState;
logExplorerState?: Partial<LogExplorerProfileContext>;
}

export interface LogExplorerProps {
scopedHistory: ScopedHistory;
state$?: BehaviorSubject<LogExplorerStateContainer>;
}

export const createLogExplorer = ({
core,
data,
discover: { DiscoverContainer },
}: CreateLogExplorerArgs) => {
const logExplorerCustomizations = [createLogExplorerProfileCustomizations({ core, data })];

const overrideServices = {
data: createDataServiceProxy(data),
};

return ({ scopedHistory }: LogExplorerProps) => {
return ({ scopedHistory, state$ }: LogExplorerProps) => {
const logExplorerCustomizations = useMemo(
() => [createLogExplorerProfileCustomizations({ core, data, state$ })],
[state$]
);

return (
<DiscoverContainer
customizationCallbacks={logExplorerCustomizations}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@

import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import { CustomizationCallback } from '@kbn/discover-plugin/public';
import { CustomizationCallback, DiscoverStateContainer } from '@kbn/discover-plugin/public';
import React from 'react';
import { type BehaviorSubject, combineLatest, from, map, Subscription } from 'rxjs';
import { dynamic } from '../utils/dynamic';
import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
import { LogExplorerStateContainer } from '../components/log_explorer';

const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector'));
const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters'));

export interface CreateLogExplorerProfileCustomizationsDeps {
core: CoreStart;
data: DataPublicPluginStart;
state$?: BehaviorSubject<LogExplorerStateContainer>;
}

export const createLogExplorerProfileCustomizations =
({ core, data }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback =>
({ core, data, state$ }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback =>
async ({ customizations, stateContainer }) => {
// Lazy load dependencies
const datasetServiceModuleLoadable = import('../services/datasets');
Expand All @@ -38,13 +42,26 @@ export const createLogExplorerProfileCustomizations =
toasts: core.notifications.toasts,
});

//
/**
* Wait for the machine to be fully initialized to set the restored selection
* create the DataView and set it in the stateContainer from Discover
*/
await waitForState(logExplorerProfileStateService, 'initialized');

/**
* Subscribe the state$ BehaviorSubject when the consumer app wants to react to state changes.
* It emits a combined state of:
* - log explorer state machine context
* - appState from the discover stateContainer
*/
let stateSubscription: Subscription;
if (state$) {
stateSubscription = createStateUpdater({
logExplorerProfileStateService,
stateContainer,
}).subscribe(state$);
}

/**
* Replace the DataViewPicker with a custom `DatasetSelector` to pick integrations streams
* Prepend the search bar with custom filter control groups depending on the selected dataset
Expand Down Expand Up @@ -76,4 +93,25 @@ export const createLogExplorerProfileCustomizations =
saveItem: { disabled: true },
},
});

return () => {
if (stateSubscription) {
stateSubscription.unsubscribe();
}
};
};

const createStateUpdater = ({
logExplorerProfileStateService,
stateContainer,
}: {
logExplorerProfileStateService: LogExplorerProfileStateService;
stateContainer: DiscoverStateContainer;
}) => {
return combineLatest([from(logExplorerProfileStateService), stateContainer.appState.state$]).pipe(
map(([logExplorerState, appState]) => ({
logExplorerState: logExplorerState.context,
appState,
}))
);
};
1 change: 1 addition & 0 deletions x-pack/plugins/log_explorer/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { PluginInitializerContext } from '@kbn/core/public';
import type { LogExplorerConfig } from '../common/plugin_config';
import { LogExplorerPlugin } from './plugin';
export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types';
export type { LogExplorerStateContainer } from './components/log_explorer';

export function plugin(context: PluginInitializerContext<LogExplorerConfig>) {
return new LogExplorerPlugin(context);
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/observability_log_explorer/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const OBSERVABILITY_LOG_EXPLORER_APP_ID = 'observability-log-explorer';
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ export const betaBadgeDescription = i18n.translate(
defaultMessage: 'This application is in beta and therefore subject to change.',
}
);

export const discoverLinkTitle = i18n.translate(
'xpack.observabilityLogExplorer.discoverLinkTitle',
{
defaultMessage: 'Discover',
}
);
3 changes: 2 additions & 1 deletion x-pack/plugins/observability_log_explorer/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
],
"requiredPlugins": [
"data",
"discover",
"logExplorer",
"observabilityShared"
],
"optionalPlugins": [
"serverless"
],
"requiredBundles": []
"requiredBundles": ["kibanaReact"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,81 @@
* 2.0.
*/

import { AppMountParameters, CoreStart, ScopedHistory } from '@kbn/core/public';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
import React from 'react';
import ReactDOM from 'react-dom';
import { ObservablityLogExplorerMainRoute } from '../routes/main';
import { ObservabilityLogExplorerPluginStart, ObservabilityLogExplorerStartDeps } from '../types';
import { useKibanaContextForPluginProvider } from '../utils/use_kibana';

export const renderObservabilityLogExplorer = (
core: CoreStart,
pluginsStart: ObservabilityLogExplorerStartDeps,
ownPluginStart: ObservabilityLogExplorerPluginStart,
{ element, history }: AppMountParameters
appParams: AppMountParameters
) => {
ReactDOM.render(
<ObservabilityLogExplorerApp
appParams={appParams}
core={core}
history={history}
plugins={pluginsStart}
pluginStart={ownPluginStart}
/>,
element
appParams.element
);

return () => {
// work around race condition between unmount effect and current app id
// observable in the search session service
pluginsStart.data.search.session.clear();

ReactDOM.unmountComponentAtNode(element);
ReactDOM.unmountComponentAtNode(appParams.element);
};
};

export interface ObservabilityLogExplorerAppProps {
appParams: AppMountParameters;
core: CoreStart;
plugins: ObservabilityLogExplorerStartDeps;
pluginStart: ObservabilityLogExplorerPluginStart;
history: ScopedHistory;
}

export const ObservabilityLogExplorerApp = ({
appParams,
core,
plugins: { logExplorer, observabilityShared, serverless },
plugins,
pluginStart,
history,
}: ObservabilityLogExplorerAppProps) => (
<KibanaRenderContextProvider i18n={core.i18n} theme={core.theme}>
<Router history={history}>
<Routes>
<Route
path="/"
exact={true}
render={() => (
<ObservablityLogExplorerMainRoute
core={core}
history={history}
logExplorer={logExplorer}
observabilityShared={observabilityShared}
serverless={serverless}
}: ObservabilityLogExplorerAppProps) => {
const { logExplorer, observabilityShared, serverless } = plugins;
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(
core,
plugins,
pluginStart
);

return (
<KibanaRenderContextProvider i18n={core.i18n} theme={core.theme}>
<KibanaContextProviderForPlugin>
<Router history={appParams.history}>
<Routes>
<Route
path="/"
exact={true}
render={() => (
<ObservablityLogExplorerMainRoute
appParams={appParams}
core={core}
logExplorer={logExplorer}
observabilityShared={observabilityShared}
serverless={serverless}
/>
)}
/>
)}
/>
</Routes>
</Router>
</KibanaRenderContextProvider>
);
</Routes>
</Router>
</KibanaContextProviderForPlugin>
</KibanaRenderContextProvider>
);
};
Loading

0 comments on commit 1c7ac5d

Please sign in to comment.