[Logs UI] Add shared observability page template and navigation (#99380)
Co-authored-by: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7fc4a1f80f
commit
06d276e060
|
@ -32,6 +32,10 @@ xpack.ruleRegistry.write.enabled: true
|
||||||
|
|
||||||
When both of the these are set to `true`, your alerts should show on the alerts page.
|
When both of the these are set to `true`, your alerts should show on the alerts page.
|
||||||
|
|
||||||
|
## Shared navigation
|
||||||
|
|
||||||
|
The Observability plugin maintains a navigation registry for Observability solutions, and exposes a shared page template component. Please refer to the docs in [the component directory](./components/shared/page_template/README.md) for more information on registering your solution's navigation structure, and rendering the navigation via the shared component.
|
||||||
|
|
||||||
## Unit testing
|
## Unit testing
|
||||||
|
|
||||||
Note: Run the following commands from `kibana/x-pack/plugins/observability`.
|
Note: Run the following commands from `kibana/x-pack/plugins/observability`.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { createMemoryHistory } from 'history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { AppMountParameters, CoreStart } from 'src/core/public';
|
import { AppMountParameters, CoreStart } from 'src/core/public';
|
||||||
|
import { KibanaPageTemplate } from '../../../../../src/plugins/kibana_react/public';
|
||||||
import { ObservabilityPublicPluginsStart } from '../plugin';
|
import { ObservabilityPublicPluginsStart } from '../plugin';
|
||||||
import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock';
|
import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock';
|
||||||
import { renderApp } from './';
|
import { renderApp } from './';
|
||||||
|
@ -59,6 +60,7 @@ describe('renderApp', () => {
|
||||||
plugins,
|
plugins,
|
||||||
appMountParameters: params,
|
appMountParameters: params,
|
||||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||||
|
ObservabilityPageTemplate: KibanaPageTemplate,
|
||||||
});
|
});
|
||||||
unmount();
|
unmount();
|
||||||
}).not.toThrowError();
|
}).not.toThrowError();
|
||||||
|
|
|
@ -10,7 +10,7 @@ import React, { MouseEvent, useEffect } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Route, Router, Switch } from 'react-router-dom';
|
import { Route, Router, Switch } from 'react-router-dom';
|
||||||
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
|
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
|
||||||
import { AppMountParameters, CoreStart } from '../../../../../src/core/public';
|
import { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '../../../../../src/core/public';
|
||||||
import {
|
import {
|
||||||
KibanaContextProvider,
|
KibanaContextProvider,
|
||||||
RedirectAppLinks,
|
RedirectAppLinks,
|
||||||
|
@ -19,6 +19,7 @@ import { PluginContext } from '../context/plugin_context';
|
||||||
import { usePluginContext } from '../hooks/use_plugin_context';
|
import { usePluginContext } from '../hooks/use_plugin_context';
|
||||||
import { useRouteParams } from '../hooks/use_route_params';
|
import { useRouteParams } from '../hooks/use_route_params';
|
||||||
import { ObservabilityPublicPluginsStart } from '../plugin';
|
import { ObservabilityPublicPluginsStart } from '../plugin';
|
||||||
|
import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template';
|
||||||
import { HasDataContextProvider } from '../context/has_data_context';
|
import { HasDataContextProvider } from '../context/has_data_context';
|
||||||
import { Breadcrumbs, routes } from '../routes';
|
import { Breadcrumbs, routes } from '../routes';
|
||||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||||
|
@ -74,12 +75,14 @@ export const renderApp = ({
|
||||||
plugins,
|
plugins,
|
||||||
appMountParameters,
|
appMountParameters,
|
||||||
observabilityRuleTypeRegistry,
|
observabilityRuleTypeRegistry,
|
||||||
|
ObservabilityPageTemplate,
|
||||||
}: {
|
}: {
|
||||||
config: ConfigSchema;
|
config: ConfigSchema;
|
||||||
core: CoreStart;
|
core: CoreStart;
|
||||||
plugins: ObservabilityPublicPluginsStart;
|
plugins: ObservabilityPublicPluginsStart;
|
||||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||||
appMountParameters: AppMountParameters;
|
appMountParameters: AppMountParameters;
|
||||||
|
ObservabilityPageTemplate: React.ComponentType<LazyObservabilityPageTemplateProps>;
|
||||||
}) => {
|
}) => {
|
||||||
const { element, history } = appMountParameters;
|
const { element, history } = appMountParameters;
|
||||||
const i18nCore = core.i18n;
|
const i18nCore = core.i18n;
|
||||||
|
@ -92,15 +95,25 @@ export const renderApp = ({
|
||||||
links: [{ linkType: 'discuss', href: 'https://ela.st/observability-discuss' }],
|
links: [{ linkType: 'discuss', href: 'https://ela.st/observability-discuss' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ensure all divs are .kbnAppWrappers
|
||||||
|
element.classList.add(APP_WRAPPER_CLASS);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<KibanaContextProvider services={{ ...core, ...plugins, storage: new Storage(localStorage) }}>
|
<KibanaContextProvider services={{ ...core, ...plugins, storage: new Storage(localStorage) }}>
|
||||||
<PluginContext.Provider
|
<PluginContext.Provider
|
||||||
value={{ appMountParameters, config, core, plugins, observabilityRuleTypeRegistry }}
|
value={{
|
||||||
|
appMountParameters,
|
||||||
|
config,
|
||||||
|
core,
|
||||||
|
plugins,
|
||||||
|
observabilityRuleTypeRegistry,
|
||||||
|
ObservabilityPageTemplate,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<EuiThemeProvider darkMode={isDarkMode}>
|
<EuiThemeProvider darkMode={isDarkMode}>
|
||||||
<i18nCore.Context>
|
<i18nCore.Context>
|
||||||
<RedirectAppLinks application={core.application}>
|
<RedirectAppLinks application={core.application} className={APP_WRAPPER_CLASS}>
|
||||||
<HasDataContextProvider>
|
<HasDataContextProvider>
|
||||||
<App />
|
<App />
|
||||||
</HasDataContextProvider>
|
</HasDataContextProvider>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import React from 'react';
|
||||||
|
import { usePluginContext } from '../../../hooks/use_plugin_context';
|
||||||
|
import HeaderMenuPortal from '../../shared/header_menu_portal';
|
||||||
|
|
||||||
|
export function ObservabilityHeaderMenu(): React.ReactElement | null {
|
||||||
|
const {
|
||||||
|
appMountParameters: { setHeaderActionMenu },
|
||||||
|
core: {
|
||||||
|
http: {
|
||||||
|
basePath: { prepend },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = usePluginContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu}>
|
||||||
|
<EuiHeaderLinks>
|
||||||
|
<EuiHeaderLink
|
||||||
|
color="primary"
|
||||||
|
href={prepend('/app/home#/tutorial_directory/logging')}
|
||||||
|
iconType="indexOpen"
|
||||||
|
>
|
||||||
|
{addDataLinkText}
|
||||||
|
</EuiHeaderLink>
|
||||||
|
</EuiHeaderLinks>
|
||||||
|
</HeaderMenuPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDataLinkText = i18n.translate('xpack.observability.home.addData', {
|
||||||
|
defaultMessage: 'Add data',
|
||||||
|
});
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from '../../../utils/test_helper';
|
|
||||||
import { Header } from './';
|
|
||||||
|
|
||||||
describe('Header', () => {
|
|
||||||
it('renders', () => {
|
|
||||||
const { getByText, getByTestId } = render(<Header color="#fff" />);
|
|
||||||
expect(getByTestId('observability-logo')).toBeInTheDocument();
|
|
||||||
expect(getByText('Observability')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -5,81 +5,4 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
export * from './header_menu';
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiHeaderLink,
|
|
||||||
EuiHeaderLinks,
|
|
||||||
EuiIcon,
|
|
||||||
EuiSpacer,
|
|
||||||
EuiTitle,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { usePluginContext } from '../../../hooks/use_plugin_context';
|
|
||||||
import HeaderMenuPortal from '../../shared/header_menu_portal';
|
|
||||||
|
|
||||||
const Container = styled.div<{ color: string }>`
|
|
||||||
background: ${(props) => props.color};
|
|
||||||
border-bottom: ${(props) => props.theme.eui.euiBorderThin};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled.div<{ restrictWidth?: number }>`
|
|
||||||
width: 100%;
|
|
||||||
max-width: ${(props) => `${props.restrictWidth}px`};
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
color: string;
|
|
||||||
datePicker?: ReactNode;
|
|
||||||
restrictWidth?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Header({ color, datePicker = null, restrictWidth }: Props) {
|
|
||||||
const { appMountParameters, core } = usePluginContext();
|
|
||||||
const { setHeaderActionMenu } = appMountParameters;
|
|
||||||
const { prepend } = core.http.basePath;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container color={color}>
|
|
||||||
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu}>
|
|
||||||
<EuiHeaderLinks>
|
|
||||||
<EuiHeaderLink
|
|
||||||
color="primary"
|
|
||||||
href={prepend('/app/home#/tutorial_directory/logging')}
|
|
||||||
iconType="indexOpen"
|
|
||||||
>
|
|
||||||
{i18n.translate('xpack.observability.home.addData', { defaultMessage: 'Add data' })}
|
|
||||||
</EuiHeaderLink>
|
|
||||||
</EuiHeaderLinks>
|
|
||||||
</HeaderMenuPortal>
|
|
||||||
<Wrapper restrictWidth={restrictWidth}>
|
|
||||||
<EuiSpacer size="l" />
|
|
||||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFlexGroup>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiIcon type="logoObservability" size="xxl" data-test-subj="observability-logo" />
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false} style={{ alignSelf: 'center' }}>
|
|
||||||
<EuiTitle>
|
|
||||||
<h1>
|
|
||||||
{i18n.translate('xpack.observability.home.title', {
|
|
||||||
defaultMessage: 'Observability',
|
|
||||||
})}
|
|
||||||
</h1>
|
|
||||||
</EuiTitle>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>{datePicker}</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiSpacer size="l" />
|
|
||||||
</Wrapper>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { EuiPage, EuiPageBody, EuiPageProps } from '@elastic/eui';
|
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Header } from '../header/index';
|
|
||||||
|
|
||||||
const Page = styled(EuiPage)<EuiPageProps>`
|
|
||||||
background: transparent;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled.div<{ color?: string }>`
|
|
||||||
overflow-y: hidden;
|
|
||||||
min-height: calc(
|
|
||||||
100vh - ${(props) => props.theme.eui.euiHeaderHeight + props.theme.eui.euiHeaderHeight}
|
|
||||||
);
|
|
||||||
background: ${(props) => props.color};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
datePicker?: ReactNode;
|
|
||||||
headerColor: string;
|
|
||||||
bodyColor: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
restrictWidth?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WithHeaderLayout({
|
|
||||||
datePicker,
|
|
||||||
headerColor,
|
|
||||||
bodyColor,
|
|
||||||
children,
|
|
||||||
restrictWidth,
|
|
||||||
}: Props) {
|
|
||||||
return (
|
|
||||||
<Container color={bodyColor}>
|
|
||||||
<Header color={headerColor} datePicker={datePicker} restrictWidth={restrictWidth} />
|
|
||||||
<Page restrictWidth={restrictWidth}>
|
|
||||||
<EuiPageBody>{children}</EuiPageBody>
|
|
||||||
</Page>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@ import { HasDataContextValue } from '../../../../context/has_data_context';
|
||||||
import { AppMountParameters, CoreStart } from 'kibana/public';
|
import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||||
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
|
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
|
||||||
import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock';
|
import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock';
|
||||||
|
import { KibanaPageTemplate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
useLocation: () => ({
|
useLocation: () => ({
|
||||||
|
@ -57,6 +58,7 @@ describe('APMSection', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown) as ObservabilityPublicPluginsStart,
|
} as unknown) as ObservabilityPublicPluginsStart,
|
||||||
|
ObservabilityPageTemplate: KibanaPageTemplate,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { render } from '../../../../utils/test_helper';
|
||||||
import { UXSection } from './';
|
import { UXSection } from './';
|
||||||
import { response } from './mock_data/ux.mock';
|
import { response } from './mock_data/ux.mock';
|
||||||
import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock';
|
import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock';
|
||||||
|
import { KibanaPageTemplate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
useLocation: () => ({
|
useLocation: () => ({
|
||||||
|
@ -56,6 +57,7 @@ describe('UXSection', () => {
|
||||||
},
|
},
|
||||||
} as unknown) as ObservabilityPublicPluginsStart,
|
} as unknown) as ObservabilityPublicPluginsStart,
|
||||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||||
|
ObservabilityPageTemplate: KibanaPageTemplate,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
it('renders with core web vitals', () => {
|
it('renders with core web vitals', () => {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import React, { lazy, Suspense } from 'react';
|
||||||
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
|
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
|
||||||
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
|
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
|
||||||
|
|
||||||
|
export { createLazyObservabilityPageTemplate } from './page_template';
|
||||||
|
|
||||||
const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index'));
|
const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index'));
|
||||||
|
|
||||||
export function getCoreVitalsComponent(props: CoreVitalProps) {
|
export function getCoreVitalsComponent(props: CoreVitalProps) {
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Observability solutions can register their navigation structures via the Observability plugin, this ensures that these navigation options display in the Observability page template component. This is a two part process, A) register your navigation structure and B) consume and render the shared page template component. These two elements are documented below.
|
||||||
|
|
||||||
|
## Navigation registration
|
||||||
|
|
||||||
|
To register a solution's navigation structure you'll first need to ensure your solution has the observability plugin specified as a dependency in your `kibana.json` file, e.g.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"requiredPlugins": [
|
||||||
|
"observability"
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
Now within your solution's **public** plugin `setup` lifecycle method you can call the `registerSections` method, this will register your solution's specific navigation structure with the overall Observability navigation registry. E.g.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// x-pack/plugins/example_plugin/public/plugin.ts
|
||||||
|
|
||||||
|
export class Plugin implements PluginClass {
|
||||||
|
constructor(_context: PluginInitializerContext) {}
|
||||||
|
|
||||||
|
setup(core: CoreSetup, plugins: PluginsSetup) {
|
||||||
|
plugins.observability.navigation.registerSections(
|
||||||
|
of([
|
||||||
|
{
|
||||||
|
label: 'A solution section',
|
||||||
|
sortKey: 200,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Example Page', app: 'exampleA', path: '/example' },
|
||||||
|
{ label: 'Another Example Page', app: 'exampleA', path: '/another-example' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Another solution section',
|
||||||
|
sortKey: 300,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Example page', app: 'exampleB', path: '/example' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {}
|
||||||
|
|
||||||
|
stop() {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here `app` would match your solution - e.g. logs, metrics, APM, uptime etc. The registry is fully typed so please refer to the types for specific options.
|
||||||
|
|
||||||
|
Observables are used to facilitate changes over time, for example within the lifetime of your application a license type or set of user permissions may change and as such you may wish to change the navigation structure. If your navigation needs are simple you can pass a value and forget about it. **Solutions are expected to handle their own permissions, and what should or should not be displayed at any time**, the Observability plugin will not add and remove items for you.
|
||||||
|
|
||||||
|
The Observability navigation registry is now aware of your solution's navigation needs ✅
|
||||||
|
|
||||||
|
## Page template component
|
||||||
|
|
||||||
|
The shared page template component can be used to actually display and render all of the registered navigation structures within your solution.
|
||||||
|
|
||||||
|
The `start` contract of the public Observability plugin exposes a React component, under `navigation.PageTemplate`.
|
||||||
|
|
||||||
|
This can be accessed like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||||
|
const pageTemplateComponent = pluginsStart.observability.navigation.PageTemplate;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that you have access to the component you can render your solution's content using it.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<ObservabilityPageTemplate
|
||||||
|
pageHeader={{
|
||||||
|
pageTitle: SolutionPageTitle,
|
||||||
|
rightSideItems: [
|
||||||
|
// Just an example
|
||||||
|
<DatePicker
|
||||||
|
rangeFrom={relativeTime.start}
|
||||||
|
rangeTo={relativeTime.end}
|
||||||
|
refreshInterval={refreshInterval}
|
||||||
|
refreshPaused={refreshPaused}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
// Render anything you like here, this is just an example.
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem>
|
||||||
|
// Content
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem>
|
||||||
|
// Content
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</ObservabilityPageTemplate>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `<ObservabilityPageTemplate />` component is a wrapper around the `<KibanaPageTemplate />` component (which in turn is a wrapper around the `<EuiPageTemplate>` component). As such the props mostly reflect those available on the wrapped components, again everything is fully typed so please refer to the types for specific options. The `pageSideBar` prop is handled by the component, and will take care of rendering out and managing the items from the registry.
|
||||||
|
|
||||||
|
After these two steps we should see something like the following (note the navigation on the left):
|
||||||
|
|
||||||
|
![Page template rendered example](./page_template.png)
|
|
@ -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 { createLazyObservabilityPageTemplate } from './lazy_page_template';
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type {
|
||||||
|
ObservabilityPageTemplateDependencies,
|
||||||
|
WrappedPageTemplateProps,
|
||||||
|
} from './page_template';
|
||||||
|
|
||||||
|
export const LazyObservabilityPageTemplate = React.lazy(() => import('./page_template'));
|
||||||
|
|
||||||
|
export type LazyObservabilityPageTemplateProps = WrappedPageTemplateProps;
|
||||||
|
|
||||||
|
export function createLazyObservabilityPageTemplate(
|
||||||
|
injectedDeps: ObservabilityPageTemplateDependencies
|
||||||
|
) {
|
||||||
|
return (pageTemplateProps: LazyObservabilityPageTemplateProps) => (
|
||||||
|
<React.Suspense fallback={null}>
|
||||||
|
<LazyObservabilityPageTemplate {...pageTemplateProps} {...injectedDeps} />
|
||||||
|
</React.Suspense>
|
||||||
|
);
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 304 KiB |
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { I18nProvider } from '@kbn/i18n/react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { createNavigationRegistry } from '../../../services/navigation_registry';
|
||||||
|
import { createLazyObservabilityPageTemplate } from './lazy_page_template';
|
||||||
|
import { ObservabilityPageTemplate } from './page_template';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useLocation: () => ({
|
||||||
|
pathname: '/test-path',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const navigationRegistry = createNavigationRegistry();
|
||||||
|
|
||||||
|
navigationRegistry.registerSections(
|
||||||
|
of([
|
||||||
|
{
|
||||||
|
label: 'Test A',
|
||||||
|
sortKey: 100,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Section A Url A', app: 'TestA', path: '/url-a' },
|
||||||
|
{ label: 'Section A Url B', app: 'TestA', path: '/url-b' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Test B',
|
||||||
|
sortKey: 200,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Section B Url A', app: 'TestB', path: '/url-a' },
|
||||||
|
{ label: 'Section B Url B', app: 'TestB', path: '/url-b' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Page template', () => {
|
||||||
|
it('Provides a working lazy wrapper', () => {
|
||||||
|
const LazyObservabilityPageTemplate = createLazyObservabilityPageTemplate({
|
||||||
|
currentAppId$: of('Test app ID'),
|
||||||
|
getUrlForApp: () => '/test-url',
|
||||||
|
navigateToApp: async () => {},
|
||||||
|
navigationSections$: navigationRegistry.sections$,
|
||||||
|
});
|
||||||
|
|
||||||
|
const component = shallow(
|
||||||
|
<LazyObservabilityPageTemplate
|
||||||
|
pageHeader={{
|
||||||
|
pageTitle: 'Test title',
|
||||||
|
rightSideItems: [<span>Test side item</span>],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Test structure</div>
|
||||||
|
</LazyObservabilityPageTemplate>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('lazy')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Utilises the KibanaPageTemplate for rendering', () => {
|
||||||
|
const component = shallow(
|
||||||
|
<ObservabilityPageTemplate
|
||||||
|
currentAppId$={of('Test app ID')}
|
||||||
|
getUrlForApp={() => '/test-url'}
|
||||||
|
navigateToApp={async () => {}}
|
||||||
|
navigationSections$={navigationRegistry.sections$}
|
||||||
|
pageHeader={{
|
||||||
|
pageTitle: 'Test title',
|
||||||
|
rightSideItems: [<span>Test side item</span>],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Test structure</div>
|
||||||
|
</ObservabilityPageTemplate>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.is('KibanaPageTemplate'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles outputting the registered navigation structures within a side nav', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<I18nProvider>
|
||||||
|
<ObservabilityPageTemplate
|
||||||
|
currentAppId$={of('Test app ID')}
|
||||||
|
getUrlForApp={() => '/test-url'}
|
||||||
|
navigateToApp={async () => {}}
|
||||||
|
navigationSections$={navigationRegistry.sections$}
|
||||||
|
pageHeader={{
|
||||||
|
pageTitle: 'Test title',
|
||||||
|
rightSideItems: [<span>Test side item</span>],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Test structure</div>
|
||||||
|
</ObservabilityPageTemplate>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toHaveTextContent('Section A Url A');
|
||||||
|
expect(container).toHaveTextContent('Section A Url B');
|
||||||
|
expect(container).toHaveTextContent('Section B Url A');
|
||||||
|
expect(container).toHaveTextContent('Section B Url B');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EuiSideNavItemType, ExclusiveUnion } from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { matchPath, useLocation } from 'react-router-dom';
|
||||||
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
|
import type { Observable } from 'rxjs';
|
||||||
|
import type { ApplicationStart } from '../../../../../../../src/core/public';
|
||||||
|
import {
|
||||||
|
KibanaPageTemplate,
|
||||||
|
KibanaPageTemplateProps,
|
||||||
|
} from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import type { NavigationSection } from '../../../services/navigation_registry';
|
||||||
|
|
||||||
|
export type WrappedPageTemplateProps = Pick<
|
||||||
|
KibanaPageTemplateProps,
|
||||||
|
| 'children'
|
||||||
|
| 'data-test-subj'
|
||||||
|
| 'paddingSize'
|
||||||
|
| 'pageBodyProps'
|
||||||
|
| 'pageContentBodyProps'
|
||||||
|
| 'pageContentProps'
|
||||||
|
| 'pageHeader'
|
||||||
|
| 'restrictWidth'
|
||||||
|
> &
|
||||||
|
// recreate the exclusivity of bottomBar-related props
|
||||||
|
ExclusiveUnion<
|
||||||
|
{ template?: 'default' } & Pick<KibanaPageTemplateProps, 'bottomBar' | 'bottomBarProps'>,
|
||||||
|
{ template: KibanaPageTemplateProps['template'] }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ObservabilityPageTemplateDependencies {
|
||||||
|
currentAppId$: Observable<string | undefined>;
|
||||||
|
getUrlForApp: ApplicationStart['getUrlForApp'];
|
||||||
|
navigateToApp: ApplicationStart['navigateToApp'];
|
||||||
|
navigationSections$: Observable<NavigationSection[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObservabilityPageTemplateProps = ObservabilityPageTemplateDependencies &
|
||||||
|
WrappedPageTemplateProps;
|
||||||
|
|
||||||
|
export function ObservabilityPageTemplate({
|
||||||
|
children,
|
||||||
|
currentAppId$,
|
||||||
|
getUrlForApp,
|
||||||
|
navigateToApp,
|
||||||
|
navigationSections$,
|
||||||
|
...pageTemplateProps
|
||||||
|
}: ObservabilityPageTemplateProps): React.ReactElement | null {
|
||||||
|
const sections = useObservable(navigationSections$, []);
|
||||||
|
const currentAppId = useObservable(currentAppId$, undefined);
|
||||||
|
const { pathname: currentPath } = useLocation();
|
||||||
|
|
||||||
|
const sideNavItems = useMemo<Array<EuiSideNavItemType<unknown>>>(
|
||||||
|
() =>
|
||||||
|
sections.map(({ label, entries }, sectionIndex) => ({
|
||||||
|
id: `${sectionIndex}`,
|
||||||
|
name: label,
|
||||||
|
items: entries.map((entry, entryIndex) => {
|
||||||
|
const href = getUrlForApp(entry.app, {
|
||||||
|
path: entry.path,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSelected =
|
||||||
|
entry.app === currentAppId &&
|
||||||
|
matchPath(currentPath, {
|
||||||
|
path: entry.path,
|
||||||
|
}) != null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${sectionIndex}.${entryIndex}`,
|
||||||
|
name: entry.label,
|
||||||
|
href,
|
||||||
|
isSelected,
|
||||||
|
onClick: (event) => {
|
||||||
|
if (
|
||||||
|
event.button !== 0 ||
|
||||||
|
event.defaultPrevented ||
|
||||||
|
event.metaKey ||
|
||||||
|
event.altKey ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
event.shiftKey
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
navigateToApp(entry.app, {
|
||||||
|
path: entry.path,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
[currentAppId, currentPath, getUrlForApp, navigateToApp, sections]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KibanaPageTemplate
|
||||||
|
restrictWidth={false}
|
||||||
|
{...pageTemplateProps}
|
||||||
|
solutionNav={{
|
||||||
|
icon: 'logoObservability',
|
||||||
|
items: sideNavItems,
|
||||||
|
name: sideNavTitle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</KibanaPageTemplate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lazy import
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default ObservabilityPageTemplate;
|
||||||
|
|
||||||
|
const sideNavTitle = i18n.translate('xpack.observability.pageLayout.sideNavTitle', {
|
||||||
|
defaultMessage: 'Observability',
|
||||||
|
});
|
|
@ -5,11 +5,12 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext } from 'react';
|
|
||||||
import { AppMountParameters, CoreStart } from 'kibana/public';
|
import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||||
|
import { createContext } from 'react';
|
||||||
import { ObservabilityPublicPluginsStart } from '../plugin';
|
import { ObservabilityPublicPluginsStart } from '../plugin';
|
||||||
import { ConfigSchema } from '..';
|
import { ConfigSchema } from '..';
|
||||||
import { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry';
|
import { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry';
|
||||||
|
import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template';
|
||||||
|
|
||||||
export interface PluginContextValue {
|
export interface PluginContextValue {
|
||||||
appMountParameters: AppMountParameters;
|
appMountParameters: AppMountParameters;
|
||||||
|
@ -17,6 +18,7 @@ export interface PluginContextValue {
|
||||||
core: CoreStart;
|
core: CoreStart;
|
||||||
plugins: ObservabilityPublicPluginsStart;
|
plugins: ObservabilityPublicPluginsStart;
|
||||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||||
|
ObservabilityPageTemplate: React.ComponentType<LazyObservabilityPageTemplateProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PluginContext = createContext({} as PluginContextValue);
|
export const PluginContext = createContext({} as PluginContextValue);
|
||||||
|
|
|
@ -40,6 +40,7 @@ describe('useTimeRange', () => {
|
||||||
},
|
},
|
||||||
} as unknown) as ObservabilityPublicPluginsStart,
|
} as unknown) as ObservabilityPublicPluginsStart,
|
||||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||||
|
ObservabilityPageTemplate: () => null,
|
||||||
}));
|
}));
|
||||||
jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({
|
jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({
|
||||||
from: '2020-10-08T05:00:00.000Z',
|
from: '2020-10-08T05:00:00.000Z',
|
||||||
|
@ -82,6 +83,7 @@ describe('useTimeRange', () => {
|
||||||
},
|
},
|
||||||
} as unknown) as ObservabilityPublicPluginsStart,
|
} as unknown) as ObservabilityPublicPluginsStart,
|
||||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||||
|
ObservabilityPageTemplate: () => null,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
it('returns ranges and absolute times from kibana default settings', () => {
|
it('returns ranges and absolute times from kibana default settings', () => {
|
||||||
|
|
|
@ -5,15 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||||
EuiButton,
|
|
||||||
EuiCallOut,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiLink,
|
|
||||||
EuiPageTemplate,
|
|
||||||
EuiSpacer,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import {
|
import {
|
||||||
ALERT_START,
|
ALERT_START,
|
||||||
|
@ -56,7 +48,7 @@ interface AlertsPageProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertsPage({ routeParams }: AlertsPageProps) {
|
export function AlertsPage({ routeParams }: AlertsPageProps) {
|
||||||
const { core, observabilityRuleTypeRegistry } = usePluginContext();
|
const { core, observabilityRuleTypeRegistry, ObservabilityPageTemplate } = usePluginContext();
|
||||||
const { prepend } = core.http.basePath;
|
const { prepend } = core.http.basePath;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const {
|
const {
|
||||||
|
@ -131,7 +123,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageTemplate
|
<ObservabilityPageTemplate
|
||||||
pageHeader={{
|
pageHeader={{
|
||||||
pageTitle: (
|
pageTitle: (
|
||||||
<>
|
<>
|
||||||
|
@ -139,7 +131,6 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
|
||||||
<ExperimentalBadge />
|
<ExperimentalBadge />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|
||||||
rightSideItems: [
|
rightSideItems: [
|
||||||
<EuiButton fill href={manageDetectionRulesHref} iconType="gear">
|
<EuiButton fill href={manageDetectionRulesHref} iconType="gear">
|
||||||
{i18n.translate('xpack.observability.alerts.manageDetectionRulesButtonLabel', {
|
{i18n.translate('xpack.observability.alerts.manageDetectionRulesButtonLabel', {
|
||||||
|
@ -206,6 +197,6 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiPageTemplate>
|
</ObservabilityPageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,21 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiPageTemplate } from '@elastic/eui';
|
import { EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ExperimentalBadge } from '../../components/shared/experimental_badge';
|
import { ExperimentalBadge } from '../../components/shared/experimental_badge';
|
||||||
import { RouteParams } from '../../routes';
|
import { RouteParams } from '../../routes';
|
||||||
|
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||||
|
|
||||||
interface CasesProps {
|
interface CasesProps {
|
||||||
routeParams: RouteParams<'/cases'>;
|
routeParams: RouteParams<'/cases'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CasesPage(props: CasesProps) {
|
export function CasesPage(props: CasesProps) {
|
||||||
|
const { ObservabilityPageTemplate } = usePluginContext();
|
||||||
return (
|
return (
|
||||||
<EuiPageTemplate
|
<ObservabilityPageTemplate
|
||||||
pageHeader={{
|
pageHeader={{
|
||||||
pageTitle: (
|
pageTitle: (
|
||||||
<>
|
<>
|
||||||
|
@ -44,6 +46,6 @@ export function CasesPage(props: CasesProps) {
|
||||||
</EuiCallOut>
|
</EuiCallOut>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiPageTemplate>
|
</ObservabilityPageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import styled, { ThemeContext } from 'styled-components';
|
import styled, { ThemeContext } from 'styled-components';
|
||||||
import { FleetPanel } from '../../components/app/fleet_panel';
|
import { FleetPanel } from '../../components/app/fleet_panel';
|
||||||
import { WithHeaderLayout } from '../../components/app/layout/with_header';
|
import { ObservabilityHeaderMenu } from '../../components/app/header';
|
||||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||||
import { useTrackPageview } from '../../hooks/use_track_metric';
|
import { useTrackPageview } from '../../hooks/use_track_metric';
|
||||||
import { appsSection } from '../home/section';
|
import { appsSection } from '../home/section';
|
||||||
|
@ -34,15 +34,12 @@ export function LandingPage() {
|
||||||
useTrackPageview({ app: 'observability-overview', path: 'landing' });
|
useTrackPageview({ app: 'observability-overview', path: 'landing' });
|
||||||
useTrackPageview({ app: 'observability-overview', path: 'landing', delay: 15000 });
|
useTrackPageview({ app: 'observability-overview', path: 'landing', delay: 15000 });
|
||||||
|
|
||||||
const { core } = usePluginContext();
|
const { core, ObservabilityPageTemplate } = usePluginContext();
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useContext(ThemeContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithHeaderLayout
|
<ObservabilityPageTemplate restrictWidth={1200}>
|
||||||
restrictWidth={1200}
|
<ObservabilityHeaderMenu />
|
||||||
headerColor={theme.eui.euiPageBackgroundColor}
|
|
||||||
bodyColor={theme.eui.euiColorEmptyShade}
|
|
||||||
>
|
|
||||||
<EuiFlexGroup direction="column">
|
<EuiFlexGroup direction="column">
|
||||||
{/* title and description */}
|
{/* title and description */}
|
||||||
<EuiFlexItem className="obsLanding__title">
|
<EuiFlexItem className="obsLanding__title">
|
||||||
|
@ -128,6 +125,6 @@ export function LandingPage() {
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</WithHeaderLayout>
|
</ObservabilityPageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import React, { useContext } from 'react';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { ThemeContext } from 'styled-components';
|
import React from 'react';
|
||||||
import { useTrackPageview } from '../..';
|
import { useTrackPageview } from '../..';
|
||||||
import { Alert } from '../../../../alerting/common';
|
import { Alert } from '../../../../alerting/common';
|
||||||
import { EmptySections } from '../../components/app/empty_sections';
|
import { EmptySections } from '../../components/app/empty_sections';
|
||||||
import { WithHeaderLayout } from '../../components/app/layout/with_header';
|
import { ObservabilityHeaderMenu } from '../../components/app/header';
|
||||||
import { NewsFeed } from '../../components/app/news_feed';
|
import { NewsFeed } from '../../components/app/news_feed';
|
||||||
import { Resources } from '../../components/app/resources';
|
import { Resources } from '../../components/app/resources';
|
||||||
import { AlertsSection } from '../../components/app/section/alerts';
|
import { AlertsSection } from '../../components/app/section/alerts';
|
||||||
|
@ -39,8 +39,7 @@ function calculateBucketSize({ start, end }: { start?: number; end?: number }) {
|
||||||
export function OverviewPage({ routeParams }: Props) {
|
export function OverviewPage({ routeParams }: Props) {
|
||||||
useTrackPageview({ app: 'observability-overview', path: 'overview' });
|
useTrackPageview({ app: 'observability-overview', path: 'overview' });
|
||||||
useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 });
|
useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 });
|
||||||
const { core } = usePluginContext();
|
const { core, ObservabilityPageTemplate } = usePluginContext();
|
||||||
const theme = useContext(ThemeContext);
|
|
||||||
|
|
||||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||||
|
|
||||||
|
@ -65,18 +64,20 @@ export function OverviewPage({ routeParams }: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithHeaderLayout
|
<ObservabilityPageTemplate
|
||||||
headerColor={theme.eui.euiColorEmptyShade}
|
pageHeader={{
|
||||||
bodyColor={theme.eui.euiPageBackgroundColor}
|
pageTitle: overviewPageTitle,
|
||||||
datePicker={
|
rightSideItems: [
|
||||||
<DatePicker
|
<DatePicker
|
||||||
rangeFrom={relativeTime.start}
|
rangeFrom={relativeTime.start}
|
||||||
rangeTo={relativeTime.end}
|
rangeTo={relativeTime.end}
|
||||||
refreshInterval={refreshInterval}
|
refreshInterval={refreshInterval}
|
||||||
refreshPaused={refreshPaused}
|
refreshPaused={refreshPaused}
|
||||||
/>
|
/>,
|
||||||
}
|
],
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<ObservabilityHeaderMenu />
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem grow={6}>
|
<EuiFlexItem grow={6}>
|
||||||
{/* Data sections */}
|
{/* Data sections */}
|
||||||
|
@ -107,6 +108,10 @@ export function OverviewPage({ routeParams }: Props) {
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</WithHeaderLayout>
|
</ObservabilityPageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', {
|
||||||
|
defaultMessage: 'Overview',
|
||||||
|
});
|
||||||
|
|
|
@ -5,49 +5,33 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiText } from '@elastic/eui';
|
||||||
import styled, { ThemeContext } from 'styled-components';
|
|
||||||
import { EuiFlexItem } from '@elastic/eui';
|
|
||||||
import { EuiPanel } from '@elastic/eui';
|
|
||||||
import { EuiFlexGroup } from '@elastic/eui';
|
|
||||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
|
||||||
import { EuiText } from '@elastic/eui';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { WithHeaderLayout } from '../../components/app/layout/with_header';
|
import React from 'react';
|
||||||
|
import { ObservabilityHeaderMenu } from '../../components/app/header';
|
||||||
const CentralizedFlexGroup = styled(EuiFlexGroup)`
|
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
// place the element in the center of the page
|
|
||||||
min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize});
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function LoadingObservability() {
|
export function LoadingObservability() {
|
||||||
const theme = useContext(ThemeContext);
|
const { ObservabilityPageTemplate } = usePluginContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithHeaderLayout
|
<ObservabilityPageTemplate template="centeredContent">
|
||||||
headerColor={theme.eui.euiColorEmptyShade}
|
<ObservabilityHeaderMenu />
|
||||||
bodyColor={theme.eui.euiPageBackgroundColor}
|
<EuiFlexGroup>
|
||||||
>
|
|
||||||
<CentralizedFlexGroup>
|
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiPanel>
|
<EuiLoadingSpinner size="xl" />
|
||||||
<EuiFlexGroup>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiLoadingSpinner size="xl" />
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
|
|
||||||
<EuiText>
|
|
||||||
{i18n.translate('xpack.observability.overview.loadingObservability', {
|
|
||||||
defaultMessage: 'Loading Observability',
|
|
||||||
})}
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiPanel>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</CentralizedFlexGroup>
|
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
|
||||||
</WithHeaderLayout>
|
<EuiText>{observabilityLoadingMessage}</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</ObservabilityPageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const observabilityLoadingMessage = i18n.translate(
|
||||||
|
'xpack.observability.overview.loadingObservability',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Loading Observability',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { emptyResponse as emptyMetricsResponse, fetchMetricsData } from './mock/
|
||||||
import { newsFeedFetchData } from './mock/news_feed.mock';
|
import { newsFeedFetchData } from './mock/news_feed.mock';
|
||||||
import { emptyResponse as emptyUptimeResponse, fetchUptimeData } from './mock/uptime.mock';
|
import { emptyResponse as emptyUptimeResponse, fetchUptimeData } from './mock/uptime.mock';
|
||||||
import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock';
|
import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock';
|
||||||
|
import { KibanaPageTemplate } from '../../../../../../src/plugins/kibana_react/public';
|
||||||
|
|
||||||
function unregisterAll() {
|
function unregisterAll() {
|
||||||
unregisterDataHandler({ appName: 'apm' });
|
unregisterDataHandler({ appName: 'apm' });
|
||||||
|
@ -55,6 +56,7 @@ const withCore = makeDecorator({
|
||||||
},
|
},
|
||||||
} as unknown) as ObservabilityPublicPluginsStart,
|
} as unknown) as ObservabilityPublicPluginsStart,
|
||||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||||
|
ObservabilityPageTemplate: KibanaPageTemplate,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EuiThemeProvider>
|
<EuiThemeProvider>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, of } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
TriggersAndActionsUIPublicPluginSetup,
|
TriggersAndActionsUIPublicPluginSetup,
|
||||||
TriggersAndActionsUIPublicPluginStart,
|
TriggersAndActionsUIPublicPluginStart,
|
||||||
|
@ -31,9 +31,11 @@ import type {
|
||||||
import type { LensPublicStart } from '../../lens/public';
|
import type { LensPublicStart } from '../../lens/public';
|
||||||
import { registerDataHandler } from './data_handler';
|
import { registerDataHandler } from './data_handler';
|
||||||
import { createCallObservabilityApi } from './services/call_observability_api';
|
import { createCallObservabilityApi } from './services/call_observability_api';
|
||||||
|
import { createNavigationRegistry } from './services/navigation_registry';
|
||||||
import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav';
|
import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav';
|
||||||
import { ConfigSchema } from '.';
|
import { ConfigSchema } from '.';
|
||||||
import { createObservabilityRuleTypeRegistry } from './rules/create_observability_rule_type_registry';
|
import { createObservabilityRuleTypeRegistry } from './rules/create_observability_rule_type_registry';
|
||||||
|
import { createLazyObservabilityPageTemplate } from './components/shared';
|
||||||
|
|
||||||
export type ObservabilityPublicSetup = ReturnType<Plugin['setup']>;
|
export type ObservabilityPublicSetup = ReturnType<Plugin['setup']>;
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ export interface ObservabilityPublicPluginsStart {
|
||||||
lens: LensPublicStart;
|
lens: LensPublicStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ObservabilityPublicStart = void;
|
export type ObservabilityPublicStart = ReturnType<Plugin['start']>;
|
||||||
|
|
||||||
export class Plugin
|
export class Plugin
|
||||||
implements
|
implements
|
||||||
|
@ -61,13 +63,14 @@ export class Plugin
|
||||||
ObservabilityPublicPluginsStart
|
ObservabilityPublicPluginsStart
|
||||||
> {
|
> {
|
||||||
private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
|
private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
|
||||||
|
private readonly navigationRegistry = createNavigationRegistry();
|
||||||
|
|
||||||
constructor(private readonly initializerContext: PluginInitializerContext<ConfigSchema>) {
|
constructor(private readonly initializerContext: PluginInitializerContext<ConfigSchema>) {
|
||||||
this.initializerContext = initializerContext;
|
this.initializerContext = initializerContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup(
|
public setup(
|
||||||
coreSetup: CoreSetup<ObservabilityPublicPluginsStart>,
|
coreSetup: CoreSetup<ObservabilityPublicPluginsStart, ObservabilityPublicStart>,
|
||||||
pluginsSetup: ObservabilityPublicPluginsSetup
|
pluginsSetup: ObservabilityPublicPluginsSetup
|
||||||
) {
|
) {
|
||||||
const category = DEFAULT_APP_CATEGORIES.observability;
|
const category = DEFAULT_APP_CATEGORIES.observability;
|
||||||
|
@ -84,7 +87,7 @@ export class Plugin
|
||||||
// Load application bundle
|
// Load application bundle
|
||||||
const { renderApp } = await import('./application');
|
const { renderApp } = await import('./application');
|
||||||
// Get start services
|
// Get start services
|
||||||
const [coreStart, pluginsStart] = await coreSetup.getStartServices();
|
const [coreStart, pluginsStart, { navigation }] = await coreSetup.getStartServices();
|
||||||
|
|
||||||
return renderApp({
|
return renderApp({
|
||||||
config,
|
config,
|
||||||
|
@ -92,6 +95,7 @@ export class Plugin
|
||||||
plugins: pluginsStart,
|
plugins: pluginsStart,
|
||||||
appMountParameters: params,
|
appMountParameters: params,
|
||||||
observabilityRuleTypeRegistry,
|
observabilityRuleTypeRegistry,
|
||||||
|
ObservabilityPageTemplate: navigation.PageTemplate,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,13 +168,39 @@ export class Plugin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.navigationRegistry.registerSections(
|
||||||
|
of([
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
sortKey: 100,
|
||||||
|
entries: [{ label: 'Overview', app: 'observability-overview', path: '/overview' }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dashboard: { register: registerDataHandler },
|
dashboard: { register: registerDataHandler },
|
||||||
observabilityRuleTypeRegistry,
|
observabilityRuleTypeRegistry,
|
||||||
isAlertingExperienceEnabled: () => config.unsafe.alertingExperience.enabled,
|
isAlertingExperienceEnabled: () => config.unsafe.alertingExperience.enabled,
|
||||||
|
navigation: {
|
||||||
|
registerSections: this.navigationRegistry.registerSections,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public start({ application }: CoreStart) {
|
public start({ application }: CoreStart) {
|
||||||
toggleOverviewLinkInNav(this.appUpdater$, application);
|
toggleOverviewLinkInNav(this.appUpdater$, application);
|
||||||
|
|
||||||
|
const PageTemplate = createLazyObservabilityPageTemplate({
|
||||||
|
currentAppId$: application.currentAppId$,
|
||||||
|
getUrlForApp: application.getUrlForApp,
|
||||||
|
navigateToApp: application.navigateToApp,
|
||||||
|
navigationSections$: this.navigationRegistry.sections$,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigation: {
|
||||||
|
PageTemplate,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { firstValueFrom } from '@kbn/std';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { createNavigationRegistry } from './navigation_registry';
|
||||||
|
|
||||||
|
describe('Navigation registry', () => {
|
||||||
|
it('Allows the registration of, and access to, navigation sections', async () => {
|
||||||
|
const navigationRegistry = createNavigationRegistry();
|
||||||
|
|
||||||
|
navigationRegistry.registerSections(
|
||||||
|
of([
|
||||||
|
{
|
||||||
|
label: 'Test A',
|
||||||
|
sortKey: 100,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Url A', app: 'TestA', path: '/url-a' },
|
||||||
|
{ label: 'Url B', app: 'TestA', path: '/url-b' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Test B',
|
||||||
|
sortKey: 200,
|
||||||
|
entries: [
|
||||||
|
{ label: 'Url A', app: 'TestB', path: '/url-a' },
|
||||||
|
{ label: 'Url B', app: 'TestB', path: '/url-b' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const sections = await firstValueFrom(navigationRegistry.sections$);
|
||||||
|
|
||||||
|
expect(sections).toEqual([
|
||||||
|
{
|
||||||
|
label: 'Test A',
|
||||||
|
sortKey: 100,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
label: 'Url A',
|
||||||
|
app: 'TestA',
|
||||||
|
path: '/url-a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Url B',
|
||||||
|
app: 'TestA',
|
||||||
|
path: '/url-b',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Test B',
|
||||||
|
sortKey: 200,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
label: 'Url A',
|
||||||
|
app: 'TestB',
|
||||||
|
path: '/url-a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Url B',
|
||||||
|
app: 'TestB',
|
||||||
|
path: '/url-b',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { combineLatest, Observable, ReplaySubject } from 'rxjs';
|
||||||
|
import { map, scan, shareReplay, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
export interface NavigationSection {
|
||||||
|
label: string | undefined;
|
||||||
|
sortKey: number;
|
||||||
|
entries: NavigationEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigationEntry {
|
||||||
|
label: string;
|
||||||
|
app: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigationRegistry {
|
||||||
|
registerSections: (sections$: Observable<NavigationSection[]>) => void;
|
||||||
|
sections$: Observable<NavigationSection[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNavigationRegistry = (): NavigationRegistry => {
|
||||||
|
const registeredSections$ = new ReplaySubject<Observable<NavigationSection[]>>();
|
||||||
|
|
||||||
|
const registerSections = (sections$: Observable<NavigationSection[]>) => {
|
||||||
|
registeredSections$.next(sections$);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sections$: Observable<NavigationSection[]> = registeredSections$.pipe(
|
||||||
|
scan(
|
||||||
|
(accumulatedSections$, newSections) => accumulatedSections$.add(newSections),
|
||||||
|
new Set<Observable<NavigationSection[]>>()
|
||||||
|
),
|
||||||
|
switchMap((registeredSections) => combineLatest([...registeredSections])),
|
||||||
|
map((registeredSections) =>
|
||||||
|
registeredSections.flat().sort((first, second) => first.sortKey - second.sortKey)
|
||||||
|
),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
registerSections,
|
||||||
|
sections$,
|
||||||
|
};
|
||||||
|
};
|
|
@ -10,7 +10,10 @@ import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
import {
|
||||||
|
KibanaContextProvider,
|
||||||
|
KibanaPageTemplate,
|
||||||
|
} from '../../../../../src/plugins/kibana_react/public';
|
||||||
import translations from '../../../translations/translations/ja-JP.json';
|
import translations from '../../../translations/translations/ja-JP.json';
|
||||||
import { PluginContext } from '../context/plugin_context';
|
import { PluginContext } from '../context/plugin_context';
|
||||||
import { ObservabilityPublicPluginsStart } from '../plugin';
|
import { ObservabilityPublicPluginsStart } from '../plugin';
|
||||||
|
@ -44,7 +47,14 @@ export const render = (component: React.ReactNode) => {
|
||||||
<IntlProvider locale="en-US" messages={translations.messages}>
|
<IntlProvider locale="en-US" messages={translations.messages}>
|
||||||
<KibanaContextProvider services={{ ...core }}>
|
<KibanaContextProvider services={{ ...core }}>
|
||||||
<PluginContext.Provider
|
<PluginContext.Provider
|
||||||
value={{ appMountParameters, config, core, plugins, observabilityRuleTypeRegistry }}
|
value={{
|
||||||
|
appMountParameters,
|
||||||
|
config,
|
||||||
|
core,
|
||||||
|
plugins,
|
||||||
|
observabilityRuleTypeRegistry,
|
||||||
|
ObservabilityPageTemplate: KibanaPageTemplate,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<EuiThemeProvider>{component}</EuiThemeProvider>
|
<EuiThemeProvider>{component}</EuiThemeProvider>
|
||||||
</PluginContext.Provider>
|
</PluginContext.Provider>
|
||||||
|
|
|
@ -17657,7 +17657,6 @@
|
||||||
"xpack.observability.home.getStatedButton": "使ってみる",
|
"xpack.observability.home.getStatedButton": "使ってみる",
|
||||||
"xpack.observability.home.sectionsubtitle": "ログ、メトリック、トレースを大規模に、1つのスタックにまとめて、環境内のあらゆる場所で生じるイベントの監視、分析、対応を行います。",
|
"xpack.observability.home.sectionsubtitle": "ログ、メトリック、トレースを大規模に、1つのスタックにまとめて、環境内のあらゆる場所で生じるイベントの監視、分析、対応を行います。",
|
||||||
"xpack.observability.home.sectionTitle": "エコシステム全体の一元的な可視性",
|
"xpack.observability.home.sectionTitle": "エコシステム全体の一元的な可視性",
|
||||||
"xpack.observability.home.title": "オブザーバビリティ",
|
|
||||||
"xpack.observability.landing.breadcrumb": "はじめて使う",
|
"xpack.observability.landing.breadcrumb": "はじめて使う",
|
||||||
"xpack.observability.news.readFullStory": "詳細なストーリーを読む",
|
"xpack.observability.news.readFullStory": "詳細なストーリーを読む",
|
||||||
"xpack.observability.news.title": "新機能",
|
"xpack.observability.news.title": "新機能",
|
||||||
|
|
|
@ -17894,7 +17894,6 @@
|
||||||
"xpack.observability.home.getStatedButton": "开始使用",
|
"xpack.observability.home.getStatedButton": "开始使用",
|
||||||
"xpack.observability.home.sectionsubtitle": "通过根据需要将日志、指标和跟踪都置于单个堆栈上,来监测、分析和响应环境中任何位置发生的事件。",
|
"xpack.observability.home.sectionsubtitle": "通过根据需要将日志、指标和跟踪都置于单个堆栈上,来监测、分析和响应环境中任何位置发生的事件。",
|
||||||
"xpack.observability.home.sectionTitle": "整个生态系统的统一可见性",
|
"xpack.observability.home.sectionTitle": "整个生态系统的统一可见性",
|
||||||
"xpack.observability.home.title": "可观测性",
|
|
||||||
"xpack.observability.landing.breadcrumb": "入门",
|
"xpack.observability.landing.breadcrumb": "入门",
|
||||||
"xpack.observability.news.readFullStory": "详细了解",
|
"xpack.observability.news.readFullStory": "详细了解",
|
||||||
"xpack.observability.news.title": "最新动态",
|
"xpack.observability.news.title": "最新动态",
|
||||||
|
|
Loading…
Reference in a new issue