Setting up and documenting Presentation Util (#88112)
This commit is contained in:
parent
608efb0a3d
commit
55afba4a4d
|
@ -150,8 +150,8 @@ It also provides a stateful version of it on the start contract.
|
||||||
Content is fetched from the remote (https://feeds.elastic.co and https://feeds-staging.elastic.co in dev mode) once a day, with periodic checks if the content needs to be refreshed. All newsfeed content is hosted remotely.
|
Content is fetched from the remote (https://feeds.elastic.co and https://feeds-staging.elastic.co in dev mode) once a day, with periodic checks if the content needs to be refreshed. All newsfeed content is hosted remotely.
|
||||||
|
|
||||||
|
|
||||||
|{kib-repo}blob/{branch}/src/plugins/presentation_util/README.md[presentationUtil]
|
|{kib-repo}blob/{branch}/src/plugins/presentation_util/README.mdx[presentationUtil]
|
||||||
|Utilities and components used by the presentation-related plugins
|
|The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas).
|
||||||
|
|
||||||
|
|
||||||
|{kib-repo}blob/{branch}/src/plugins/region_map/README.md[regionMap]
|
|{kib-repo}blob/{branch}/src/plugins/region_map/README.md[regionMap]
|
||||||
|
|
|
@ -393,6 +393,7 @@
|
||||||
"@storybook/addon-essentials": "^6.0.26",
|
"@storybook/addon-essentials": "^6.0.26",
|
||||||
"@storybook/addon-knobs": "^6.0.26",
|
"@storybook/addon-knobs": "^6.0.26",
|
||||||
"@storybook/addon-storyshots": "^6.0.26",
|
"@storybook/addon-storyshots": "^6.0.26",
|
||||||
|
"@storybook/addon-docs": "^6.0.26",
|
||||||
"@storybook/components": "^6.0.26",
|
"@storybook/components": "^6.0.26",
|
||||||
"@storybook/core": "^6.0.26",
|
"@storybook/core": "^6.0.26",
|
||||||
"@storybook/core-events": "^6.0.26",
|
"@storybook/core-events": "^6.0.26",
|
||||||
|
|
|
@ -18,4 +18,5 @@ export const storybookAliases = {
|
||||||
security_solution: 'x-pack/plugins/security_solution/.storybook',
|
security_solution: 'x-pack/plugins/security_solution/.storybook',
|
||||||
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook',
|
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook',
|
||||||
observability: 'x-pack/plugins/observability/.storybook',
|
observability: 'x-pack/plugins/observability/.storybook',
|
||||||
|
presentation: 'src/plugins/presentation_util/storybook',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# presentationUtil
|
|
||||||
|
|
||||||
Utilities and components used by the presentation-related plugins
|
|
211
src/plugins/presentation_util/README.mdx
Executable file
211
src/plugins/presentation_util/README.mdx
Executable file
|
@ -0,0 +1,211 @@
|
||||||
|
---
|
||||||
|
id: presentationUtilPlugin
|
||||||
|
slug: /kibana-dev-docs/presentationPlugin
|
||||||
|
title: Presentation Utility Plugin
|
||||||
|
summary: Introduction to the Presentation Utility Plugin.
|
||||||
|
date: 2020-01-12
|
||||||
|
tags: ['kibana', 'presentation', 'services']
|
||||||
|
related: []
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas).
|
||||||
|
|
||||||
|
## Plugin Services Toolkit
|
||||||
|
|
||||||
|
While Kibana provides a `useKibana` hook for use in a plugin, the number of services it provides is very large. This presents a set of difficulties:
|
||||||
|
|
||||||
|
- a direct dependency upon the Kibana environment;
|
||||||
|
- a requirement to mock the full Kibana environment when testing or using Storybook;
|
||||||
|
- a lack of knowledge as to what services are being consumed at any given time.
|
||||||
|
|
||||||
|
To mitigate these difficulties, the Presentation Team creates services within the plugin that then consume Kibana-provided (or other) services. This is a toolkit for creating simple services within a plugin.
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- A `PluginServiceFactory` is a function that will return a set of functions-- which comprise a `Service`-- given a set of parameters.
|
||||||
|
- A `PluginServiceProvider` is an object that use a factory to start, stop or provide a `Service`.
|
||||||
|
- A `PluginServiceRegistry` is a collection of providers for a given environment, (e.g. Kibana, Jest, Storybook, stub, etc).
|
||||||
|
- A `PluginServices` object uses a registry to provide services throughout the plugin.
|
||||||
|
|
||||||
|
### Defining Services
|
||||||
|
|
||||||
|
To start, a plugin should define a set of services it wants to provide to itself or other plugins.
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Service Definition Example" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
export interface PresentationDashboardsService {
|
||||||
|
findDashboards: (
|
||||||
|
query: string,
|
||||||
|
fields: string[]
|
||||||
|
) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||||
|
findDashboardsByTitle: (title: string) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresentationFooService {
|
||||||
|
getFoo: () => string;
|
||||||
|
setFoo: (bar: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresentationUtilServices {
|
||||||
|
dashboards: PresentationDashboardsService;
|
||||||
|
foo: PresentationFooService;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
||||||
|
|
||||||
|
This definition will be used in the toolkit to ensure services are complete and as expected.
|
||||||
|
|
||||||
|
### Plugin Services
|
||||||
|
|
||||||
|
The `PluginServices` class hosts a registry of service providers from which a plugin can access its services. It uses the service definition as a generic.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const pluginServices = new PluginServices<PresentationUtilServices>();
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be placed in the `index.ts` file of a `services` directory within your plugin.
|
||||||
|
|
||||||
|
Once created, it simply requires a `PluginServiceRegistry` to be started and set.
|
||||||
|
|
||||||
|
### Service Provider Registry
|
||||||
|
|
||||||
|
Each environment in which components are used requires a `PluginServiceRegistry` to specify how the providers are started. For example, simple stubs of services require no parameters to start, (so the `StartParameters` generic remains unspecified)
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Stubbed Service Registry Example" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
export const providers: PluginServiceProviders<PresentationUtilServices> = {
|
||||||
|
dashboards: new PluginServiceProvider(dashboardsServiceFactory),
|
||||||
|
foo: new PluginServiceProvider(fooServiceFactory),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serviceRegistry = new PluginServiceRegistry<PresentationUtilServices>(providers);
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
||||||
|
|
||||||
|
By contrast, a registry that uses Kibana can provide `KibanaPluginServiceParams` to determine how to start its providers, so the `StartParameters` generic is given:
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Kibana Service Registry Example" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
export const providers: PluginServiceProviders<
|
||||||
|
PresentationUtilServices,
|
||||||
|
KibanaPluginServiceParams<PresentationUtilPluginStart>
|
||||||
|
> = {
|
||||||
|
dashboards: new PluginServiceProvider(dashboardsServiceFactory),
|
||||||
|
foo: new PluginServiceProvider(fooServiceFactory),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serviceRegistry = new PluginServiceRegistry<
|
||||||
|
PresentationUtilServices,
|
||||||
|
KibanaPluginServiceParams<PresentationUtilPluginStart>
|
||||||
|
>(providers);
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
||||||
|
|
||||||
|
### Service Provider
|
||||||
|
|
||||||
|
A `PluginServiceProvider` is a container for a Service Factory that is responsible for starting, stopping and providing a service implementation. A Service Provider doesn't change, rather the factory and the relevant `StartParameters` change.
|
||||||
|
|
||||||
|
### Service Factories
|
||||||
|
|
||||||
|
A Service Factory is nothing more than a function that uses `StartParameters` to return a set of functions that conforms to a portion of the `Services` specification. For each service, a factory is provided for each environment.
|
||||||
|
|
||||||
|
Given a service definition:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface PresentationFooService {
|
||||||
|
getFoo: () => string;
|
||||||
|
setFoo: (bar: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
a factory for a stubbed version might look like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type FooServiceFactory = PluginServiceFactory<PresentationFooService>;
|
||||||
|
|
||||||
|
export const fooServiceFactory: FooServiceFactory = () => ({
|
||||||
|
getFoo: () => 'bar',
|
||||||
|
setFoo: (bar) => { console.log(`${bar} set!`)},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
and a factory for a Kibana version might look like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type FooServiceFactory = KibanaPluginServiceFactory<
|
||||||
|
PresentationFooService,
|
||||||
|
PresentationUtilPluginStart
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const fooServiceFactory: FooServiceFactory = ({
|
||||||
|
coreStart,
|
||||||
|
startPlugins,
|
||||||
|
}) => {
|
||||||
|
// ...do something with Kibana services...
|
||||||
|
|
||||||
|
return {
|
||||||
|
getFoo: //...
|
||||||
|
setFoo: //...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Services
|
||||||
|
|
||||||
|
Once your services and providers are defined, and you have at least one set of factories, you can use `PluginServices` to provide the services to your React components:
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Services starting in a plugin" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
// plugin.ts
|
||||||
|
import { pluginServices } from './services';
|
||||||
|
import { registry } from './services/kibana';
|
||||||
|
|
||||||
|
public async start(
|
||||||
|
coreStart: CoreStart,
|
||||||
|
startPlugins: StartDeps
|
||||||
|
): Promise<PresentationUtilPluginStart> {
|
||||||
|
pluginServices.setRegistry(registry.start({ coreStart, startPlugins }));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
||||||
|
|
||||||
|
and wrap your root React component with the `PluginServices` context:
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Providing services in a React context" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
import { pluginServices } from './services';
|
||||||
|
|
||||||
|
const ContextProvider = pluginServices.getContextProvider(),
|
||||||
|
|
||||||
|
return(
|
||||||
|
<I18nContext>
|
||||||
|
<WhateverElse>
|
||||||
|
<ContextProvider>{application}</ContextProvider>
|
||||||
|
</WhateverElse>
|
||||||
|
</I18nContext>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
||||||
|
|
||||||
|
and then, consume your services using provided hooks in a component:
|
||||||
|
|
||||||
|
<DocAccordion buttonContent="Consuming services in a component" initialIsOpen>
|
||||||
|
```ts
|
||||||
|
// component.ts
|
||||||
|
|
||||||
|
import { pluginServices } from '../services';
|
||||||
|
|
||||||
|
export function MyComponent() {
|
||||||
|
// Retrieve all context hooks from `PluginServices`, destructuring for the one we're using
|
||||||
|
const { foo } = pluginServices.getHooks();
|
||||||
|
|
||||||
|
// Use the `useContext` hook to access the API.
|
||||||
|
const { getFoo } = foo.useService();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</DocAccordion>
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { DashboardPicker } from './dashboard_picker';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: DashboardPicker,
|
||||||
|
title: 'Dashboard Picker',
|
||||||
|
argTypes: {
|
||||||
|
isDisabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Example = ({ isDisabled }: { isDisabled: boolean }) => (
|
||||||
|
<DashboardPicker onChange={action('onChange')} isDisabled={isDisabled} />
|
||||||
|
);
|
|
@ -6,18 +6,16 @@
|
||||||
* Public License, v 1.
|
* Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { EuiComboBox } from '@elastic/eui';
|
import { EuiComboBox } from '@elastic/eui';
|
||||||
import { SavedObjectsClientContract } from '../../../../core/public';
|
import { pluginServices } from '../services';
|
||||||
import { DashboardSavedObject } from '../../../../plugins/dashboard/public';
|
|
||||||
|
|
||||||
export interface DashboardPickerProps {
|
export interface DashboardPickerProps {
|
||||||
onChange: (dashboard: { name: string; id: string } | null) => void;
|
onChange: (dashboard: { name: string; id: string } | null) => void;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DashboardOption {
|
interface DashboardOption {
|
||||||
|
@ -26,34 +24,43 @@ interface DashboardOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardPicker(props: DashboardPickerProps) {
|
export function DashboardPicker(props: DashboardPickerProps) {
|
||||||
const [dashboards, setDashboards] = useState<DashboardOption[]>([]);
|
const [dashboardOptions, setDashboardOptions] = useState<DashboardOption[]>([]);
|
||||||
const [isLoadingDashboards, setIsLoadingDashboards] = useState(true);
|
const [isLoadingDashboards, setIsLoadingDashboards] = useState(true);
|
||||||
const [selectedDashboard, setSelectedDashboard] = useState<DashboardOption | null>(null);
|
const [selectedDashboard, setSelectedDashboard] = useState<DashboardOption | null>(null);
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
const { savedObjectsClient, isDisabled, onChange } = props;
|
const { isDisabled, onChange } = props;
|
||||||
|
const { dashboards } = pluginServices.getHooks();
|
||||||
|
const { findDashboardsByTitle } = dashboards.useService();
|
||||||
|
|
||||||
const fetchDashboards = useCallback(
|
|
||||||
async (query) => {
|
|
||||||
setIsLoadingDashboards(true);
|
|
||||||
setDashboards([]);
|
|
||||||
|
|
||||||
const { savedObjects } = await savedObjectsClient.find<DashboardSavedObject>({
|
|
||||||
type: 'dashboard',
|
|
||||||
search: query ? `${query}*` : '',
|
|
||||||
searchFields: ['title'],
|
|
||||||
});
|
|
||||||
if (savedObjects) {
|
|
||||||
setDashboards(savedObjects.map((d) => ({ value: d.id, label: d.attributes.title })));
|
|
||||||
}
|
|
||||||
setIsLoadingDashboards(false);
|
|
||||||
},
|
|
||||||
[savedObjectsClient]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initial dashboard load
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDashboards('');
|
// We don't want to manipulate the React state if the component has been unmounted
|
||||||
}, [fetchDashboards]);
|
// while we wait for the saved objects to return.
|
||||||
|
let cleanedUp = false;
|
||||||
|
|
||||||
|
const fetchDashboards = async () => {
|
||||||
|
setIsLoadingDashboards(true);
|
||||||
|
setDashboardOptions([]);
|
||||||
|
|
||||||
|
const objects = await findDashboardsByTitle(query ? `${query}*` : '');
|
||||||
|
|
||||||
|
if (cleanedUp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objects) {
|
||||||
|
setDashboardOptions(objects.map((d) => ({ value: d.id, label: d.attributes.title })));
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingDashboards(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDashboards();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cleanedUp = true;
|
||||||
|
};
|
||||||
|
}, [findDashboardsByTitle, query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiComboBox
|
<EuiComboBox
|
||||||
|
@ -61,7 +68,7 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
||||||
defaultMessage: 'Search dashboards...',
|
defaultMessage: 'Search dashboards...',
|
||||||
})}
|
})}
|
||||||
singleSelection={{ asPlainText: true }}
|
singleSelection={{ asPlainText: true }}
|
||||||
options={dashboards || []}
|
options={dashboardOptions || []}
|
||||||
selectedOptions={!!selectedDashboard ? [selectedDashboard] : undefined}
|
selectedOptions={!!selectedDashboard ? [selectedDashboard] : undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.length) {
|
if (e.length) {
|
||||||
|
@ -72,7 +79,7 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
||||||
onChange(null);
|
onChange(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSearchChange={fetchDashboards}
|
onSearchChange={setQuery}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isLoading={isLoadingDashboards}
|
isLoading={isLoadingDashboards}
|
||||||
compressed={true}
|
compressed={true}
|
||||||
|
|
|
@ -9,18 +9,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiFormRow,
|
|
||||||
EuiRadio,
|
|
||||||
EuiIconTip,
|
|
||||||
EuiPanel,
|
|
||||||
EuiSpacer,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { SavedObjectsClientContract } from '../../../../core/public';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OnSaveProps,
|
OnSaveProps,
|
||||||
|
@ -28,9 +16,9 @@ import {
|
||||||
SavedObjectSaveModal,
|
SavedObjectSaveModal,
|
||||||
} from '../../../../plugins/saved_objects/public';
|
} from '../../../../plugins/saved_objects/public';
|
||||||
|
|
||||||
import { DashboardPicker } from './dashboard_picker';
|
|
||||||
|
|
||||||
import './saved_object_save_modal_dashboard.scss';
|
import './saved_object_save_modal_dashboard.scss';
|
||||||
|
import { pluginServices } from '../services';
|
||||||
|
import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector';
|
||||||
|
|
||||||
interface SaveModalDocumentInfo {
|
interface SaveModalDocumentInfo {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -38,116 +26,50 @@ interface SaveModalDocumentInfo {
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardSaveModalProps {
|
export interface SaveModalDashboardProps {
|
||||||
documentInfo: SaveModalDocumentInfo;
|
documentInfo: SaveModalDocumentInfo;
|
||||||
objectType: string;
|
objectType: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (props: OnSaveProps & { dashboardId: string | null }) => void;
|
onSave: (props: OnSaveProps & { dashboardId: string | null }) => void;
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode);
|
tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
|
||||||
const { documentInfo, savedObjectsClient, tagOptions } = props;
|
const { documentInfo, tagOptions, objectType, onClose } = props;
|
||||||
const initialCopyOnSave = !Boolean(documentInfo.id);
|
const { id: documentId } = documentInfo;
|
||||||
|
const initialCopyOnSave = !Boolean(documentId);
|
||||||
|
|
||||||
|
const { capabilities } = pluginServices.getHooks();
|
||||||
|
const {
|
||||||
|
canAccessDashboards,
|
||||||
|
canCreateNewDashboards,
|
||||||
|
canEditDashboards,
|
||||||
|
} = capabilities.useService();
|
||||||
|
|
||||||
|
const disableDashboardOptions =
|
||||||
|
!canAccessDashboards() || (!canCreateNewDashboards && !canEditDashboards);
|
||||||
|
|
||||||
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>(
|
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>(
|
||||||
documentInfo.id ? null : 'existing'
|
documentId || disableDashboardOptions ? null : 'existing'
|
||||||
);
|
);
|
||||||
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
|
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [copyOnSave, setCopyOnSave] = useState<boolean>(initialCopyOnSave);
|
const [copyOnSave, setCopyOnSave] = useState<boolean>(initialCopyOnSave);
|
||||||
|
|
||||||
const renderDashboardSelect = (state: SaveModalState) => {
|
const rightOptions = !disableDashboardOptions
|
||||||
const isDisabled = Boolean(!state.copyOnSave && documentInfo.id);
|
? () => (
|
||||||
|
<SaveModalDashboardSelector
|
||||||
return (
|
onSelectDashboard={(dash) => {
|
||||||
<>
|
setSelectedDashboard(dash);
|
||||||
<EuiFormRow
|
}}
|
||||||
label={
|
onChange={(option) => {
|
||||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
setDashboardOption(option);
|
||||||
<EuiFlexItem grow={false}>
|
}}
|
||||||
<FormattedMessage
|
{...{ copyOnSave, documentId, dashboardOption }}
|
||||||
id="presentationUtil.saveModalDashboard.addToDashboardLabel"
|
/>
|
||||||
defaultMessage="Add to dashboard"
|
)
|
||||||
/>
|
: null;
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiIconTip
|
|
||||||
type="iInCircle"
|
|
||||||
content={
|
|
||||||
<FormattedMessage
|
|
||||||
id="presentationUtil.saveModalDashboard.dashboardInfoTooltip"
|
|
||||||
defaultMessage="Items added to a dashboard will not appear in the library and must be edited from the dashboard."
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
}
|
|
||||||
hasChildLabel={false}
|
|
||||||
>
|
|
||||||
<EuiPanel color="subdued" hasShadow={false} data-test-subj="add-to-dashboard-options">
|
|
||||||
<div>
|
|
||||||
<EuiRadio
|
|
||||||
checked={dashboardOption === 'existing'}
|
|
||||||
id="existing-dashboard-option"
|
|
||||||
name="dashboard-option"
|
|
||||||
label={i18n.translate(
|
|
||||||
'presentationUtil.saveModalDashboard.existingDashboardOptionLabel',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Existing',
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
onChange={() => setDashboardOption('existing')}
|
|
||||||
disabled={isDisabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="savAddDashboard__searchDashboards">
|
|
||||||
<DashboardPicker
|
|
||||||
savedObjectsClient={savedObjectsClient}
|
|
||||||
isDisabled={dashboardOption !== 'existing'}
|
|
||||||
onChange={(dash) => {
|
|
||||||
setSelectedDashboard(dash);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<EuiSpacer size="s" />
|
|
||||||
|
|
||||||
<EuiRadio
|
|
||||||
checked={dashboardOption === 'new'}
|
|
||||||
id="new-dashboard-option"
|
|
||||||
name="dashboard-option"
|
|
||||||
label={i18n.translate(
|
|
||||||
'presentationUtil.saveModalDashboard.newDashboardOptionLabel',
|
|
||||||
{
|
|
||||||
defaultMessage: 'New',
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
onChange={() => setDashboardOption('new')}
|
|
||||||
disabled={isDisabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EuiSpacer size="s" />
|
|
||||||
|
|
||||||
<EuiRadio
|
|
||||||
checked={dashboardOption === null}
|
|
||||||
id="add-to-library-option"
|
|
||||||
name="dashboard-option"
|
|
||||||
label={i18n.translate('presentationUtil.saveModalDashboard.libraryOptionLabel', {
|
|
||||||
defaultMessage: 'No dashboard, but add to library',
|
|
||||||
})}
|
|
||||||
onChange={() => setDashboardOption(null)}
|
|
||||||
disabled={isDisabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</EuiPanel>
|
|
||||||
</EuiFormRow>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCopyOnSaveChange = (newCopyOnSave: boolean) => {
|
const onCopyOnSaveChange = (newCopyOnSave: boolean) => {
|
||||||
setDashboardOption(null);
|
setDashboardOption(null);
|
||||||
|
@ -159,7 +81,7 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
||||||
|
|
||||||
// Don't save with a dashboard ID if we're
|
// Don't save with a dashboard ID if we're
|
||||||
// just updating an existing visualization
|
// just updating an existing visualization
|
||||||
if (!(!onSaveProps.newCopyOnSave && documentInfo.id)) {
|
if (!(!onSaveProps.newCopyOnSave && documentId)) {
|
||||||
if (dashboardOption === 'existing') {
|
if (dashboardOption === 'existing') {
|
||||||
dashboardId = selectedDashboard?.id || null;
|
dashboardId = selectedDashboard?.id || null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,13 +93,14 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveLibraryLabel =
|
const saveLibraryLabel =
|
||||||
!copyOnSave && documentInfo.id
|
!copyOnSave && documentId
|
||||||
? i18n.translate('presentationUtil.saveModalDashboard.saveLabel', {
|
? i18n.translate('presentationUtil.saveModalDashboard.saveLabel', {
|
||||||
defaultMessage: 'Save',
|
defaultMessage: 'Save',
|
||||||
})
|
})
|
||||||
: i18n.translate('presentationUtil.saveModalDashboard.saveToLibraryLabel', {
|
: i18n.translate('presentationUtil.saveModalDashboard.saveToLibraryLabel', {
|
||||||
defaultMessage: 'Save and add to library',
|
defaultMessage: 'Save and add to library',
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveDashboardLabel = i18n.translate(
|
const saveDashboardLabel = i18n.translate(
|
||||||
'presentationUtil.saveModalDashboard.saveAndGoToDashboardLabel',
|
'presentationUtil.saveModalDashboard.saveAndGoToDashboardLabel',
|
||||||
{
|
{
|
||||||
|
@ -192,18 +115,20 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
||||||
return (
|
return (
|
||||||
<SavedObjectSaveModal
|
<SavedObjectSaveModal
|
||||||
onSave={onModalSave}
|
onSave={onModalSave}
|
||||||
onClose={props.onClose}
|
|
||||||
title={documentInfo.title}
|
title={documentInfo.title}
|
||||||
showCopyOnSave={documentInfo.id ? true : false}
|
showCopyOnSave={documentId ? true : false}
|
||||||
initialCopyOnSave={initialCopyOnSave}
|
|
||||||
confirmButtonLabel={confirmButtonLabel}
|
|
||||||
objectType={props.objectType}
|
|
||||||
options={dashboardOption === null ? tagOptions : undefined} // Show tags when not adding to dashboard
|
options={dashboardOption === null ? tagOptions : undefined} // Show tags when not adding to dashboard
|
||||||
rightOptions={renderDashboardSelect}
|
|
||||||
description={documentInfo.description}
|
description={documentInfo.description}
|
||||||
showDescription={true}
|
showDescription={true}
|
||||||
isValid={isValid}
|
{...{
|
||||||
onCopyOnSaveChange={onCopyOnSaveChange}
|
confirmButtonLabel,
|
||||||
|
initialCopyOnSave,
|
||||||
|
isValid,
|
||||||
|
objectType,
|
||||||
|
onClose,
|
||||||
|
onCopyOnSaveChange,
|
||||||
|
rightOptions,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { StorybookParams } from '../services/storybook';
|
||||||
|
import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: SaveModalDashboardSelector,
|
||||||
|
title: 'Save Modal Dashboard Selector',
|
||||||
|
description: 'A selector for determining where an object will be saved after it is created.',
|
||||||
|
argTypes: {
|
||||||
|
hasDocumentId: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
copyOnSave: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
canCreateNewDashboards: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
canEditDashboards: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Example({
|
||||||
|
copyOnSave,
|
||||||
|
hasDocumentId,
|
||||||
|
}: {
|
||||||
|
copyOnSave: boolean;
|
||||||
|
hasDocumentId: boolean;
|
||||||
|
} & StorybookParams) {
|
||||||
|
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>('existing');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SaveModalDashboardSelector
|
||||||
|
onSelectDashboard={action('onSelect')}
|
||||||
|
onChange={setDashboardOption}
|
||||||
|
dashboardOption={dashboardOption}
|
||||||
|
copyOnSave={copyOnSave}
|
||||||
|
documentId={hasDocumentId ? 'abc' : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiFormRow,
|
||||||
|
EuiRadio,
|
||||||
|
EuiIconTip,
|
||||||
|
EuiPanel,
|
||||||
|
EuiSpacer,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { pluginServices } from '../services';
|
||||||
|
import { DashboardPicker, DashboardPickerProps } from './dashboard_picker';
|
||||||
|
|
||||||
|
import './saved_object_save_modal_dashboard.scss';
|
||||||
|
|
||||||
|
export interface SaveModalDashboardSelectorProps {
|
||||||
|
copyOnSave: boolean;
|
||||||
|
documentId?: string;
|
||||||
|
onSelectDashboard: DashboardPickerProps['onChange'];
|
||||||
|
|
||||||
|
dashboardOption: 'new' | 'existing' | null;
|
||||||
|
onChange: (dashboardOption: 'new' | 'existing' | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProps) {
|
||||||
|
const { documentId, onSelectDashboard, dashboardOption, onChange, copyOnSave } = props;
|
||||||
|
const { capabilities } = pluginServices.getHooks();
|
||||||
|
const { canCreateNewDashboards, canEditDashboards } = capabilities.useService();
|
||||||
|
|
||||||
|
const isDisabled = !copyOnSave && !!documentId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EuiFormRow
|
||||||
|
label={
|
||||||
|
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="presentationUtil.saveModalDashboard.addToDashboardLabel"
|
||||||
|
defaultMessage="Add to dashboard"
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiIconTip
|
||||||
|
type="iInCircle"
|
||||||
|
content={
|
||||||
|
<FormattedMessage
|
||||||
|
id="presentationUtil.saveModalDashboard.dashboardInfoTooltip"
|
||||||
|
defaultMessage="Items added to a dashboard will not appear in the library and must be edited from the dashboard."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
}
|
||||||
|
hasChildLabel={false}
|
||||||
|
>
|
||||||
|
<EuiPanel color="subdued" hasShadow={false} data-test-subj="add-to-dashboard-options">
|
||||||
|
<div>
|
||||||
|
{canEditDashboards() && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<EuiRadio
|
||||||
|
checked={dashboardOption === 'existing'}
|
||||||
|
id="existing-dashboard-option"
|
||||||
|
name="dashboard-option"
|
||||||
|
label={i18n.translate(
|
||||||
|
'presentationUtil.saveModalDashboard.existingDashboardOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Existing',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onChange={() => onChange('existing')}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
<div className="savAddDashboard__searchDashboards">
|
||||||
|
<DashboardPicker
|
||||||
|
isDisabled={dashboardOption !== 'existing'}
|
||||||
|
onChange={onSelectDashboard}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{canCreateNewDashboards() && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<EuiRadio
|
||||||
|
checked={dashboardOption === 'new'}
|
||||||
|
id="new-dashboard-option"
|
||||||
|
name="dashboard-option"
|
||||||
|
label={i18n.translate(
|
||||||
|
'presentationUtil.saveModalDashboard.newDashboardOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'New',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onChange={() => onChange('new')}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<EuiRadio
|
||||||
|
checked={dashboardOption === null}
|
||||||
|
id="add-to-library-option"
|
||||||
|
name="dashboard-option"
|
||||||
|
label={i18n.translate('presentationUtil.saveModalDashboard.libraryOptionLabel', {
|
||||||
|
defaultMessage: 'No dashboard, but add to library',
|
||||||
|
})}
|
||||||
|
onChange={() => onChange(null)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</EuiPanel>
|
||||||
|
</EuiFormRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -10,9 +10,11 @@ import { PresentationUtilPlugin } from './plugin';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SavedObjectSaveModalDashboard,
|
SavedObjectSaveModalDashboard,
|
||||||
DashboardSaveModalProps,
|
SaveModalDashboardProps,
|
||||||
} from './components/saved_object_save_modal_dashboard';
|
} from './components/saved_object_save_modal_dashboard';
|
||||||
|
|
||||||
|
export { DashboardPicker } from './components/dashboard_picker';
|
||||||
|
|
||||||
export function plugin() {
|
export function plugin() {
|
||||||
return new PresentationUtilPlugin();
|
return new PresentationUtilPlugin();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,39 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||||
import { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types';
|
import { pluginServices } from './services';
|
||||||
|
import { registry } from './services/kibana';
|
||||||
|
import {
|
||||||
|
PresentationUtilPluginSetup,
|
||||||
|
PresentationUtilPluginStart,
|
||||||
|
PresentationUtilPluginSetupDeps,
|
||||||
|
PresentationUtilPluginStartDeps,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export class PresentationUtilPlugin
|
export class PresentationUtilPlugin
|
||||||
implements Plugin<PresentationUtilPluginSetup, PresentationUtilPluginStart> {
|
implements
|
||||||
public setup(core: CoreSetup): PresentationUtilPluginSetup {
|
Plugin<
|
||||||
|
PresentationUtilPluginSetup,
|
||||||
|
PresentationUtilPluginStart,
|
||||||
|
PresentationUtilPluginSetupDeps,
|
||||||
|
PresentationUtilPluginStartDeps
|
||||||
|
> {
|
||||||
|
public setup(
|
||||||
|
_coreSetup: CoreSetup<PresentationUtilPluginSetup>,
|
||||||
|
_setupPlugins: PresentationUtilPluginSetupDeps
|
||||||
|
): PresentationUtilPluginSetup {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(core: CoreStart): PresentationUtilPluginStart {
|
public async start(
|
||||||
return {};
|
coreStart: CoreStart,
|
||||||
|
startPlugins: PresentationUtilPluginStartDeps
|
||||||
|
): Promise<PresentationUtilPluginStart> {
|
||||||
|
pluginServices.setRegistry(registry.start({ coreStart, startPlugins }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
ContextProvider: pluginServices.getContextProvider(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop() {}
|
public stop() {}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { CoreStart, AppUpdater } from 'src/core/public';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory function for creating a service.
|
||||||
|
*
|
||||||
|
* The `Service` generic determines the shape of the API being produced.
|
||||||
|
* The `StartParameters` generic determines what parameters are expected to
|
||||||
|
* create the service.
|
||||||
|
*/
|
||||||
|
export type PluginServiceFactory<Service, Parameters = {}> = (params: Parameters) => Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters necessary to create a Kibana-based service, (e.g. during Plugin
|
||||||
|
* startup or setup).
|
||||||
|
*
|
||||||
|
* The `Start` generic refers to the specific Plugin `TPluginsStart`.
|
||||||
|
*/
|
||||||
|
export interface KibanaPluginServiceParams<Start extends {}> {
|
||||||
|
coreStart: CoreStart;
|
||||||
|
startPlugins: Start;
|
||||||
|
appUpdater?: BehaviorSubject<AppUpdater>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory function for creating a Kibana-based service.
|
||||||
|
*
|
||||||
|
* The `Service` generic determines the shape of the API being produced.
|
||||||
|
* The `Setup` generic refers to the specific Plugin `TPluginsSetup`.
|
||||||
|
* The `Start` generic refers to the specific Plugin `TPluginsStart`.
|
||||||
|
*/
|
||||||
|
export type KibanaPluginServiceFactory<Service, Start extends {}> = (
|
||||||
|
params: KibanaPluginServiceParams<Start>
|
||||||
|
) => Service;
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mapValues } from 'lodash';
|
||||||
|
|
||||||
|
import { PluginServiceRegistry } from './registry';
|
||||||
|
|
||||||
|
export { PluginServiceRegistry } from './registry';
|
||||||
|
export { PluginServiceProvider, PluginServiceProviders } from './provider';
|
||||||
|
export {
|
||||||
|
PluginServiceFactory,
|
||||||
|
KibanaPluginServiceFactory,
|
||||||
|
KibanaPluginServiceParams,
|
||||||
|
} from './factory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `PluginServices` is a top-level class for specifying and accessing services within a plugin.
|
||||||
|
*
|
||||||
|
* A `PluginServices` object can be provided with a `PluginServiceRegistry` at any time, which will
|
||||||
|
* then be used to provide services to any component that accesses it.
|
||||||
|
*
|
||||||
|
* The `Services` generic determines the shape of all service APIs being produced.
|
||||||
|
*/
|
||||||
|
export class PluginServices<Services> {
|
||||||
|
private registry: PluginServiceRegistry<Services, any> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supply a `PluginServiceRegistry` for the class to use to provide services and context.
|
||||||
|
*
|
||||||
|
* @param registry A setup and started `PluginServiceRegistry`.
|
||||||
|
*/
|
||||||
|
setRegistry(registry: PluginServiceRegistry<Services, any> | null) {
|
||||||
|
if (registry && !registry.isStarted()) {
|
||||||
|
throw new Error('Registry has not been started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a registry has been provided, false otherwise.
|
||||||
|
*/
|
||||||
|
hasRegistry() {
|
||||||
|
return !!this.registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private getter that will enforce proper setup throughout the class.
|
||||||
|
*/
|
||||||
|
private getRegistry() {
|
||||||
|
if (!this.registry) {
|
||||||
|
throw new Error('No registry has been provided.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the React Context Provider that will supply services.
|
||||||
|
*/
|
||||||
|
getContextProvider() {
|
||||||
|
return this.getRegistry().getContextProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a map of React Hooks that can be used in React components.
|
||||||
|
*/
|
||||||
|
getHooks(): { [K in keyof Services]: { useService: () => Services[K] } } {
|
||||||
|
const registry = this.getRegistry();
|
||||||
|
const providers = registry.getServiceProviders();
|
||||||
|
|
||||||
|
// @ts-expect-error Need to fix this; the type isn't fully understood when inferred.
|
||||||
|
return mapValues(providers, (provider) => ({
|
||||||
|
useService: provider.getUseServiceHook(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { PluginServiceFactory } from './factory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of `PluginServiceProvider` objects, keyed by the `Services` API generic.
|
||||||
|
*
|
||||||
|
* The `Services` generic determines the shape of all service APIs being produced.
|
||||||
|
* The `StartParameters` generic determines what parameters are expected to
|
||||||
|
* start the service.
|
||||||
|
*/
|
||||||
|
export type PluginServiceProviders<Services, StartParameters = {}> = {
|
||||||
|
[K in keyof Services]: PluginServiceProvider<Services[K], StartParameters>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which uses a given factory to start, stop or provide a service.
|
||||||
|
*
|
||||||
|
* The `Service` generic determines the shape of the API being produced.
|
||||||
|
* The `StartParameters` generic determines what parameters are expected to
|
||||||
|
* start the service.
|
||||||
|
*/
|
||||||
|
export class PluginServiceProvider<Service extends {}, StartParameters = {}> {
|
||||||
|
private factory: PluginServiceFactory<Service, StartParameters>;
|
||||||
|
private context = createContext<Service | null>(null);
|
||||||
|
private pluginService: Service | null = null;
|
||||||
|
public readonly Provider: React.FC = ({ children }) => {
|
||||||
|
return <this.context.Provider value={this.getService()}>{children}</this.context.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(factory: PluginServiceFactory<Service, StartParameters>) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.context.displayName = 'PluginServiceContext';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private getter that will enforce proper setup throughout the class.
|
||||||
|
*/
|
||||||
|
private getService() {
|
||||||
|
if (!this.pluginService) {
|
||||||
|
throw new Error('Service not started');
|
||||||
|
}
|
||||||
|
return this.pluginService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the service.
|
||||||
|
*
|
||||||
|
* @param params Parameters used to start the service.
|
||||||
|
*/
|
||||||
|
start(params: StartParameters) {
|
||||||
|
this.pluginService = this.factory(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function for providing a Context hook for the service.
|
||||||
|
*/
|
||||||
|
getUseServiceHook() {
|
||||||
|
return () => {
|
||||||
|
const service = useContext(this.context);
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
throw new Error('Provider is not set up correctly');
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the service.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
this.pluginService = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { values } from 'lodash';
|
||||||
|
import { PluginServiceProvider, PluginServiceProviders } from './provider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `PluginServiceRegistry` maintains a set of service providers which can be collectively
|
||||||
|
* started, stopped or retreived.
|
||||||
|
*
|
||||||
|
* The `Services` generic determines the shape of all service APIs being produced.
|
||||||
|
* The `StartParameters` generic determines what parameters are expected to
|
||||||
|
* start the service.
|
||||||
|
*/
|
||||||
|
export class PluginServiceRegistry<Services, StartParameters = {}> {
|
||||||
|
private providers: PluginServiceProviders<Services, StartParameters>;
|
||||||
|
private _isStarted = false;
|
||||||
|
|
||||||
|
constructor(providers: PluginServiceProviders<Services, StartParameters>) {
|
||||||
|
this.providers = providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the registry has been started, false otherwise.
|
||||||
|
*/
|
||||||
|
isStarted() {
|
||||||
|
return this._isStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of `PluginServiceProvider` objects.
|
||||||
|
*/
|
||||||
|
getServiceProviders() {
|
||||||
|
if (!this._isStarted) {
|
||||||
|
throw new Error('Registry not started');
|
||||||
|
}
|
||||||
|
return this.providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a React Context Provider for use in consuming applications.
|
||||||
|
*/
|
||||||
|
getContextProvider() {
|
||||||
|
// Collect and combine Context.Provider elements from each Service Provider into a single
|
||||||
|
// Functional Component.
|
||||||
|
const provider: React.FC = ({ children }) => (
|
||||||
|
<>
|
||||||
|
{values<PluginServiceProvider<any, any>>(this.getServiceProviders()).reduceRight(
|
||||||
|
(acc, serviceProvider) => {
|
||||||
|
return <serviceProvider.Provider>{acc}</serviceProvider.Provider>;
|
||||||
|
},
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the registry.
|
||||||
|
*
|
||||||
|
* @param params Parameters used to start the registry.
|
||||||
|
*/
|
||||||
|
start(params: StartParameters) {
|
||||||
|
values<PluginServiceProvider<any, any>>(this.providers).map((serviceProvider) =>
|
||||||
|
serviceProvider.start(params)
|
||||||
|
);
|
||||||
|
this._isStarted = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the registry.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
values<PluginServiceProvider<any, any>>(this.providers).map((serviceProvider) =>
|
||||||
|
serviceProvider.stop()
|
||||||
|
);
|
||||||
|
this._isStarted = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
31
src/plugins/presentation_util/public/services/index.ts
Normal file
31
src/plugins/presentation_util/public/services/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SimpleSavedObject } from 'src/core/public';
|
||||||
|
import { DashboardSavedObject } from 'src/plugins/dashboard/public';
|
||||||
|
import { PluginServices } from './create';
|
||||||
|
export interface PresentationDashboardsService {
|
||||||
|
findDashboards: (
|
||||||
|
query: string,
|
||||||
|
fields: string[]
|
||||||
|
) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||||
|
findDashboardsByTitle: (title: string) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresentationCapabilitiesService {
|
||||||
|
canAccessDashboards: () => boolean;
|
||||||
|
canCreateNewDashboards: () => boolean;
|
||||||
|
canEditDashboards: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresentationUtilServices {
|
||||||
|
dashboards: PresentationDashboardsService;
|
||||||
|
capabilities: PresentationCapabilitiesService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pluginServices = new PluginServices<PresentationUtilServices>();
|
|
@ -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
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PresentationUtilPluginStartDeps } from '../../types';
|
||||||
|
import { KibanaPluginServiceFactory } from '../create';
|
||||||
|
import { PresentationCapabilitiesService } from '..';
|
||||||
|
|
||||||
|
export type CapabilitiesServiceFactory = KibanaPluginServiceFactory<
|
||||||
|
PresentationCapabilitiesService,
|
||||||
|
PresentationUtilPluginStartDeps
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({ coreStart }) => {
|
||||||
|
const { dashboard } = coreStart.application.capabilities;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canAccessDashboards: () => Boolean(dashboard.show),
|
||||||
|
canCreateNewDashboards: () => Boolean(dashboard.createNew),
|
||||||
|
canEditDashboards: () => !Boolean(dashboard.hideWriteControls),
|
||||||
|
};
|
||||||
|
};
|
|
@ -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
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DashboardSavedObject } from 'src/plugins/dashboard/public';
|
||||||
|
|
||||||
|
import { PresentationUtilPluginStartDeps } from '../../types';
|
||||||
|
import { KibanaPluginServiceFactory } from '../create';
|
||||||
|
import { PresentationDashboardsService } from '..';
|
||||||
|
|
||||||
|
export type DashboardsServiceFactory = KibanaPluginServiceFactory<
|
||||||
|
PresentationDashboardsService,
|
||||||
|
PresentationUtilPluginStartDeps
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const dashboardsServiceFactory: DashboardsServiceFactory = ({ coreStart }) => {
|
||||||
|
const findDashboards = async (query: string = '', fields: string[] = []) => {
|
||||||
|
const { find } = coreStart.savedObjects.client;
|
||||||
|
|
||||||
|
const { savedObjects } = await find<DashboardSavedObject>({
|
||||||
|
type: 'dashboard',
|
||||||
|
search: `${query}*`,
|
||||||
|
searchFields: fields,
|
||||||
|
});
|
||||||
|
|
||||||
|
return savedObjects;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findDashboardsByTitle = async (title: string = '') => findDashboards(title, ['title']);
|
||||||
|
|
||||||
|
return {
|
||||||
|
findDashboards,
|
||||||
|
findDashboardsByTitle,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { dashboardsServiceFactory } from './dashboards';
|
||||||
|
import { capabilitiesServiceFactory } from './capabilities';
|
||||||
|
import {
|
||||||
|
PluginServiceProviders,
|
||||||
|
KibanaPluginServiceParams,
|
||||||
|
PluginServiceProvider,
|
||||||
|
PluginServiceRegistry,
|
||||||
|
} from '../create';
|
||||||
|
import { PresentationUtilPluginStartDeps } from '../../types';
|
||||||
|
import { PresentationUtilServices } from '..';
|
||||||
|
|
||||||
|
export { dashboardsServiceFactory } from './dashboards';
|
||||||
|
export { capabilitiesServiceFactory } from './capabilities';
|
||||||
|
|
||||||
|
export const providers: PluginServiceProviders<
|
||||||
|
PresentationUtilServices,
|
||||||
|
KibanaPluginServiceParams<PresentationUtilPluginStartDeps>
|
||||||
|
> = {
|
||||||
|
dashboards: new PluginServiceProvider(dashboardsServiceFactory),
|
||||||
|
capabilities: new PluginServiceProvider(capabilitiesServiceFactory),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registry = new PluginServiceRegistry<
|
||||||
|
PresentationUtilServices,
|
||||||
|
KibanaPluginServiceParams<PresentationUtilPluginStartDeps>
|
||||||
|
>(providers);
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginServiceFactory } from '../create';
|
||||||
|
import { StorybookParams } from '.';
|
||||||
|
import { PresentationCapabilitiesService } from '..';
|
||||||
|
|
||||||
|
type CapabilitiesServiceFactory = PluginServiceFactory<
|
||||||
|
PresentationCapabilitiesService,
|
||||||
|
StorybookParams
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({
|
||||||
|
canAccessDashboards,
|
||||||
|
canCreateNewDashboards,
|
||||||
|
canEditDashboards,
|
||||||
|
}) => {
|
||||||
|
const check = (value: boolean = true) => value;
|
||||||
|
return {
|
||||||
|
canAccessDashboards: () => check(canAccessDashboards),
|
||||||
|
canCreateNewDashboards: () => check(canCreateNewDashboards),
|
||||||
|
canEditDashboards: () => check(canEditDashboards),
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginServices, PluginServiceProviders, PluginServiceProvider } from '../create';
|
||||||
|
import { dashboardsServiceFactory } from '../stub/dashboards';
|
||||||
|
import { capabilitiesServiceFactory } from './capabilities';
|
||||||
|
import { PresentationUtilServices } from '..';
|
||||||
|
|
||||||
|
export { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry } from '../create';
|
||||||
|
export { PresentationUtilServices } from '..';
|
||||||
|
|
||||||
|
export interface StorybookParams {
|
||||||
|
canAccessDashboards?: boolean;
|
||||||
|
canCreateNewDashboards?: boolean;
|
||||||
|
canEditDashboards?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const providers: PluginServiceProviders<PresentationUtilServices, StorybookParams> = {
|
||||||
|
dashboards: new PluginServiceProvider(dashboardsServiceFactory),
|
||||||
|
capabilities: new PluginServiceProvider(capabilitiesServiceFactory),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pluginServices = new PluginServices<PresentationUtilServices>();
|
|
@ -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
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginServiceFactory } from '../create';
|
||||||
|
import { PresentationCapabilitiesService } from '..';
|
||||||
|
|
||||||
|
type CapabilitiesServiceFactory = PluginServiceFactory<PresentationCapabilitiesService>;
|
||||||
|
|
||||||
|
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = () => ({
|
||||||
|
canAccessDashboards: () => true,
|
||||||
|
canCreateNewDashboards: () => true,
|
||||||
|
canEditDashboards: () => true,
|
||||||
|
});
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginServiceFactory } from '../create';
|
||||||
|
import { PresentationDashboardsService } from '..';
|
||||||
|
|
||||||
|
// TODO (clint): Create set of dashboards to stub and return.
|
||||||
|
|
||||||
|
type DashboardsServiceFactory = PluginServiceFactory<PresentationDashboardsService>;
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dashboardsServiceFactory: DashboardsServiceFactory = () => ({
|
||||||
|
findDashboards: async (query: string = '', _fields: string[] = []) => {
|
||||||
|
if (!query) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(2000);
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
findDashboardsByTitle: async (title: string) => {
|
||||||
|
if (!title) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(2000);
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
22
src/plugins/presentation_util/public/services/stub/index.ts
Normal file
22
src/plugins/presentation_util/public/services/stub/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { dashboardsServiceFactory } from './dashboards';
|
||||||
|
import { capabilitiesServiceFactory } from './capabilities';
|
||||||
|
import { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry } from '../create';
|
||||||
|
import { PresentationUtilServices } from '..';
|
||||||
|
|
||||||
|
export { dashboardsServiceFactory } from './dashboards';
|
||||||
|
export { capabilitiesServiceFactory } from './capabilities';
|
||||||
|
|
||||||
|
export const providers: PluginServiceProviders<PresentationUtilServices> = {
|
||||||
|
dashboards: new PluginServiceProvider(dashboardsServiceFactory),
|
||||||
|
capabilities: new PluginServiceProvider(capabilitiesServiceFactory),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registry = new PluginServiceRegistry<PresentationUtilServices>(providers);
|
|
@ -8,5 +8,12 @@
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface PresentationUtilPluginSetup {}
|
export interface PresentationUtilPluginSetup {}
|
||||||
|
|
||||||
|
export interface PresentationUtilPluginStart {
|
||||||
|
ContextProvider: React.FC;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface PresentationUtilPluginStart {}
|
export interface PresentationUtilPluginSetupDeps {}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface PresentationUtilPluginStartDeps {}
|
||||||
|
|
28
src/plugins/presentation_util/storybook/decorator.tsx
Normal file
28
src/plugins/presentation_util/storybook/decorator.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { DecoratorFn } from '@storybook/react';
|
||||||
|
import { I18nProvider } from '@kbn/i18n/react';
|
||||||
|
import { pluginServices } from '../public/services';
|
||||||
|
import { PresentationUtilServices } from '../public/services';
|
||||||
|
import { providers, StorybookParams } from '../public/services/storybook';
|
||||||
|
import { PluginServiceRegistry } from '../public/services/create';
|
||||||
|
|
||||||
|
export const servicesContextDecorator: DecoratorFn = (story: Function, storybook) => {
|
||||||
|
const registry = new PluginServiceRegistry<PresentationUtilServices, StorybookParams>(providers);
|
||||||
|
pluginServices.setRegistry(registry.start(storybook.args));
|
||||||
|
const ContextProvider = pluginServices.getContextProvider();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18nProvider>
|
||||||
|
<ContextProvider>{story()}</ContextProvider>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
};
|
19
src/plugins/presentation_util/storybook/main.ts
Normal file
19
src/plugins/presentation_util/storybook/main.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Configuration } from 'webpack';
|
||||||
|
import { defaultConfig } from '@kbn/storybook';
|
||||||
|
import webpackConfig from '@kbn/storybook/target/webpack.config';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...defaultConfig,
|
||||||
|
addons: ['@storybook/addon-essentials'],
|
||||||
|
webpackFinal: (config: Configuration) => {
|
||||||
|
return webpackConfig({ config });
|
||||||
|
},
|
||||||
|
};
|
21
src/plugins/presentation_util/storybook/manager.ts
Normal file
21
src/plugins/presentation_util/storybook/manager.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addons } from '@storybook/addons';
|
||||||
|
import { create } from '@storybook/theming';
|
||||||
|
import { PANEL_ID } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
addons.setConfig({
|
||||||
|
theme: create({
|
||||||
|
base: 'light',
|
||||||
|
brandTitle: 'Kibana Presentation Utility Storybook',
|
||||||
|
brandUrl: 'https://github.com/elastic/kibana/tree/master/src/plugins/presentation_util',
|
||||||
|
}),
|
||||||
|
showPanel: true.valueOf,
|
||||||
|
selectedPanel: PANEL_ID,
|
||||||
|
});
|
29
src/plugins/presentation_util/storybook/preview.tsx
Normal file
29
src/plugins/presentation_util/storybook/preview.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* and the Server Side Public License, v 1; you may not use this file except in
|
||||||
|
* compliance with, at your election, the Elastic License or the Server Side
|
||||||
|
* Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { addDecorator } from '@storybook/react';
|
||||||
|
import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
import { servicesContextDecorator } from './decorator';
|
||||||
|
|
||||||
|
addDecorator(servicesContextDecorator);
|
||||||
|
|
||||||
|
export const parameters = {
|
||||||
|
docs: {
|
||||||
|
page: () => (
|
||||||
|
<>
|
||||||
|
<Title />
|
||||||
|
<Subtitle />
|
||||||
|
<Description />
|
||||||
|
<Primary />
|
||||||
|
<Stories />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
|
@ -7,7 +7,7 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true
|
"declarationMap": true
|
||||||
},
|
},
|
||||||
"include": ["common/**/*", "public/**/*"],
|
"include": ["common/**/*", "public/**/*", "storybook/**/*", "../../../typings/**/*"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../core/tsconfig.json" },
|
{ "path": "../../core/tsconfig.json" },
|
||||||
{ "path": "../dashboard/tsconfig.json" },
|
{ "path": "../dashboard/tsconfig.json" },
|
||||||
|
|
|
@ -31,7 +31,8 @@ interface MinimalSaveModalProps {
|
||||||
|
|
||||||
export function showSaveModal(
|
export function showSaveModal(
|
||||||
saveModal: React.ReactElement<MinimalSaveModalProps>,
|
saveModal: React.ReactElement<MinimalSaveModalProps>,
|
||||||
I18nContext: I18nStart['Context']
|
I18nContext: I18nStart['Context'],
|
||||||
|
Wrapper?: React.FC
|
||||||
) {
|
) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
|
@ -55,5 +56,13 @@ export function showSaveModal(
|
||||||
onClose: closeModal,
|
onClose: closeModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
ReactDOM.render(<I18nContext>{element}</I18nContext>, container);
|
const wrappedElement = Wrapper ? (
|
||||||
|
<I18nContext>
|
||||||
|
<Wrapper>{element}</Wrapper>
|
||||||
|
</I18nContext>
|
||||||
|
) : (
|
||||||
|
<I18nContext>{element}</I18nContext>
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDOM.render(wrappedElement, container);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"visualizations",
|
"visualizations",
|
||||||
"embeddable",
|
"embeddable",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"uiActions"
|
"uiActions",
|
||||||
|
"presentationUtil"
|
||||||
],
|
],
|
||||||
"optionalPlugins": [
|
"optionalPlugins": [
|
||||||
"home",
|
"home",
|
||||||
|
@ -22,7 +23,6 @@
|
||||||
"kibanaUtils",
|
"kibanaUtils",
|
||||||
"kibanaReact",
|
"kibanaReact",
|
||||||
"home",
|
"home",
|
||||||
"presentationUtil",
|
|
||||||
"discover"
|
"discover"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,6 @@ const TopNav = ({
|
||||||
},
|
},
|
||||||
[visInstance.embeddableHandler]
|
[visInstance.embeddableHandler]
|
||||||
);
|
);
|
||||||
const savedObjectsClient = services.savedObjects.client;
|
|
||||||
|
|
||||||
const config = useMemo(() => {
|
const config = useMemo(() => {
|
||||||
if (isEmbeddableRendered) {
|
if (isEmbeddableRendered) {
|
||||||
|
@ -85,7 +84,6 @@ const TopNav = ({
|
||||||
stateContainer,
|
stateContainer,
|
||||||
visualizationIdFromUrl,
|
visualizationIdFromUrl,
|
||||||
stateTransfer: services.stateTransferService,
|
stateTransfer: services.stateTransferService,
|
||||||
savedObjectsClient,
|
|
||||||
embeddableId,
|
embeddableId,
|
||||||
},
|
},
|
||||||
services
|
services
|
||||||
|
@ -104,7 +102,6 @@ const TopNav = ({
|
||||||
visualizationIdFromUrl,
|
visualizationIdFromUrl,
|
||||||
services,
|
services,
|
||||||
embeddableId,
|
embeddableId,
|
||||||
savedObjectsClient,
|
|
||||||
]);
|
]);
|
||||||
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>(
|
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>(
|
||||||
vis.data.indexPattern ? [vis.data.indexPattern] : []
|
vis.data.indexPattern ? [vis.data.indexPattern] : []
|
||||||
|
|
|
@ -30,9 +30,11 @@ export const renderApp = (
|
||||||
const app = (
|
const app = (
|
||||||
<Router history={services.history}>
|
<Router history={services.history}>
|
||||||
<KibanaContextProvider services={services}>
|
<KibanaContextProvider services={services}>
|
||||||
<services.i18n.Context>
|
<services.presentationUtil.ContextProvider>
|
||||||
<VisualizeApp onAppLeave={onAppLeave} />
|
<services.i18n.Context>
|
||||||
</services.i18n.Context>
|
<VisualizeApp onAppLeave={onAppLeave} />
|
||||||
|
</services.i18n.Context>
|
||||||
|
</services.presentationUtil.ContextProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { SharePluginStart } from 'src/plugins/share/public';
|
||||||
import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
|
import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
|
||||||
import { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
|
import { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
|
||||||
import { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
|
import { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
|
||||||
|
import { PresentationUtilPluginStart } from 'src/plugins/presentation_util/public';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { DashboardStart } from '../../../dashboard/public';
|
import { DashboardStart } from '../../../dashboard/public';
|
||||||
import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
|
import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
|
||||||
|
@ -93,6 +94,7 @@ export interface VisualizeServices extends CoreStart {
|
||||||
dashboard: DashboardStart;
|
dashboard: DashboardStart;
|
||||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||||
savedObjectsTagging?: SavedObjectsTaggingApi;
|
savedObjectsTagging?: SavedObjectsTaggingApi;
|
||||||
|
presentationUtil: PresentationUtilPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SavedVisInstance {
|
export interface SavedVisInstance {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
} from '../../../../saved_objects/public';
|
} from '../../../../saved_objects/public';
|
||||||
import { SavedObjectSaveModalDashboard } from '../../../../presentation_util/public';
|
import { SavedObjectSaveModalDashboard } from '../../../../presentation_util/public';
|
||||||
import { unhashUrl } from '../../../../kibana_utils/public';
|
import { unhashUrl } from '../../../../kibana_utils/public';
|
||||||
import { SavedObjectsClientContract } from '../../../../../core/public';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
VisualizeServices,
|
VisualizeServices,
|
||||||
|
@ -50,7 +49,6 @@ interface TopNavConfigParams {
|
||||||
stateContainer: VisualizeAppStateContainer;
|
stateContainer: VisualizeAppStateContainer;
|
||||||
visualizationIdFromUrl?: string;
|
visualizationIdFromUrl?: string;
|
||||||
stateTransfer: EmbeddableStateTransfer;
|
stateTransfer: EmbeddableStateTransfer;
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
embeddableId?: string;
|
embeddableId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +70,6 @@ export const getTopNavConfig = (
|
||||||
hasUnappliedChanges,
|
hasUnappliedChanges,
|
||||||
visInstance,
|
visInstance,
|
||||||
stateContainer,
|
stateContainer,
|
||||||
savedObjectsClient,
|
|
||||||
visualizationIdFromUrl,
|
visualizationIdFromUrl,
|
||||||
stateTransfer,
|
stateTransfer,
|
||||||
embeddableId,
|
embeddableId,
|
||||||
|
@ -88,6 +85,7 @@ export const getTopNavConfig = (
|
||||||
i18n: { Context: I18nContext },
|
i18n: { Context: I18nContext },
|
||||||
dashboard,
|
dashboard,
|
||||||
savedObjectsTagging,
|
savedObjectsTagging,
|
||||||
|
presentationUtil,
|
||||||
}: VisualizeServices
|
}: VisualizeServices
|
||||||
) => {
|
) => {
|
||||||
const { vis, embeddableHandler } = visInstance;
|
const { vis, embeddableHandler } = visInstance;
|
||||||
|
@ -397,39 +395,43 @@ export const getTopNavConfig = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveModal =
|
const useByRefFlow =
|
||||||
!!originatingApp ||
|
!!originatingApp || !dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables;
|
||||||
!dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables ? (
|
|
||||||
<SavedObjectSaveModalOrigin
|
const saveModal = useByRefFlow ? (
|
||||||
documentInfo={savedVis || { title: '' }}
|
<SavedObjectSaveModalOrigin
|
||||||
onSave={onSave}
|
documentInfo={savedVis || { title: '' }}
|
||||||
options={tagOptions}
|
onSave={onSave}
|
||||||
getAppNameFromId={stateTransfer.getAppNameFromId}
|
options={tagOptions}
|
||||||
objectType={'visualization'}
|
getAppNameFromId={stateTransfer.getAppNameFromId}
|
||||||
onClose={() => {}}
|
objectType={'visualization'}
|
||||||
originatingApp={originatingApp}
|
onClose={() => {}}
|
||||||
returnToOriginSwitchLabel={
|
originatingApp={originatingApp}
|
||||||
originatingApp && embeddableId
|
returnToOriginSwitchLabel={
|
||||||
? i18n.translate('visualize.topNavMenu.updatePanel', {
|
originatingApp && embeddableId
|
||||||
defaultMessage: 'Update panel on {originatingAppName}',
|
? i18n.translate('visualize.topNavMenu.updatePanel', {
|
||||||
values: {
|
defaultMessage: 'Update panel on {originatingAppName}',
|
||||||
originatingAppName: stateTransfer.getAppNameFromId(originatingApp),
|
values: {
|
||||||
},
|
originatingAppName: stateTransfer.getAppNameFromId(originatingApp),
|
||||||
})
|
},
|
||||||
: undefined
|
})
|
||||||
}
|
: undefined
|
||||||
/>
|
}
|
||||||
) : (
|
/>
|
||||||
<SavedObjectSaveModalDashboard
|
) : (
|
||||||
documentInfo={savedVis || { title: '' }}
|
<SavedObjectSaveModalDashboard
|
||||||
onSave={onSave}
|
documentInfo={savedVis || { title: '' }}
|
||||||
tagOptions={tagOptions}
|
onSave={onSave}
|
||||||
objectType={'visualization'}
|
tagOptions={tagOptions}
|
||||||
onClose={() => {}}
|
objectType={'visualization'}
|
||||||
savedObjectsClient={savedObjectsClient}
|
onClose={() => {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
showSaveModal(saveModal, I18nContext);
|
showSaveModal(
|
||||||
|
saveModal,
|
||||||
|
I18nContext,
|
||||||
|
!useByRefFlow ? presentationUtil.ContextProvider : React.Fragment
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
ScopedHistory,
|
ScopedHistory,
|
||||||
} from 'kibana/public';
|
} from 'kibana/public';
|
||||||
|
|
||||||
|
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||||
import {
|
import {
|
||||||
Storage,
|
Storage,
|
||||||
createKbnUrlTracker,
|
createKbnUrlTracker,
|
||||||
|
@ -62,6 +63,7 @@ export interface VisualizePluginStartDependencies {
|
||||||
savedObjects: SavedObjectsStart;
|
savedObjects: SavedObjectsStart;
|
||||||
dashboard: DashboardStart;
|
dashboard: DashboardStart;
|
||||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||||
|
presentationUtil: PresentationUtilPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VisualizePluginSetupDependencies {
|
export interface VisualizePluginSetupDependencies {
|
||||||
|
@ -204,6 +206,7 @@ export class VisualizePlugin
|
||||||
dashboard: pluginsStart.dashboard,
|
dashboard: pluginsStart.dashboard,
|
||||||
setHeaderActionMenu: params.setHeaderActionMenu,
|
setHeaderActionMenu: params.setHeaderActionMenu,
|
||||||
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
|
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
|
||||||
|
presentationUtil: pluginsStart.presentationUtil,
|
||||||
};
|
};
|
||||||
|
|
||||||
params.element.classList.add('visAppWrapper');
|
params.element.classList.add('visAppWrapper');
|
||||||
|
|
7
typings/index.d.ts
vendored
7
typings/index.d.ts
vendored
|
@ -23,3 +23,10 @@ declare module '*.svg' {
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Storybook references this module. It's @ts-ignored in the codebase but when
|
||||||
|
// built into its dist it strips that out. Add it here to avoid a type checking
|
||||||
|
// error.
|
||||||
|
//
|
||||||
|
// See https://github.com/storybookjs/storybook/issues/11684
|
||||||
|
declare module 'react-syntax-highlighter/dist/cjs/create-element';
|
||||||
|
|
|
@ -14,10 +14,26 @@
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"uiActions",
|
"uiActions",
|
||||||
"embeddable",
|
"embeddable",
|
||||||
"share"
|
"share",
|
||||||
|
"presentationUtil"
|
||||||
],
|
],
|
||||||
"optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"],
|
"optionalPlugins": [
|
||||||
"configPath": ["xpack", "lens"],
|
"usageCollection",
|
||||||
"extraPublicDirs": ["common/constants"],
|
"taskManager",
|
||||||
"requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "presentationUtil"]
|
"globalSearch",
|
||||||
|
"savedObjectsTagging"
|
||||||
|
],
|
||||||
|
"configPath": [
|
||||||
|
"xpack",
|
||||||
|
"lens"
|
||||||
|
],
|
||||||
|
"extraPublicDirs": [
|
||||||
|
"common/constants"
|
||||||
|
],
|
||||||
|
"requiredBundles": [
|
||||||
|
"savedObjects",
|
||||||
|
"kibanaUtils",
|
||||||
|
"kibanaReact",
|
||||||
|
"embeddable"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -707,7 +707,6 @@ export function App({
|
||||||
isVisible={state.isSaveModalVisible}
|
isVisible={state.isSaveModalVisible}
|
||||||
originatingApp={state.isLinkedToOriginatingApp ? incomingState?.originatingApp : undefined}
|
originatingApp={state.isLinkedToOriginatingApp ? incomingState?.originatingApp : undefined}
|
||||||
allowByValueEmbeddables={dashboardFeatureFlag.allowByValueEmbeddables}
|
allowByValueEmbeddables={dashboardFeatureFlag.allowByValueEmbeddables}
|
||||||
savedObjectsClient={savedObjectsClient}
|
|
||||||
savedObjectsTagging={savedObjectsTagging}
|
savedObjectsTagging={savedObjectsTagging}
|
||||||
tagsIds={tagsIds}
|
tagsIds={tagsIds}
|
||||||
onSave={runSave}
|
onSave={runSave}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import React, { useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
|
|
||||||
import { AppMountParameters, CoreSetup } from 'kibana/public';
|
import { AppMountParameters, CoreSetup } from 'kibana/public';
|
||||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||||
|
@ -39,9 +39,15 @@ export async function mountApp(
|
||||||
createEditorFrame: EditorFrameStart['createInstance'];
|
createEditorFrame: EditorFrameStart['createInstance'];
|
||||||
getByValueFeatureFlag: () => Promise<DashboardFeatureFlagConfig>;
|
getByValueFeatureFlag: () => Promise<DashboardFeatureFlagConfig>;
|
||||||
attributeService: () => Promise<LensAttributeService>;
|
attributeService: () => Promise<LensAttributeService>;
|
||||||
|
getPresentationUtilContext: () => Promise<FC>;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { createEditorFrame, getByValueFeatureFlag, attributeService } = mountProps;
|
const {
|
||||||
|
createEditorFrame,
|
||||||
|
getByValueFeatureFlag,
|
||||||
|
attributeService,
|
||||||
|
getPresentationUtilContext,
|
||||||
|
} = mountProps;
|
||||||
const [coreStart, startDependencies] = await core.getStartServices();
|
const [coreStart, startDependencies] = await core.getStartServices();
|
||||||
const { data, navigation, embeddable, savedObjectsTagging } = startDependencies;
|
const { data, navigation, embeddable, savedObjectsTagging } = startDependencies;
|
||||||
|
|
||||||
|
@ -196,21 +202,26 @@ export async function mountApp(
|
||||||
});
|
});
|
||||||
|
|
||||||
params.element.classList.add('lnsAppWrapper');
|
params.element.classList.add('lnsAppWrapper');
|
||||||
|
|
||||||
|
const PresentationUtilContext = await getPresentationUtilContext();
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<KibanaContextProvider services={lensServices}>
|
<KibanaContextProvider services={lensServices}>
|
||||||
<HashRouter>
|
<PresentationUtilContext>
|
||||||
<Switch>
|
<HashRouter>
|
||||||
<Route exact path="/edit/:id" component={EditorRoute} />
|
<Switch>
|
||||||
<Route
|
<Route exact path="/edit/:id" component={EditorRoute} />
|
||||||
exact
|
<Route
|
||||||
path={`/${LENS_EDIT_BY_VALUE}`}
|
exact
|
||||||
render={(routeProps) => <EditorRoute {...routeProps} editByValue />}
|
path={`/${LENS_EDIT_BY_VALUE}`}
|
||||||
/>
|
render={(routeProps) => <EditorRoute {...routeProps} editByValue />}
|
||||||
<Route exact path="/" component={EditorRoute} />
|
/>
|
||||||
<Route path="/" component={NotFound} />
|
<Route exact path="/" component={EditorRoute} />
|
||||||
</Switch>
|
<Route path="/" component={NotFound} />
|
||||||
</HashRouter>
|
</Switch>
|
||||||
|
</HashRouter>
|
||||||
|
</PresentationUtilContext>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</I18nProvider>,
|
</I18nProvider>,
|
||||||
params.element
|
params.element
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { SavedObjectsStart } from '../../../../../src/core/public';
|
|
||||||
|
|
||||||
import { Document } from '../persistence';
|
import { Document } from '../persistence';
|
||||||
import type { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
import type { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
||||||
|
|
||||||
|
@ -29,8 +27,6 @@ export interface Props {
|
||||||
originatingApp?: string;
|
originatingApp?: string;
|
||||||
allowByValueEmbeddables: boolean;
|
allowByValueEmbeddables: boolean;
|
||||||
|
|
||||||
savedObjectsClient: SavedObjectsStart['client'];
|
|
||||||
|
|
||||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||||
tagsIds: string[];
|
tagsIds: string[];
|
||||||
|
|
||||||
|
@ -51,7 +47,6 @@ export const SaveModal = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
originatingApp,
|
originatingApp,
|
||||||
savedObjectsTagging,
|
savedObjectsTagging,
|
||||||
savedObjectsClient,
|
|
||||||
tagsIds,
|
tagsIds,
|
||||||
lastKnownDoc,
|
lastKnownDoc,
|
||||||
allowByValueEmbeddables,
|
allowByValueEmbeddables,
|
||||||
|
@ -88,7 +83,6 @@ export const SaveModal = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<TagEnhancedSavedObjectSaveModalDashboard
|
<TagEnhancedSavedObjectSaveModalDashboard
|
||||||
savedObjectsTagging={savedObjectsTagging}
|
savedObjectsTagging={savedObjectsTagging}
|
||||||
savedObjectsClient={savedObjectsClient}
|
|
||||||
initialTags={tagsIds}
|
initialTags={tagsIds}
|
||||||
onSave={(saveProps) => {
|
onSave={(saveProps) => {
|
||||||
const saveToLibrary = saveProps.dashboardId === null;
|
const saveToLibrary = saveProps.dashboardId === null;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import React, { FC, useState, useMemo, useCallback } from 'react';
|
import React, { FC, useState, useMemo, useCallback } from 'react';
|
||||||
import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
|
import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
|
||||||
import {
|
import {
|
||||||
DashboardSaveModalProps,
|
SaveModalDashboardProps,
|
||||||
SavedObjectSaveModalDashboard,
|
SavedObjectSaveModalDashboard,
|
||||||
} from '../../../../../src/plugins/presentation_util/public';
|
} from '../../../../../src/plugins/presentation_util/public';
|
||||||
import { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
import { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
||||||
|
@ -19,7 +19,7 @@ export type DashboardSaveProps = OnSaveProps & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit<
|
export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit<
|
||||||
DashboardSaveModalProps,
|
SaveModalDashboardProps,
|
||||||
'onSave'
|
'onSave'
|
||||||
> & {
|
> & {
|
||||||
initialTags: string[];
|
initialTags: string[];
|
||||||
|
@ -48,7 +48,7 @@ export const TagEnhancedSavedObjectSaveModalDashboard: FC<TagEnhancedSavedObject
|
||||||
|
|
||||||
const tagEnhancedOptions = <>{tagSelectorOption}</>;
|
const tagEnhancedOptions = <>{tagSelectorOption}</>;
|
||||||
|
|
||||||
const tagEnhancedOnSave: DashboardSaveModalProps['onSave'] = useCallback(
|
const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback(
|
||||||
(saveOptions) => {
|
(saveOptions) => {
|
||||||
onSave({
|
onSave({
|
||||||
...saveOptions,
|
...saveOptions,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/
|
||||||
import { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public';
|
import { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public';
|
||||||
import { GlobalSearchPluginSetup } from '../../global_search/public';
|
import { GlobalSearchPluginSetup } from '../../global_search/public';
|
||||||
import { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public';
|
import { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public';
|
||||||
|
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||||
import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public';
|
import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public';
|
||||||
import { EditorFrameService } from './editor_frame_service';
|
import { EditorFrameService } from './editor_frame_service';
|
||||||
import {
|
import {
|
||||||
|
@ -71,6 +72,7 @@ export interface LensPluginStartDependencies {
|
||||||
embeddable: EmbeddableStart;
|
embeddable: EmbeddableStart;
|
||||||
charts: ChartsPluginStart;
|
charts: ChartsPluginStart;
|
||||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||||
|
presentationUtil: PresentationUtilPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LensPublicStart {
|
export interface LensPublicStart {
|
||||||
|
@ -172,6 +174,12 @@ export class LensPlugin {
|
||||||
return deps.dashboard.dashboardFeatureFlagConfig;
|
return deps.dashboard.dashboardFeatureFlagConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPresentationUtilContext = async () => {
|
||||||
|
const [, deps] = await core.getStartServices();
|
||||||
|
const { ContextProvider } = deps.presentationUtil;
|
||||||
|
return ContextProvider;
|
||||||
|
};
|
||||||
|
|
||||||
core.application.register({
|
core.application.register({
|
||||||
id: 'lens',
|
id: 'lens',
|
||||||
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
|
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
|
||||||
|
@ -183,6 +191,7 @@ export class LensPlugin {
|
||||||
createEditorFrame: this.createEditorFrame!,
|
createEditorFrame: this.createEditorFrame!,
|
||||||
attributeService: this.attributeService!,
|
attributeService: this.attributeService!,
|
||||||
getByValueFeatureFlag,
|
getByValueFeatureFlag,
|
||||||
|
getPresentationUtilContext,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
"id": "maps",
|
"id": "maps",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"kibanaVersion": "kibana",
|
"kibanaVersion": "kibana",
|
||||||
"configPath": ["xpack", "maps"],
|
"configPath": [
|
||||||
|
"xpack",
|
||||||
|
"maps"
|
||||||
|
],
|
||||||
"requiredPlugins": [
|
"requiredPlugins": [
|
||||||
"licensing",
|
"licensing",
|
||||||
"features",
|
"features",
|
||||||
|
@ -17,11 +20,21 @@
|
||||||
"mapsLegacy",
|
"mapsLegacy",
|
||||||
"usageCollection",
|
"usageCollection",
|
||||||
"savedObjects",
|
"savedObjects",
|
||||||
"share"
|
"share",
|
||||||
|
"presentationUtil"
|
||||||
|
],
|
||||||
|
"optionalPlugins": [
|
||||||
|
"home",
|
||||||
|
"savedObjectsTagging"
|
||||||
],
|
],
|
||||||
"optionalPlugins": ["home", "savedObjectsTagging"],
|
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"server": true,
|
"server": true,
|
||||||
"extraPublicDirs": ["common/constants"],
|
"extraPublicDirs": [
|
||||||
"requiredBundles": ["kibanaReact", "kibanaUtils", "home", "presentationUtil"]
|
"common/constants"
|
||||||
|
],
|
||||||
|
"requiredBundles": [
|
||||||
|
"kibanaReact",
|
||||||
|
"kibanaUtils",
|
||||||
|
"home"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ export const getSearchService = () => pluginsStart.data.search;
|
||||||
export const getEmbeddableService = () => pluginsStart.embeddable;
|
export const getEmbeddableService = () => pluginsStart.embeddable;
|
||||||
export const getNavigateToApp = () => coreStart.application.navigateToApp;
|
export const getNavigateToApp = () => coreStart.application.navigateToApp;
|
||||||
export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging;
|
export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging;
|
||||||
|
export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider;
|
||||||
|
|
||||||
// xpack.maps.* kibana.yml settings from this plugin
|
// xpack.maps.* kibana.yml settings from this plugin
|
||||||
let mapAppConfig: MapsConfigType;
|
let mapAppConfig: MapsConfigType;
|
||||||
|
|
|
@ -55,6 +55,7 @@ import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||||
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
|
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
|
||||||
import { StartContract as FileUploadStartContract } from '../../maps_file_upload/public';
|
import { StartContract as FileUploadStartContract } from '../../maps_file_upload/public';
|
||||||
import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public';
|
import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public';
|
||||||
|
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||||
import {
|
import {
|
||||||
getIsEnterprisePlus,
|
getIsEnterprisePlus,
|
||||||
registerLicensedFeatures,
|
registerLicensedFeatures,
|
||||||
|
@ -86,6 +87,7 @@ export interface MapsPluginStartDependencies {
|
||||||
savedObjects: SavedObjectsStart;
|
savedObjects: SavedObjectsStart;
|
||||||
dashboard: DashboardStart;
|
dashboard: DashboardStart;
|
||||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||||
|
presentationUtil: PresentationUtilPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
getSavedObjectsClient,
|
getSavedObjectsClient,
|
||||||
getCoreOverlays,
|
getCoreOverlays,
|
||||||
getSavedObjectsTagging,
|
getSavedObjectsTagging,
|
||||||
|
getPresentationUtilContext,
|
||||||
} from '../../kibana_services';
|
} from '../../kibana_services';
|
||||||
import {
|
import {
|
||||||
checkForDuplicateTitle,
|
checkForDuplicateTitle,
|
||||||
|
@ -185,7 +186,7 @@ export function getTopNavConfig({
|
||||||
defaultMessage: 'map',
|
defaultMessage: 'map',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
const PresentationUtilContext = getPresentationUtilContext();
|
||||||
const saveModal =
|
const saveModal =
|
||||||
savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables() ? (
|
savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables() ? (
|
||||||
<SavedObjectSaveModalOrigin
|
<SavedObjectSaveModalOrigin
|
||||||
|
@ -195,14 +196,10 @@ export function getTopNavConfig({
|
||||||
options={tagSelector}
|
options={tagSelector}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SavedObjectSaveModalDashboard
|
<SavedObjectSaveModalDashboard {...saveModalProps} tagOptions={tagSelector} />
|
||||||
{...saveModalProps}
|
|
||||||
savedObjectsClient={getSavedObjectsClient()}
|
|
||||||
tagOptions={tagSelector}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
showSaveModal(saveModal, getCoreI18n().Context);
|
showSaveModal(saveModal, getCoreI18n().Context, PresentationUtilContext);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4445,7 +4445,7 @@
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
ts-dedent "^1.1.1"
|
ts-dedent "^1.1.1"
|
||||||
|
|
||||||
"@storybook/addon-docs@6.0.26":
|
"@storybook/addon-docs@6.0.26", "@storybook/addon-docs@^6.0.26":
|
||||||
version "6.0.26"
|
version "6.0.26"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.0.26.tgz#bd7fc1fcdc47bb7992fa8d3254367e8c3bba373d"
|
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.0.26.tgz#bd7fc1fcdc47bb7992fa8d3254367e8c3bba373d"
|
||||||
integrity sha512-3t8AOPkp8ZW74h7FnzxF3wAeb1wRyYjMmgJZxqzgi/x7K0i1inbCq8MuJnytuTcZ7+EK4HR6Ih7o9tJuAtIBLw==
|
integrity sha512-3t8AOPkp8ZW74h7FnzxF3wAeb1wRyYjMmgJZxqzgi/x7K0i1inbCq8MuJnytuTcZ7+EK4HR6Ih7o9tJuAtIBLw==
|
||||||
|
|
Loading…
Reference in a new issue