Skip to content

Commit

Permalink
Add plugin status API
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover committed Aug 25, 2020
1 parent 947a939 commit 3ea7db2
Show file tree
Hide file tree
Showing 20 changed files with 823 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) &gt; [derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md)

## StatusServiceSetup.derivedStatus$ property

The status of this plugin as derived from its dependencies.

<b>Signature:</b>

```typescript
derivedStatus$: Observable<ServiceStatus>;
```

## Remarks

By default, plugins inherit this derived status from their dependencies. Calling overrides this default status.

Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,13 @@ export interface StatusServiceSetup
| Property | Type | Description |
| --- | --- | --- |
| [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) | <code>Observable&lt;CoreStatus&gt;</code> | Current status for all Core services. |
| [derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) | <code>Observable&lt;ServiceStatus&gt;</code> | The status of this plugin as derived from its dependencies. |
| [overall$](./kibana-plugin-core-server.statusservicesetup.overall_.md) | <code>Observable&lt;ServiceStatus&gt;</code> | Overall system status for all of Kibana. |
| [plugins$](./kibana-plugin-core-server.statusservicesetup.plugins_.md) | <code>Observable&lt;Record&lt;string, ServiceStatus&gt;&gt;</code> | Current status for all dependencies of the current plugin. Each key of the <code>Record</code> is a plugin id. |

## Methods

| Method | Description |
| --- | --- |
| [set(status$)](./kibana-plugin-core-server.statusservicesetup.set.md) | Allows a plugin to specify a custom status dependent on its own criteria. Completely overrides the default inherited status. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) &gt; [plugins$](./kibana-plugin-core-server.statusservicesetup.plugins_.md)

## StatusServiceSetup.plugins$ property

Current status for all dependencies of the current plugin. Each key of the `Record` is a plugin id.

<b>Signature:</b>

```typescript
plugins$: Observable<Record<string, ServiceStatus>>;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) &gt; [set](./kibana-plugin-core-server.statusservicesetup.set.md)

## StatusServiceSetup.set() method

Allows a plugin to specify a custom status dependent on its own criteria. Completely overrides the default inherited status.

<b>Signature:</b>

```typescript
set(status$: Observable<ServiceStatus>): void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| status$ | <code>Observable&lt;ServiceStatus&gt;</code> | |

<b>Returns:</b>

`void`

## Remarks

See the [StatusServiceSetup.derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) API for leveraging the default status calculation that is provided by Core.

