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.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/presentation_util/README.md[presentationUtil]
|
||||
|Utilities and components used by the presentation-related plugins
|
||||
|{kib-repo}blob/{branch}/src/plugins/presentation_util/README.mdx[presentationUtil]
|
||||
|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]
|
||||
|
|
|
@ -393,6 +393,7 @@
|
|||
"@storybook/addon-essentials": "^6.0.26",
|
||||
"@storybook/addon-knobs": "^6.0.26",
|
||||
"@storybook/addon-storyshots": "^6.0.26",
|
||||
"@storybook/addon-docs": "^6.0.26",
|
||||
"@storybook/components": "^6.0.26",
|
||||
"@storybook/core": "^6.0.26",
|
||||
"@storybook/core-events": "^6.0.26",
|
||||
|
|
|
@ -18,4 +18,5 @@ export const storybookAliases = {
|
|||
security_solution: 'x-pack/plugins/security_solution/.storybook',
|
||||
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.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.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { SavedObjectsClientContract } from '../../../../core/public';
|
||||
import { DashboardSavedObject } from '../../../../plugins/dashboard/public';
|
||||
import { pluginServices } from '../services';
|
||||
|
||||
export interface DashboardPickerProps {
|
||||
onChange: (dashboard: { name: string; id: string } | null) => void;
|
||||
isDisabled: boolean;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
interface DashboardOption {
|
||||
|
@ -26,34 +24,43 @@ interface DashboardOption {
|
|||
}
|
||||
|
||||
export function DashboardPicker(props: DashboardPickerProps) {
|
||||
const [dashboards, setDashboards] = useState<DashboardOption[]>([]);
|
||||
const [dashboardOptions, setDashboardOptions] = useState<DashboardOption[]>([]);
|
||||
const [isLoadingDashboards, setIsLoadingDashboards] = useState(true);
|
||||
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(() => {
|
||||
fetchDashboards('');
|
||||
}, [fetchDashboards]);
|
||||
// We don't want to manipulate the React state if the component has been unmounted
|
||||
// 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 (
|
||||
<EuiComboBox
|
||||
|
@ -61,7 +68,7 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
|||
defaultMessage: 'Search dashboards...',
|
||||
})}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={dashboards || []}
|
||||
options={dashboardOptions || []}
|
||||
selectedOptions={!!selectedDashboard ? [selectedDashboard] : undefined}
|
||||
onChange={(e) => {
|
||||
if (e.length) {
|
||||
|
@ -72,7 +79,7 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
|||
onChange(null);
|
||||
}
|
||||
}}
|
||||
onSearchChange={fetchDashboards}
|
||||
onSearchChange={setQuery}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoadingDashboards}
|
||||
compressed={true}
|
||||
|
|
|
@ -9,18 +9,6 @@
|
|||
import React, { useState } 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 { SavedObjectsClientContract } from '../../../../core/public';
|
||||
|
||||
import {
|
||||
OnSaveProps,
|
||||
|
@ -28,9 +16,9 @@ import {
|
|||
SavedObjectSaveModal,
|
||||
} from '../../../../plugins/saved_objects/public';
|
||||
|
||||
import { DashboardPicker } from './dashboard_picker';
|
||||
|
||||
import './saved_object_save_modal_dashboard.scss';
|
||||
import { pluginServices } from '../services';
|
||||
import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector';
|
||||
|
||||
interface SaveModalDocumentInfo {
|
||||
id?: string;
|
||||
|
@ -38,116 +26,50 @@ interface SaveModalDocumentInfo {
|
|||
description?: string;
|
||||
}
|
||||
|
||||
export interface DashboardSaveModalProps {
|
||||
export interface SaveModalDashboardProps {
|
||||
documentInfo: SaveModalDocumentInfo;
|
||||
objectType: string;
|
||||
onClose: () => void;
|
||||
onSave: (props: OnSaveProps & { dashboardId: string | null }) => void;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode);
|
||||
}
|
||||
|
||||
export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
||||
const { documentInfo, savedObjectsClient, tagOptions } = props;
|
||||
const initialCopyOnSave = !Boolean(documentInfo.id);
|
||||
export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
|
||||
const { documentInfo, tagOptions, objectType, onClose } = props;
|
||||
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>(
|
||||
documentInfo.id ? null : 'existing'
|
||||
documentId || disableDashboardOptions ? null : 'existing'
|
||||
);
|
||||
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
|
||||
null
|
||||
);
|
||||
const [copyOnSave, setCopyOnSave] = useState<boolean>(initialCopyOnSave);
|
||||
|
||||
const renderDashboardSelect = (state: SaveModalState) => {
|
||||
const isDisabled = Boolean(!state.copyOnSave && documentInfo.id);
|
||||
|
||||
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>
|
||||
<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 rightOptions = !disableDashboardOptions
|
||||
? () => (
|
||||
<SaveModalDashboardSelector
|
||||
onSelectDashboard={(dash) => {
|
||||
setSelectedDashboard(dash);
|
||||
}}
|
||||
onChange={(option) => {
|
||||
setDashboardOption(option);
|
||||
}}
|
||||
{...{ copyOnSave, documentId, dashboardOption }}
|
||||
/>
|
||||
)
|
||||
: null;
|
||||
|
||||
const onCopyOnSaveChange = (newCopyOnSave: boolean) => {
|
||||
setDashboardOption(null);
|
||||
|
@ -159,7 +81,7 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
|||
|
||||
// Don't save with a dashboard ID if we're
|
||||
// just updating an existing visualization
|
||||
if (!(!onSaveProps.newCopyOnSave && documentInfo.id)) {
|
||||
if (!(!onSaveProps.newCopyOnSave && documentId)) {
|
||||
if (dashboardOption === 'existing') {
|
||||
dashboardId = selectedDashboard?.id || null;
|
||||
} else {
|
||||
|
@ -171,13 +93,14 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
|||
};
|
||||
|
||||
const saveLibraryLabel =
|
||||
!copyOnSave && documentInfo.id
|
||||
!copyOnSave && documentId
|
||||
? i18n.translate('presentationUtil.saveModalDashboard.saveLabel', {
|
||||
defaultMessage: 'Save',
|
||||
})
|
||||
: i18n.translate('presentationUtil.saveModalDashboard.saveToLibraryLabel', {
|
||||
defaultMessage: 'Save and add to library',
|
||||
});
|
||||
|
||||
const saveDashboardLabel = i18n.translate(
|
||||
'presentationUtil.saveModalDashboard.saveAndGoToDashboardLabel',
|
||||
{
|
||||
|
@ -192,18 +115,20 @@ export function SavedObjectSaveModalDashboard(props: DashboardSaveModalProps) {
|
|||
return (
|
||||
<SavedObjectSaveModal
|
||||
onSave={onModalSave}
|
||||
onClose={props.onClose}
|
||||
title={documentInfo.title}
|
||||
showCopyOnSave={documentInfo.id ? true : false}
|
||||
initialCopyOnSave={initialCopyOnSave}
|
||||
confirmButtonLabel={confirmButtonLabel}
|
||||
objectType={props.objectType}
|
||||
showCopyOnSave={documentId ? true : false}
|
||||
options={dashboardOption === null ? tagOptions : undefined} // Show tags when not adding to dashboard
|
||||
rightOptions={renderDashboardSelect}
|
||||
description={documentInfo.description}
|
||||
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 {
|
||||
SavedObjectSaveModalDashboard,
|
||||
DashboardSaveModalProps,
|
||||
SaveModalDashboardProps,
|
||||
} from './components/saved_object_save_modal_dashboard';
|
||||
|
||||
export { DashboardPicker } from './components/dashboard_picker';
|
||||
|
||||
export function plugin() {
|
||||
return new PresentationUtilPlugin();
|
||||
}
|
||||
|
|
|
@ -7,16 +7,39 @@
|
|||
*/
|
||||
|
||||
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
|
||||
implements Plugin<PresentationUtilPluginSetup, PresentationUtilPluginStart> {
|
||||
public setup(core: CoreSetup): PresentationUtilPluginSetup {
|
||||
implements
|
||||
Plugin<
|
||||
PresentationUtilPluginSetup,
|
||||
PresentationUtilPluginStart,
|
||||
PresentationUtilPluginSetupDeps,
|
||||
PresentationUtilPluginStartDeps
|
||||
> {
|
||||
public setup(
|
||||
_coreSetup: CoreSetup<PresentationUtilPluginSetup>,
|
||||
_setupPlugins: PresentationUtilPluginSetupDeps
|
||||
): PresentationUtilPluginSetup {
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): PresentationUtilPluginStart {
|
||||
return {};
|
||||
public async start(
|
||||
coreStart: CoreStart,
|
||||
startPlugins: PresentationUtilPluginStartDeps
|
||||
): Promise<PresentationUtilPluginStart> {
|
||||
pluginServices.setRegistry(registry.start({ coreStart, startPlugins }));
|
||||
|
||||
return {
|
||||
ContextProvider: pluginServices.getContextProvider(),
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
export interface PresentationUtilPluginSetup {}
|
||||
|
||||
export interface PresentationUtilPluginStart {
|
||||
ContextProvider: React.FC;
|
||||
}
|
||||
|
||||
// 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,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["common/**/*", "public/**/*"],
|
||||
"include": ["common/**/*", "public/**/*", "storybook/**/*", "../../../typings/**/*"],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" },
|
||||
{ "path": "../dashboard/tsconfig.json" },
|
||||
|
|
|
@ -31,7 +31,8 @@ interface MinimalSaveModalProps {
|
|||
|
||||
export function showSaveModal(
|
||||
saveModal: React.ReactElement<MinimalSaveModalProps>,
|
||||
I18nContext: I18nStart['Context']
|
||||
I18nContext: I18nStart['Context'],
|
||||
Wrapper?: React.FC
|
||||
) {
|
||||
const container = document.createElement('div');
|
||||
const closeModal = () => {
|
||||
|
@ -55,5 +56,13 @@ export function showSaveModal(
|
|||
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",
|
||||
"embeddable",
|
||||
"dashboard",
|
||||
"uiActions"
|
||||
"uiActions",
|
||||
"presentationUtil"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
|
@ -22,7 +23,6 @@
|
|||
"kibanaUtils",
|
||||
"kibanaReact",
|
||||
"home",
|
||||
"presentationUtil",
|
||||
"discover"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ const TopNav = ({
|
|||
},
|
||||
[visInstance.embeddableHandler]
|
||||
);
|
||||
const savedObjectsClient = services.savedObjects.client;
|
||||
|
||||
const config = useMemo(() => {
|
||||
if (isEmbeddableRendered) {
|
||||
|
@ -85,7 +84,6 @@ const TopNav = ({
|
|||
stateContainer,
|
||||
visualizationIdFromUrl,
|
||||
stateTransfer: services.stateTransferService,
|
||||
savedObjectsClient,
|
||||
embeddableId,
|
||||
},
|
||||
services
|
||||
|
@ -104,7 +102,6 @@ const TopNav = ({
|
|||
visualizationIdFromUrl,
|
||||
services,
|
||||
embeddableId,
|
||||
savedObjectsClient,
|
||||
]);
|
||||
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>(
|
||||
vis.data.indexPattern ? [vis.data.indexPattern] : []
|
||||
|
|
|
@ -30,9 +30,11 @@ export const renderApp = (
|
|||
const app = (
|
||||
<Router history={services.history}>
|
||||
<KibanaContextProvider services={services}>
|
||||
<services.i18n.Context>
|
||||
<VisualizeApp onAppLeave={onAppLeave} />
|
||||
</services.i18n.Context>
|
||||
<services.presentationUtil.ContextProvider>
|
||||
<services.i18n.Context>
|
||||
<VisualizeApp onAppLeave={onAppLeave} />
|
||||
</services.i18n.Context>
|
||||
</services.presentationUtil.ContextProvider>
|
||||
</KibanaContextProvider>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
@ -34,6 +34,7 @@ import { SharePluginStart } from 'src/plugins/share/public';
|
|||
import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
|
||||
import { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
|
||||
import { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
|
||||
import { PresentationUtilPluginStart } from 'src/plugins/presentation_util/public';
|
||||
import { EventEmitter } from 'events';
|
||||
import { DashboardStart } from '../../../dashboard/public';
|
||||
import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
|
||||
|
@ -93,6 +94,7 @@ export interface VisualizeServices extends CoreStart {
|
|||
dashboard: DashboardStart;
|
||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
savedObjectsTagging?: SavedObjectsTaggingApi;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
}
|
||||
|
||||
export interface SavedVisInstance {
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
} from '../../../../saved_objects/public';
|
||||
import { SavedObjectSaveModalDashboard } from '../../../../presentation_util/public';
|
||||
import { unhashUrl } from '../../../../kibana_utils/public';
|
||||
import { SavedObjectsClientContract } from '../../../../../core/public';
|
||||
|
||||
import {
|
||||
VisualizeServices,
|
||||
|
@ -50,7 +49,6 @@ interface TopNavConfigParams {
|
|||
stateContainer: VisualizeAppStateContainer;
|
||||
visualizationIdFromUrl?: string;
|
||||
stateTransfer: EmbeddableStateTransfer;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
embeddableId?: string;
|
||||
}
|
||||
|
||||
|
@ -72,7 +70,6 @@ export const getTopNavConfig = (
|
|||
hasUnappliedChanges,
|
||||
visInstance,
|
||||
stateContainer,
|
||||
savedObjectsClient,
|
||||
visualizationIdFromUrl,
|
||||
stateTransfer,
|
||||
embeddableId,
|
||||
|
@ -88,6 +85,7 @@ export const getTopNavConfig = (
|
|||
i18n: { Context: I18nContext },
|
||||
dashboard,
|
||||
savedObjectsTagging,
|
||||
presentationUtil,
|
||||
}: VisualizeServices
|
||||
) => {
|
||||
const { vis, embeddableHandler } = visInstance;
|
||||
|
@ -397,39 +395,43 @@ export const getTopNavConfig = (
|
|||
);
|
||||
}
|
||||
|
||||
const saveModal =
|
||||
!!originatingApp ||
|
||||
!dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables ? (
|
||||
<SavedObjectSaveModalOrigin
|
||||
documentInfo={savedVis || { title: '' }}
|
||||
onSave={onSave}
|
||||
options={tagOptions}
|
||||
getAppNameFromId={stateTransfer.getAppNameFromId}
|
||||
objectType={'visualization'}
|
||||
onClose={() => {}}
|
||||
originatingApp={originatingApp}
|
||||
returnToOriginSwitchLabel={
|
||||
originatingApp && embeddableId
|
||||
? i18n.translate('visualize.topNavMenu.updatePanel', {
|
||||
defaultMessage: 'Update panel on {originatingAppName}',
|
||||
values: {
|
||||
originatingAppName: stateTransfer.getAppNameFromId(originatingApp),
|
||||
},
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<SavedObjectSaveModalDashboard
|
||||
documentInfo={savedVis || { title: '' }}
|
||||
onSave={onSave}
|
||||
tagOptions={tagOptions}
|
||||
objectType={'visualization'}
|
||||
onClose={() => {}}
|
||||
savedObjectsClient={savedObjectsClient}
|
||||
/>
|
||||
);
|
||||
showSaveModal(saveModal, I18nContext);
|
||||
const useByRefFlow =
|
||||
!!originatingApp || !dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables;
|
||||
|
||||
const saveModal = useByRefFlow ? (
|
||||
<SavedObjectSaveModalOrigin
|
||||
documentInfo={savedVis || { title: '' }}
|
||||
onSave={onSave}
|
||||
options={tagOptions}
|
||||
getAppNameFromId={stateTransfer.getAppNameFromId}
|
||||
objectType={'visualization'}
|
||||
onClose={() => {}}
|
||||
originatingApp={originatingApp}
|
||||
returnToOriginSwitchLabel={
|
||||
originatingApp && embeddableId
|
||||
? i18n.translate('visualize.topNavMenu.updatePanel', {
|
||||
defaultMessage: 'Update panel on {originatingAppName}',
|
||||
values: {
|
||||
originatingAppName: stateTransfer.getAppNameFromId(originatingApp),
|
||||
},
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<SavedObjectSaveModalDashboard
|
||||
documentInfo={savedVis || { title: '' }}
|
||||
onSave={onSave}
|
||||
tagOptions={tagOptions}
|
||||
objectType={'visualization'}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
);
|
||||
showSaveModal(
|
||||
saveModal,
|
||||
I18nContext,
|
||||
!useByRefFlow ? presentationUtil.ContextProvider : React.Fragment
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
ScopedHistory,
|
||||
} from 'kibana/public';
|
||||
|
||||
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||
import {
|
||||
Storage,
|
||||
createKbnUrlTracker,
|
||||
|
@ -62,6 +63,7 @@ export interface VisualizePluginStartDependencies {
|
|||
savedObjects: SavedObjectsStart;
|
||||
dashboard: DashboardStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
}
|
||||
|
||||
export interface VisualizePluginSetupDependencies {
|
||||
|
@ -204,6 +206,7 @@ export class VisualizePlugin
|
|||
dashboard: pluginsStart.dashboard,
|
||||
setHeaderActionMenu: params.setHeaderActionMenu,
|
||||
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
|
||||
presentationUtil: pluginsStart.presentationUtil,
|
||||
};
|
||||
|
||||
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
|
||||
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",
|
||||
"uiActions",
|
||||
"embeddable",
|
||||
"share"
|
||||
"share",
|
||||
"presentationUtil"
|
||||
],
|
||||
"optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"],
|
||||
"configPath": ["xpack", "lens"],
|
||||
"extraPublicDirs": ["common/constants"],
|
||||
"requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "presentationUtil"]
|
||||
"optionalPlugins": [
|
||||
"usageCollection",
|
||||
"taskManager",
|
||||
"globalSearch",
|
||||
"savedObjectsTagging"
|
||||
],
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"lens"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"common/constants"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"savedObjects",
|
||||
"kibanaUtils",
|
||||
"kibanaReact",
|
||||
"embeddable"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -707,7 +707,6 @@ export function App({
|
|||
isVisible={state.isSaveModalVisible}
|
||||
originatingApp={state.isLinkedToOriginatingApp ? incomingState?.originatingApp : undefined}
|
||||
allowByValueEmbeddables={dashboardFeatureFlag.allowByValueEmbeddables}
|
||||
savedObjectsClient={savedObjectsClient}
|
||||
savedObjectsTagging={savedObjectsTagging}
|
||||
tagsIds={tagsIds}
|
||||
onSave={runSave}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under 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 { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
|
@ -39,9 +39,15 @@ export async function mountApp(
|
|||
createEditorFrame: EditorFrameStart['createInstance'];
|
||||
getByValueFeatureFlag: () => Promise<DashboardFeatureFlagConfig>;
|
||||
attributeService: () => Promise<LensAttributeService>;
|
||||
getPresentationUtilContext: () => Promise<FC>;
|
||||
}
|
||||
) {
|
||||
const { createEditorFrame, getByValueFeatureFlag, attributeService } = mountProps;
|
||||
const {
|
||||
createEditorFrame,
|
||||
getByValueFeatureFlag,
|
||||
attributeService,
|
||||
getPresentationUtilContext,
|
||||
} = mountProps;
|
||||
const [coreStart, startDependencies] = await core.getStartServices();
|
||||
const { data, navigation, embeddable, savedObjectsTagging } = startDependencies;
|
||||
|
||||
|
@ -196,21 +202,26 @@ export async function mountApp(
|
|||
});
|
||||
|
||||
params.element.classList.add('lnsAppWrapper');
|
||||
|
||||
const PresentationUtilContext = await getPresentationUtilContext();
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={lensServices}>
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route exact path="/edit/:id" component={EditorRoute} />
|
||||
<Route
|
||||
exact
|
||||
path={`/${LENS_EDIT_BY_VALUE}`}
|
||||
render={(routeProps) => <EditorRoute {...routeProps} editByValue />}
|
||||
/>
|
||||
<Route exact path="/" component={EditorRoute} />
|
||||
<Route path="/" component={NotFound} />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
<PresentationUtilContext>
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route exact path="/edit/:id" component={EditorRoute} />
|
||||
<Route
|
||||
exact
|
||||
path={`/${LENS_EDIT_BY_VALUE}`}
|
||||
render={(routeProps) => <EditorRoute {...routeProps} editByValue />}
|
||||
/>
|
||||
<Route exact path="/" component={EditorRoute} />
|
||||
<Route path="/" component={NotFound} />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</PresentationUtilContext>
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>,
|
||||
params.element
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SavedObjectsStart } from '../../../../../src/core/public';
|
||||
|
||||
import { Document } from '../persistence';
|
||||
import type { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
||||
|
||||
|
@ -29,8 +27,6 @@ export interface Props {
|
|||
originatingApp?: string;
|
||||
allowByValueEmbeddables: boolean;
|
||||
|
||||
savedObjectsClient: SavedObjectsStart['client'];
|
||||
|
||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
tagsIds: string[];
|
||||
|
||||
|
@ -51,7 +47,6 @@ export const SaveModal = (props: Props) => {
|
|||
const {
|
||||
originatingApp,
|
||||
savedObjectsTagging,
|
||||
savedObjectsClient,
|
||||
tagsIds,
|
||||
lastKnownDoc,
|
||||
allowByValueEmbeddables,
|
||||
|
@ -88,7 +83,6 @@ export const SaveModal = (props: Props) => {
|
|||
return (
|
||||
<TagEnhancedSavedObjectSaveModalDashboard
|
||||
savedObjectsTagging={savedObjectsTagging}
|
||||
savedObjectsClient={savedObjectsClient}
|
||||
initialTags={tagsIds}
|
||||
onSave={(saveProps) => {
|
||||
const saveToLibrary = saveProps.dashboardId === null;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { FC, useState, useMemo, useCallback } from 'react';
|
||||
import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
|
||||
import {
|
||||
DashboardSaveModalProps,
|
||||
SaveModalDashboardProps,
|
||||
SavedObjectSaveModalDashboard,
|
||||
} from '../../../../../src/plugins/presentation_util/public';
|
||||
import { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public';
|
||||
|
@ -19,7 +19,7 @@ export type DashboardSaveProps = OnSaveProps & {
|
|||
};
|
||||
|
||||
export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit<
|
||||
DashboardSaveModalProps,
|
||||
SaveModalDashboardProps,
|
||||
'onSave'
|
||||
> & {
|
||||
initialTags: string[];
|
||||
|
@ -48,7 +48,7 @@ export const TagEnhancedSavedObjectSaveModalDashboard: FC<TagEnhancedSavedObject
|
|||
|
||||
const tagEnhancedOptions = <>{tagSelectorOption}</>;
|
||||
|
||||
const tagEnhancedOnSave: DashboardSaveModalProps['onSave'] = useCallback(
|
||||
const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback(
|
||||
(saveOptions) => {
|
||||
onSave({
|
||||
...saveOptions,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/
|
|||
import { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public';
|
||||
import { GlobalSearchPluginSetup } from '../../global_search/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 { EditorFrameService } from './editor_frame_service';
|
||||
import {
|
||||
|
@ -71,6 +72,7 @@ export interface LensPluginStartDependencies {
|
|||
embeddable: EmbeddableStart;
|
||||
charts: ChartsPluginStart;
|
||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
}
|
||||
|
||||
export interface LensPublicStart {
|
||||
|
@ -172,6 +174,12 @@ export class LensPlugin {
|
|||
return deps.dashboard.dashboardFeatureFlagConfig;
|
||||
};
|
||||
|
||||
const getPresentationUtilContext = async () => {
|
||||
const [, deps] = await core.getStartServices();
|
||||
const { ContextProvider } = deps.presentationUtil;
|
||||
return ContextProvider;
|
||||
};
|
||||
|
||||
core.application.register({
|
||||
id: 'lens',
|
||||
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
|
||||
|
@ -183,6 +191,7 @@ export class LensPlugin {
|
|||
createEditorFrame: this.createEditorFrame!,
|
||||
attributeService: this.attributeService!,
|
||||
getByValueFeatureFlag,
|
||||
getPresentationUtilContext,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
"id": "maps",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "maps"],
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"maps"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"licensing",
|
||||
"features",
|
||||
|
@ -17,11 +20,21 @@
|
|||
"mapsLegacy",
|
||||
"usageCollection",
|
||||
"savedObjects",
|
||||
"share"
|
||||
"share",
|
||||
"presentationUtil"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
"savedObjectsTagging"
|
||||
],
|
||||
"optionalPlugins": ["home", "savedObjectsTagging"],
|
||||
"ui": true,
|
||||
"server": true,
|
||||
"extraPublicDirs": ["common/constants"],
|
||||
"requiredBundles": ["kibanaReact", "kibanaUtils", "home", "presentationUtil"]
|
||||
"extraPublicDirs": [
|
||||
"common/constants"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"home"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ export const getSearchService = () => pluginsStart.data.search;
|
|||
export const getEmbeddableService = () => pluginsStart.embeddable;
|
||||
export const getNavigateToApp = () => coreStart.application.navigateToApp;
|
||||
export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging;
|
||||
export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider;
|
||||
|
||||
// xpack.maps.* kibana.yml settings from this plugin
|
||||
let mapAppConfig: MapsConfigType;
|
||||
|
|
|
@ -55,6 +55,7 @@ import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
|||
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
|
||||
import { StartContract as FileUploadStartContract } from '../../maps_file_upload/public';
|
||||
import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public';
|
||||
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||
import {
|
||||
getIsEnterprisePlus,
|
||||
registerLicensedFeatures,
|
||||
|
@ -86,6 +87,7 @@ export interface MapsPluginStartDependencies {
|
|||
savedObjects: SavedObjectsStart;
|
||||
dashboard: DashboardStart;
|
||||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
getSavedObjectsClient,
|
||||
getCoreOverlays,
|
||||
getSavedObjectsTagging,
|
||||
getPresentationUtilContext,
|
||||
} from '../../kibana_services';
|
||||
import {
|
||||
checkForDuplicateTitle,
|
||||
|
@ -185,7 +186,7 @@ export function getTopNavConfig({
|
|||
defaultMessage: 'map',
|
||||
}),
|
||||
};
|
||||
|
||||
const PresentationUtilContext = getPresentationUtilContext();
|
||||
const saveModal =
|
||||
savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables() ? (
|
||||
<SavedObjectSaveModalOrigin
|
||||
|
@ -195,14 +196,10 @@ export function getTopNavConfig({
|
|||
options={tagSelector}
|
||||
/>
|
||||
) : (
|
||||
<SavedObjectSaveModalDashboard
|
||||
{...saveModalProps}
|
||||
savedObjectsClient={getSavedObjectsClient()}
|
||||
tagOptions={tagSelector}
|
||||
/>
|
||||
<SavedObjectSaveModalDashboard {...saveModalProps} tagOptions={tagSelector} />
|
||||
);
|
||||
|
||||
showSaveModal(saveModal, getCoreI18n().Context);
|
||||
showSaveModal(saveModal, getCoreI18n().Context, PresentationUtilContext);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -4445,7 +4445,7 @@
|
|||
core-js "^3.0.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"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.0.26.tgz#bd7fc1fcdc47bb7992fa8d3254367e8c3bba373d"
|
||||
integrity sha512-3t8AOPkp8ZW74h7FnzxF3wAeb1wRyYjMmgJZxqzgi/x7K0i1inbCq8MuJnytuTcZ7+EK4HR6Ih7o9tJuAtIBLw==
|
||||
|
|
Loading…
Reference in a new issue