Skip to content

Commit

Permalink
Load Capabilities from InjectedMetadata (#36710)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover authored May 21, 2019
1 parent 1e25d1d commit d9c34ca
Show file tree
Hide file tree
Showing 26 changed files with 251 additions and 185 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [InjectedMetadataSetup](./kibana-plugin-public.injectedmetadatasetup.md) &gt; [getCapabilities](./kibana-plugin-public.injectedmetadatasetup.getcapabilities.md)

## InjectedMetadataSetup.getCapabilities property

<b>Signature:</b>

```typescript
getCapabilities: () => Capabilities;
```
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface InjectedMetadataSetup
| Property | Type | Description |
| --- | --- | --- |
| [getBasePath](./kibana-plugin-public.injectedmetadatasetup.getbasepath.md) | <code>() =&gt; string</code> | |
| [getCapabilities](./kibana-plugin-public.injectedmetadatasetup.getcapabilities.md) | <code>() =&gt; Capabilities</code> | |
| [getCspConfig](./kibana-plugin-public.injectedmetadatasetup.getcspconfig.md) | <code>() =&gt; {`<p/>` warnLegacyBrowsers: boolean;`<p/>` }</code> | |
| [getInjectedVar](./kibana-plugin-public.injectedmetadatasetup.getinjectedvar.md) | <code>(name: string, defaultValue?: any) =&gt; unknown</code> | |
| [getInjectedVars](./kibana-plugin-public.injectedmetadatasetup.getinjectedvars.md) | <code>() =&gt; {`<p/>` [key: string]: unknown;`<p/>` }</code> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
* under the License.
*/

// @ts-ignore
import fetchMock from 'fetch-mock/es5/client';

import { InjectedMetadataService } from '../../injected_metadata';
import { CapabilitiesService } from './capabilities_service';
import { basePathServiceMock } from '../../base_path/base_path_service.mock';
Expand All @@ -29,74 +26,21 @@ describe('#start', () => {
basePath.addToPath.mockImplementation(str => str);
const injectedMetadata = new InjectedMetadataService({
injectedMetadata: {
vars: {
uiCapabilities: {
foo: { feature: true },
bar: { feature: true },
capabilities: {
catalogue: {},
management: {},
navLinks: {
app1: true,
app2: false,
},
foo: { feature: true },
bar: { feature: true },
},
} as any,
}).start();
const apps = [{ id: 'app1' }, { id: 'app2', capabilities: { app2: { feature: true } } }] as any;

beforeEach(() => {
fetchMock.post('/api/capabilities', (url: string, options: any) => ({
body: options.body,
status: 200,
}));
});

afterEach(() => {
fetchMock.restore();
});

it('calls backend API with merged capabilities', async () => {
const service = new CapabilitiesService();
await service.start({ apps, basePath, injectedMetadata });
expect(fetchMock.calls()).toMatchInlineSnapshot(`
Array [
Array [
"/api/capabilities",
Object {
"body": "{\\"capabilities\\":{\\"navLinks\\":{\\"app2\\":true,\\"app1\\":true},\\"management\\":{},\\"catalogue\\":{},\\"app2\\":{\\"feature\\":true}}}",
"credentials": "same-origin",
"headers": Object {
"kbn-xsrf": "xxx",
},
"method": "POST",
},
],
]
`);
});

it('returns capabilities from backend', async () => {
const service = new CapabilitiesService();
expect((await service.start({ apps, basePath, injectedMetadata })).capabilities)
.toMatchInlineSnapshot(`
Object {
"app2": Object {
"feature": true,
},
"catalogue": Object {},
"management": Object {},
"navLinks": Object {
"app1": true,
"app2": true,
},
}
`);
});

it('filters available apps based on returned navLinks', async () => {
fetchMock.post(
'/api/capabilities',
(url: string, options: any) => ({
body: JSON.stringify({ capabilities: { navLinks: { app1: true, app2: false } } }),
status: 200,
}),
{ overwriteRoutes: true }
);
const service = new CapabilitiesService();
expect((await service.start({ apps, basePath, injectedMetadata })).availableApps).toEqual([
{ id: 'app1' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import { deepFreeze, RecursiveReadonly } from '../../utils/deep_freeze';
import { MixedApp } from '../application_service';
import { mergeCapabilities } from './merge_capabilities';
import { InjectedMetadataStart } from '../../injected_metadata';
import { BasePathStart } from '../../base_path';

Expand Down Expand Up @@ -76,38 +75,7 @@ export interface CapabilitiesStart {
*/
export class CapabilitiesService {
public async start({ apps, basePath, injectedMetadata }: StartDeps): Promise<CapabilitiesStart> {
const mergedCapabilities = mergeCapabilities(
// Custom capabilites for new platform apps
...apps.filter(app => app.capabilities).map(app => app.capabilities!),
// Generate navLink capabilities for all apps
...apps.map(app => ({ navLinks: { [app.id]: true } }))
);

// NOTE: should replace `fetch` with browser HTTP service once it exists
const res = await fetch(basePath.addToPath('/api/capabilities'), {
method: 'POST',
body: JSON.stringify({ capabilities: mergedCapabilities }),
headers: {
'kbn-xsrf': 'xxx',
},
credentials: 'same-origin',
});

if (res.status === 401) {
return {
availableApps: [],
capabilities: deepFreeze({
navLinks: {},
management: {},
catalogue: {},
}),
};
} else if (res.status !== 200) {
throw new Error(`Capabilities check failed.`);
}

const body = await res.json();
const capabilities = deepFreeze(body.capabilities as Capabilities);
const capabilities = deepFreeze(injectedMetadata.getCapabilities());
const availableApps = apps.filter(app => capabilities.navLinks[app.id]);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ const createSetupContractMock = () => {
const setupContract: jest.Mocked<InjectedMetadataSetup> = {
getBasePath: jest.fn(),
getKibanaVersion: jest.fn(),
getCapabilities: jest.fn(),
getCspConfig: jest.fn(),
getLegacyMetadata: jest.fn(),
getPlugins: jest.fn(),
getInjectedVar: jest.fn(),
getInjectedVars: jest.fn(),
getKibanaBuildNumber: jest.fn(),
};
setupContract.getCapabilities.mockReturnValue({} as any);
setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
setupContract.getKibanaVersion.mockReturnValue('kibanaVersion');
setupContract.getLegacyMetadata.mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { get } from 'lodash';
import { DiscoveredPlugin, PluginName } from '../../server';
import { UiSettingsState } from '../ui_settings';
import { deepFreeze } from '../utils/deep_freeze';
import { Capabilities } from '..';

/** @public */
export interface LegacyNavLink {
Expand Down Expand Up @@ -48,6 +49,7 @@ export interface InjectedMetadataParams {
id: PluginName;
plugin: DiscoveredPlugin;
}>;
capabilities: Capabilities;
legacyMetadata: {
app: unknown;
translations: unknown;
Expand Down Expand Up @@ -97,6 +99,10 @@ export class InjectedMetadataService {
return this.state.version;
},

getCapabilities: () => {
return this.state.capabilities;
},

getCspConfig: () => {
return this.state.csp;
},
Expand Down Expand Up @@ -133,6 +139,7 @@ export interface InjectedMetadataSetup {
getBasePath: () => string;
getKibanaBuildNumber: () => number;
getKibanaVersion: () => string;
getCapabilities: () => Capabilities;
getCspConfig: () => {
warnLegacyBrowsers: boolean;
};
Expand Down
7 changes: 5 additions & 2 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export interface InjectedMetadataParams {
id: PluginName;
plugin: DiscoveredPlugin;
}>;
capabilities: Capabilities;
legacyMetadata: {
app: unknown;
translations: unknown;
Expand All @@ -246,6 +247,8 @@ export interface InjectedMetadataSetup {
// (undocumented)
getBasePath: () => string;
// (undocumented)
getCapabilities: () => Capabilities;
// (undocumented)
getCspConfig: () => {
warnLegacyBrowsers: boolean;
};
Expand Down Expand Up @@ -452,8 +455,8 @@ export interface UiSettingsState {

// Warnings were encountered during analysis:
//
// src/core/public/injected_metadata/injected_metadata_service.ts:48:7 - (ae-forgotten-export) The symbol "PluginName" needs to be exported by the entry point index.d.ts
// src/core/public/injected_metadata/injected_metadata_service.ts:49:7 - (ae-forgotten-export) The symbol "DiscoveredPlugin" needs to be exported by the entry point index.d.ts
// src/core/public/injected_metadata/injected_metadata_service.ts:49:7 - (ae-forgotten-export) The symbol "PluginName" needs to be exported by the entry point index.d.ts
// src/core/public/injected_metadata/injected_metadata_service.ts:50:7 - (ae-forgotten-export) The symbol "DiscoveredPlugin" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const coreSystem = new CoreSystem({
csp: {
warnLegacyBrowsers: false,
},
capabilities: uiCapabilities,
uiPlugins: [],
vars: {
kbnIndex: '.kibana',
Expand Down
11 changes: 11 additions & 0 deletions src/legacy/server/capabilities/capabilities_mixin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,15 @@ describe('capabilitiesMixin', () => {

expect(mockRegisterCapabilitiesRoute.mock.calls[0][2]).toEqual([mockModifier1, mockModifier2]);
});

it('exposes request#getCapabilities for retrieving legacy capabilities', async () => {
const kbnServer = getKbnServer();
jest.spyOn(server, 'decorate');
await capabilitiesMixin(kbnServer, server);
expect(server.decorate).toHaveBeenCalledWith(
'request',
'getCapabilities',
expect.any(Function)
);
});
});
14 changes: 14 additions & 0 deletions src/legacy/server/capabilities/capabilities_mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Capabilities } from '../../../core/public';
import KbnServer from '../kbn_server';
import { registerCapabilitiesRoute } from './capabilities_route';
import { mergeCapabilities } from './merge_capabilities';
import { resolveCapabilities } from './resolve_capabilities';

export type CapabilitiesModifier = (
request: Request,
Expand All @@ -48,6 +49,19 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) {
))
);

server.decorate('request', 'getCapabilities', function() {
// Get legacy nav links
const navLinks = server.getUiNavLinks().reduce(
(acc, spec) => ({
...acc,
[spec._id]: true,
}),
{} as Record<string, boolean>
);

return resolveCapabilities(this, modifiers, defaultCapabilities, { navLinks });
});

registerCapabilitiesRoute(server, defaultCapabilities, modifiers);
});
}
19 changes: 9 additions & 10 deletions src/legacy/server/capabilities/capabilities_route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import Joi from 'joi';
import { Server } from 'hapi';

import { CapabilitiesModifier } from '.';
import { Capabilities } from '../../../core/public';
import { mergeCapabilities } from './merge_capabilities';
import { CapabilitiesModifier } from './capabilities_mixin';
import { resolveCapabilities } from './resolve_capabilities';

export const registerCapabilitiesRoute = (
server: Server,
Expand All @@ -40,15 +40,14 @@ export const registerCapabilitiesRoute = (
},
},
async handler(request) {
let { capabilities } = request.payload as { capabilities: Capabilities };
capabilities = mergeCapabilities({ ...defaultCapabilities }, capabilities);

for (const provider of modifiers) {
capabilities = await provider(request, capabilities);
}

const { capabilities } = request.payload as { capabilities: Capabilities };
return {
capabilities,
capabilities: await resolveCapabilities(
request,
modifiers,
defaultCapabilities,
capabilities
),
};
},
});
Expand Down
6 changes: 3 additions & 3 deletions src/legacy/server/capabilities/merge_capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

import { Capabilities } from '../../../core/public';

export const mergeCapabilities = (...sources: Capabilities[]): Capabilities =>
export const mergeCapabilities = (...sources: Array<Partial<Capabilities>>): Capabilities =>
sources.reduce(
(capabilities, source) => {
Object.entries(source).forEach(([key, value]) => {
(capabilities: Capabilities, source) => {
Object.entries(source).forEach(([key, value = {}]) => {
capabilities[key] = {
...value,
...capabilities[key],
Expand Down
34 changes: 34 additions & 0 deletions src/legacy/server/capabilities/resolve_capabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Request } from 'hapi';

import { Capabilities } from '../../../core/public';
import { mergeCapabilities } from './merge_capabilities';
import { CapabilitiesModifier } from './capabilities_mixin';

export const resolveCapabilities = (
request: Request,
modifiers: CapabilitiesModifier[],
...capabilities: Array<Partial<Capabilities>>
) =>
modifiers.reduce(
async (resolvedCaps, modifier) => modifier(request, await resolvedCaps),
Promise.resolve(mergeCapabilities(...capabilities))
);
3 changes: 3 additions & 0 deletions src/legacy/server/kbn_server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
SavedObjectsSchema,
SavedObjectsManagement,
} from './saved_objects';
import { Capabilities } from '../../core/public';

export interface KibanaConfig {
get<T>(key: string): T;
Expand Down Expand Up @@ -71,12 +72,14 @@ declare module 'hapi' {
scopedTutorialContextFactory: (...args: any[]) => any
) => void;
savedObjectsManagement(): SavedObjectsManagement;
getUiNavLinks(): Array<{ _id: string }>;
}

interface Request {
getSavedObjectsClient(): SavedObjectsClient;
getBasePath(): string;
getUiSettingsService(): any;
getCapabilities(): Promise<Capabilities>;
}

interface ResponseToolkit {
Expand Down
Loading

0 comments on commit d9c34ca

Please sign in to comment.