Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Observability] Add Observability Shared app #154716

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ packages/kbn-object-versioning @elastic/appex-sharedux
x-pack/packages/observability/alert_details @elastic/actionable-observability
x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops
x-pack/plugins/observability @elastic/actionable-observability
x-pack/plugins/observability_shared @elastic/actionable-observability
x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security
test/common/plugins/otel_metrics @elastic/infra-monitoring-ui
packages/kbn-optimizer @elastic/kibana-operations
Expand Down
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,10 @@ Elastic.
|This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI.


|{kib-repo}blob/{branch}/x-pack/plugins/observability_shared/README.md[observabilityShared]
|A plugin that contains components and utilities shared by all Observability plugins.


|{kib-repo}blob/{branch}/x-pack/plugins/osquery/README.md[osquery]
|This plugin adds extended support to Security Solution Fleet Osquery integration

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@
"@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details",
"@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability",
"@kbn/observability-plugin": "link:x-pack/plugins/observability",
"@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_shared",
"@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider",
"@kbn/open-telemetry-instrumented-plugin": "link:test/common/plugins/otel_metrics",
"@kbn/osquery-io-ts-types": "link:packages/kbn-osquery-io-ts-types",
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-babel-preset/styled_components_files.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = {
USES_STYLED_COMPONENTS: [
/packages[\/\\](kbn-ui-shared-deps-(npm|src)|kbn-ecs-data-quality-dashboard)[\/\\]/,
/src[\/\\]plugins[\/\\](kibana_react)[\/\\]/,
/x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|exploratory_view|osquery|security_solution|timelines|synthetics|ux)[\/\\]/,
/x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|observability_shared|exploratory_view|osquery|security_solution|timelines|synthetics|ux)[\/\\]/,
/x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/,
],
};
1 change: 1 addition & 0 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pageLoadAssetSize:
navigation: 37269
newsfeed: 42228
observability: 95000
observabilityShared: 21266
osquery: 107090
painlessLab: 179748
presentationUtil: 58834
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@
"@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"],
"@kbn/observability-plugin": ["x-pack/plugins/observability"],
"@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"],
"@kbn/observability-shared-plugin": ["x-pack/plugins/observability_shared"],
"@kbn/observability-shared-plugin/*": ["x-pack/plugins/observability_shared/*"],
"@kbn/oidc-provider-plugin": ["x-pack/test/security_api_integration/plugins/oidc_provider"],
"@kbn/oidc-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/oidc_provider/*"],
"@kbn/open-telemetry-instrumented-plugin": ["test/common/plugins/otel_metrics"],
Expand Down
11 changes: 6 additions & 5 deletions x-pack/.i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"xpack.stackAlerts": "plugins/stack_alerts",
"xpack.stackConnectors": "plugins/stack_connectors",
"xpack.apm": "plugins/apm",
"xpack.banners": "plugins/banners",
"xpack.canvas": "plugins/canvas",
"xpack.cases": "plugins/cases",
"xpack.cloud": "plugins/cloud",
Expand Down Expand Up @@ -47,9 +48,11 @@
"xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"],
"xpack.ml": ["packages/ml/date_picker", "packages/ml/trained_models_utils", "plugins/ml"],
"xpack.monitoring": ["plugins/monitoring"],
"xpack.observability": "plugins/observability",
"xpack.observabilityShared": "plugins/observability_shared",
"xpack.osquery": ["plugins/osquery"],
"xpack.painlessLab": "plugins/painless_lab",
"xpack.profiling": [ "plugins/profiling" ],
"xpack.profiling": ["plugins/profiling"],
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.reporting": ["plugins/reporting"],
"xpack.rollupJobs": ["plugins/rollup"],
Expand All @@ -64,17 +67,15 @@
"xpack.spaces": "plugins/spaces",
"xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"],
"xpack.taskManager": "legacy/plugins/task_manager",
"xpack.threatIntelligence": "plugins/threat_intelligence",
"xpack.timelines": "plugins/timelines",
"xpack.transform": "plugins/transform",
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
"xpack.synthetics": ["plugins/synthetics"],
"xpack.ux": ["plugins/ux"],
"xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
"xpack.watcher": "plugins/watcher",
"xpack.observability": "plugins/observability",
"xpack.banners": "plugins/banners",
"xpack.threatIntelligence": "plugins/threat_intelligence"
"xpack.watcher": "plugins/watcher"
},
"exclude": ["examples"],
"translations": [
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/observability_shared/.storybook/jest_setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { setGlobalConfig } from '@storybook/testing-react';
import * as globalStorybookConfig from './preview';

setGlobalConfig(globalStorybookConfig);
8 changes: 8 additions & 0 deletions x-pack/plugins/observability_shared/.storybook/main.js
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.
*/

module.exports = require('@kbn/storybook').defaultConfig;
10 changes: 10 additions & 0 deletions x-pack/plugins/observability_shared/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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 { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';

export const decorators = [EuiThemeProviderDecorator];
11 changes: 11 additions & 0 deletions x-pack/plugins/observability_shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Observability Shared

A plugin that contains components and utilities shared by all Observability plugins.

## 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](public/components/shared/page_template) for more information on registering your solution's navigation structure, and rendering the navigation via the shared component.

## A note on cyclical dependencies

Do not import any Observability plugins into this plugin. Only export shared stuff to other plugins.
11 changes: 11 additions & 0 deletions x-pack/plugins/observability_shared/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 observabilityFeatureId = 'observability';
export const observabilityAppId = 'observability-overview';
export const casesFeatureId = 'observabilityCases';
export const sloFeatureId = 'slo';
18 changes: 18 additions & 0 deletions x-pack/plugins/observability_shared/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/x-pack/plugins/observability_shared'],
setupFiles: ['<rootDir>/x-pack/plugins/observability_shared/.storybook/jest_setup.js'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/observability_shared',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/observability_shared/{common,public,server}/**/*.{js,ts,tsx}',
],
};
15 changes: 15 additions & 0 deletions x-pack/plugins/observability_shared/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "plugin",
"id": "@kbn/observability-shared-plugin",
"owner": "@elastic/actionable-observability",
"plugin": {
"id": "observabilityShared",
"server": false,
"browser": true,
"configPath": ["xpack", "observability_shared"],
"requiredPlugins": ["cases", "guidedOnboarding"],
"optionalPlugins": [],
"requiredBundles": ["kibanaReact"],
"extraPublicDirs": ["common"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
## 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 Shared Observability plugin specified as a dependency in your `kibana.json` file, e.g.

```json
"requiredPlugins": [
"sharedObservability"
],
```

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.

The `registerSections` function takes an `Observable` of an array of
`NavigationSection`s. Each section can be defined as

```typescript
export interface NavigationSection {
// the label of the section, should be translated
label: string | undefined;
// the key to sort by in ascending order relative to other entries
sortKey: number;
// the entries to render inside the section
entries: NavigationEntry[];
}
```

Each entry inside of a navigation section is defined as

```typescript
export interface NavigationEntry {
// the label of the menu entry, should be translated
label: string;
// the kibana app id
app: string;
// the path after the application prefix corresponding to this entry
path: string;
// whether to only match when the full path matches, defaults to `false`
matchFullPath?: boolean;
// whether to ignore trailing slashes, defaults to `true`
ignoreTrailingSlash?: boolean;
// shows NEW badge besides the navigation label, which will automatically disappear when menu item is clicked.
isNewFeature?: boolean;
// shows beta badge lab icon if the feature is still beta besides the navigation label
isBeta?: boolean;
}
```

A registration might therefore look like the following:

```typescript
// x-pack/plugins/example_plugin/public/plugin.ts

import { of } from 'rxjs';

export class Plugin implements PluginClass {
constructor(_context: PluginInitializerContext) {}

setup(core: CoreSetup, plugins: PluginsSetup) {
plugins.observabilityShared.navigation.registerSections(
of([
{
label: 'A solution section',
sortKey: 200,
entries: [
{ label: 'Home Page', app: 'exampleA', path: '/', matchFullPath: true },
{ 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 ObservabilityPageTemplate = pluginsStart.observabilityShared.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)

## Adding NEW badge

You can add a NEW badge beside the label by using the property `isNewFeature?: boolean;`.

```js
setup(core: CoreSetup, plugins: PluginsSetup) {
plugins.observabilityShared.navigation.registerSections(
of([
{
label: 'A solution section',
sortKey: 200,
entries: [
{ label: 'Backends', app: 'exampleA', path: '/example', isNewFeature: true },
],
}
])
);
}

```

![NEW Badge example](./badge.png)

The badge is going to be shown until user clicks on the menu item for the first time. Then we'll save an information at local storage, following this pattern `observability.nav_item_badge_visible_${app}${path}`, the above example would save `observability.nav_item_badge_visible_exampleA/example`. And the badge is removed. It'll only show again if the item saved at local storage is removed or set to `false`.

It's recommended to remove the badge (e.g. a new feature promotion) in the subsequent release.

To avoid the navigation flooding with badges, we also want to propose keeping it to maximum 2 active badges for every iteration
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { combineLatest, map, Observable, ReplaySubject, scan, shareReplay, switchMap } from 'rxjs';
import { NavigationSection } from '../page_template';

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$,
};
};
Loading