3 changes: 3 additions & 0 deletions src/core/server/legacy/legacy_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ export class LegacyService implements CoreService {
status: {
core$: setupDeps.core.status.core$,
overall$: setupDeps.core.status.overall$,
set: setupDeps.core.status.plugins.set.bind(null, 'legacy'),
plugins$: setupDeps.core.status.plugins.getPlugins$('legacy'),
derivedStatus$: setupDeps.core.status.plugins.getDerivedStatus$('legacy'),
},
uiSettings: {
register: setupDeps.core.uiSettings.register,
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
status: {
core$: deps.status.core$,
overall$: deps.status.overall$,
set: deps.status.plugins.set.bind(null, plugin.name),
plugins$: deps.status.plugins.getPlugins$(plugin.name),
derivedStatus$: deps.status.plugins.getDerivedStatus$(plugin.name),
},
uiSettings: {
register: deps.uiSettings.register,
Expand Down
30 changes: 21 additions & 9 deletions src/core/server/plugins/plugins_system.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,27 @@ test('getPluginDependencies returns dependency tree of symbols', () => {
pluginsSystem.addPlugin(createPlugin('no-dep'));

expect(pluginsSystem.getPluginDependencies()).toMatchInlineSnapshot(`
Map {
Symbol(plugin-a) => Array [
Symbol(no-dep),
],
Symbol(plugin-b) => Array [
Symbol(plugin-a),
Symbol(no-dep),
],
Symbol(no-dep) => Array [],
Object {
"asNames": Map {
"plugin-a" => Array [
"no-dep",
],
"plugin-b" => Array [
"plugin-a",
"no-dep",
],
"no-dep" => Array [],
},
"asOpaqueIds": Map {
Symbol(plugin-a) => Array [
Symbol(no-dep),
],
Symbol(plugin-b) => Array [
Symbol(plugin-a),
Symbol(no-dep),
],
Symbol(no-dep) => Array [],
},
}
`);
});
Expand Down
21 changes: 17 additions & 4 deletions src/core/server/plugins/plugins_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { PluginWrapper } from './plugin';
import { DiscoveredPlugin, PluginName, PluginOpaqueId } from './types';
import { DiscoveredPlugin, PluginName } from './types';
import { createPluginSetupContext, createPluginStartContext } from './plugin_context';
import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service';
import { withTimeout } from '../../utils';
import { PluginDependencies } from '.';

const Sec = 1000;
/** @internal */
Expand All @@ -45,9 +46,19 @@ export class PluginsSystem {
* @returns a ReadonlyMap of each plugin and an Array of its available dependencies
* @internal
*/
public getPluginDependencies(): ReadonlyMap<PluginOpaqueId, PluginOpaqueId[]> {
// Return dependency map of opaque ids
return new Map(
public getPluginDependencies(): PluginDependencies {
const asNames = new Map(
[...this.plugins].map(([name, plugin]) => [
plugin.name,
[
...new Set([
...plugin.requiredPlugins,
...plugin.optionalPlugins.filter((optPlugin) => this.plugins.has(optPlugin)),
]),
].map((depId) => this.plugins.get(depId)!.name),
])
);
const asOpaqueIds = new Map(
[...this.plugins].map(([name, plugin]) => [
plugin.opaqueId,
[
Expand All @@ -58,6 +69,8 @@ export class PluginsSystem {
].map((depId) => this.plugins.get(depId)!.opaqueId),
])
);

return { asNames, asOpaqueIds };
}

public async setupPlugins(deps: PluginsServiceSetupDeps) {
Expand Down
6 changes: 6 additions & 0 deletions src/core/server/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export type PluginName = string;
/** @public */
export type PluginOpaqueId = symbol;

/** @internal */
export interface PluginDependencies {
asNames: ReadonlyMap<PluginName, PluginName[]>;
asOpaqueIds: ReadonlyMap<PluginOpaqueId, PluginOpaqueId[]>;
}

/**
* Describes the set of required and optional properties plugin can define in its
* mandatory JSON manifest file.
Expand Down
10 changes: 7 additions & 3 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2802,7 +2802,11 @@ export type StartServicesAccessor<TPluginsStart extends object = object, TStart
// @public
export interface StatusServiceSetup {
core$: Observable<CoreStatus>;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "StatusSetup"
derivedStatus$: Observable<ServiceStatus>;
overall$: Observable<ServiceStatus>;
plugins$: Observable<Record<string, ServiceStatus>>;
set(status$: Observable<ServiceStatus>): void;
}

// @public
Expand Down Expand Up @@ -2900,8 +2904,8 @@ export const validBodyOutput: readonly ["data", "stream"];
// src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:166:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:167:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:268:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts

```
30 changes: 28 additions & 2 deletions src/core/server/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { Server } from './server';
import { getEnvOptions } from './config/__mocks__/env';
import { loggingSystemMock } from './logging/logging_system.mock';
import { rawConfigServiceMock } from './config/raw_config_service.mock';
import { PluginName } from './plugins';

const env = new Env('.', getEnvOptions());
const logger = loggingSystemMock.create();
Expand All @@ -49,7 +50,7 @@ const rawConfigService = rawConfigServiceMock.create({});
beforeEach(() => {
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
mockPluginsService.discover.mockResolvedValue({
pluginTree: new Map(),
pluginTree: { asOpaqueIds: new Map(), asNames: new Map() },
uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() },
});
});
Expand Down Expand Up @@ -98,7 +99,7 @@ test('injects legacy dependency to context#setup()', async () => {
[pluginB, [pluginA]],
]);
mockPluginsService.discover.mockResolvedValue({
pluginTree: pluginDependencies,
pluginTree: { asOpaqueIds: pluginDependencies, asNames: new Map() },
uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() },
});

Expand All @@ -113,6 +114,31 @@ test('injects legacy dependency to context#setup()', async () => {
});
});

test('injects legacy dependency to status#setup()', async () => {
const server = new Server(rawConfigService, env, logger);

const pluginDependencies = new Map<PluginName, PluginName[]>([
['a', []],
['b', ['a']],
]);
mockPluginsService.discover.mockResolvedValue({
pluginTree: { asOpaqueIds: new Map(), asNames: pluginDependencies },
uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() },
});

await server.setup();

expect(mockStatusService.setup).toHaveBeenCalledWith({
elasticsearch: expect.any(Object),
savedObjects: expect.any(Object),
pluginDependencies: new Map([
['a', []],
['b', ['a']],
['legacy', ['a', 'b']],
]),
});
});

test('runs services on "start"', async () => {
const server = new Server(rawConfigService, env, logger);

Expand Down
13 changes: 11 additions & 2 deletions src/core/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,13 @@ export class Server {

const contextServiceSetup = this.context.setup({
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
// 1) Can access context from any NP plugin
// 1) Can access context from any KP plugin
// 2) Can register context providers that will only be available to other legacy plugins and will not leak into
// New Platform plugins.
pluginDependencies: new Map([...pluginTree, [this.legacy.legacyId, [...pluginTree.keys()]]]),
pluginDependencies: new Map([
...pluginTree.asOpaqueIds,
[this.legacy.legacyId, [...pluginTree.asOpaqueIds.keys()]],
]),
});

const auditTrailSetup = this.auditTrail.setup();
Expand Down Expand Up @@ -151,6 +154,12 @@ export class Server {

const statusSetup = await this.status.setup({
elasticsearch: elasticsearchServiceSetup,
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy can access plugin status from
// any KP plugin
pluginDependencies: new Map([
...pluginTree.asNames,
['legacy', [...pluginTree.asNames.keys()]],
]),
savedObjects: savedObjectsSetup,
});

Expand Down
44 changes: 43 additions & 1 deletion src/core/server/status/get_summary_status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,38 @@ describe('getSummaryStatus', () => {
describe('summary', () => {
describe('when a single service is at highest level', () => {
it('returns all information about that single service', () => {
expect(
getSummaryStatus(
Object.entries({
s1: degraded,
s2: {
level: ServiceStatusLevels.unavailable,
summary: 'Lorem ipsum',
meta: {
custom: { data: 'here' },
},
},
})
)
).toEqual({
level: ServiceStatusLevels.unavailable,
summary: '[s2]: Lorem ipsum',
detail: 'See the status page for more information',
meta: {
affectedServices: {
s2: {
level: ServiceStatusLevels.unavailable,
summary: 'Lorem ipsum',
meta: {
custom: { data: 'here' },
},
},
},
},
});
});

it('allows the single service to override the detail and documentationUrl fields', () => {
expect(
getSummaryStatus(
Object.entries({
Expand All @@ -115,7 +147,17 @@ describe('getSummaryStatus', () => {
detail: 'Vivamus pulvinar sem ac luctus ultrices.',
documentationUrl: 'http://helpmenow.com/problem1',
meta: {
custom: { data: 'here' },
affectedServices: {
s2: {
level: ServiceStatusLevels.unavailable,
summary: 'Lorem ipsum',
detail: 'Vivamus pulvinar sem ac luctus ultrices.',
documentationUrl: 'http://helpmenow.com/problem1',
meta: {
custom: { data: 'here' },
},
},
},
},
});
});
Expand Down
12 changes: 10 additions & 2 deletions src/core/server/status/get_summary_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,29 @@ import { ServiceStatus, ServiceStatusLevels, ServiceStatusLevel } from './types'
* Returns a single {@link ServiceStatus} that summarizes the most severe status level from a group of statuses.
* @param statuses
*/
export const getSummaryStatus = (statuses: Array<[string, ServiceStatus]>): ServiceStatus => {
export const getSummaryStatus = (
statuses: Array<[string, ServiceStatus]>,
{ allAvailableSummary = `All services are available` }: { allAvailableSummary?: string } = {}
): ServiceStatus => {
const grouped = groupByLevel(statuses);
const highestSeverityLevel = getHighestSeverityLevel(grouped.keys());
const highestSeverityGroup = grouped.get(highestSeverityLevel)!;

if (highestSeverityLevel === ServiceStatusLevels.available) {
return {
level: ServiceStatusLevels.available,
summary: `All services are available`,
summary: allAvailableSummary,
};
} else if (highestSeverityGroup.size === 1) {
const [serviceName, status] = [...highestSeverityGroup.entries()][0];
return {
...status,
summary: `[${serviceName}]: ${status.summary!}`,
// TODO: include URL to status page
detail: status.detail ?? `See the status page for more information`,
meta: {
affectedServices: { [serviceName]: status },
},
};
} else {
return {
Expand Down
Loading

0 comments on commit 3ea7db2

Please sign in to comment.