[sharedUX] Move to Package-based Architecture (#127546)

* [shared-ux][packages] 1. Create Services Package

* Address review feedback

* [shared-ux][packages] 2. Create Storybook Package (#127548)

* [shared-ux][packages] 2. Create Storybook Package

* [shared-ux][packages] 3. Create Utility Package (#127549)

* [shared-ux][packages] 3. Create Utility Package

* [shared-ux][packages] 4. Create Components Package (#127551)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* Merging

* Adding docs

* A few fixes

* Fix TS types

* Fix TS types

* Fix i18n

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Clint Andrew Hall 2022-03-16 16:13:58 -05:00 committed by GitHub
parent b5fd56b96e
commit 71af73c18b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
152 changed files with 1889 additions and 578 deletions

View file

@ -60,6 +60,7 @@
"server": "src/legacy/server",
"share": "src/plugins/share",
"sharedUX": "src/plugins/shared_ux",
"sharedUXComponents": "packages/kbn-shared-ux-components/src",
"statusPage": "src/legacy/core_plugins/status_page",
"telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"],
"timelion": ["src/plugins/vis_types/timelion"],

View file

@ -167,6 +167,10 @@
"@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils",
"@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools",
"@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository",
"@kbn/shared-ux-components": "link:bazel-bin/packages/kbn-shared-ux-components",
"@kbn/shared-ux-services": "link:bazel-bin/packages/kbn-shared-ux-services",
"@kbn/shared-ux-storybook": "link:bazel-bin/packages/kbn-shared-ux-storybook",
"@kbn/shared-ux-utility": "link:bazel-bin/packages/kbn-shared-ux-utility",
"@kbn/std": "link:bazel-bin/packages/kbn-std",
"@kbn/timelion-grammar": "link:bazel-bin/packages/kbn-timelion-grammar",
"@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath",
@ -196,6 +200,10 @@
"@turf/helpers": "6.0.1",
"@turf/length": "^6.0.2",
"@types/jsonwebtoken": "^8.5.6",
"@types/kbn__shared-ux-components": "link:bazel-bin/packages/kbn-shared-ux-components/npm_module_types",
"@types/kbn__shared-ux-services": "link:bazel-bin/packages/kbn-shared-ux-services/npm_module_types",
"@types/kbn__shared-ux-storybook": "link:bazel-bin/packages/kbn-shared-ux-storybook/npm_module_types",
"@types/kbn__shared-ux-utility": "link:bazel-bin/packages/kbn-shared-ux-utility/npm_module_types",
"@types/moment-duration-format": "^2.2.3",
"JSONStream": "1.3.5",
"abort-controller": "^3.0.0",

View file

@ -66,6 +66,10 @@ filegroup(
"//packages/kbn-securitysolution-utils:build",
"//packages/kbn-server-http-tools:build",
"//packages/kbn-server-route-repository:build",
"//packages/kbn-shared-ux-components:build",
"//packages/kbn-shared-ux-services:build",
"//packages/kbn-shared-ux-storybook:build",
"//packages/kbn-shared-ux-utility:build",
"//packages/kbn-spec-to-console:build",
"//packages/kbn-std:build",
"//packages/kbn-storybook:build",
@ -139,6 +143,10 @@ filegroup(
"//packages/kbn-securitysolution-utils:build_types",
"//packages/kbn-server-http-tools:build_types",
"//packages/kbn-server-route-repository:build_types",
"//packages/kbn-shared-ux-components:build_types",
"//packages/kbn-shared-ux-services:build_types",
"//packages/kbn-shared-ux-storybook:build_types",
"//packages/kbn-shared-ux-utility:build_types",
"//packages/kbn-std:build_types",
"//packages/kbn-storybook:build_types",
"//packages/kbn-telemetry-tools:build_types",

View file

@ -0,0 +1,150 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "kbn-shared-ux-components"
PKG_REQUIRE_NAME = "@kbn/shared-ux-components"
SOURCE_FILES = glob(
[
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.scss",
"src/**/*.mdx",
],
exclude = [
"**/*.test.*",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"//packages/kbn-i18n",
"//packages/kbn-i18n-react",
"//packages/kbn-shared-ux-services",
"//packages/kbn-shared-ux-storybook",
"//packages/kbn-shared-ux-utility",
"@npm//@elastic/eui",
"@npm//@emotion/react",
"@npm//@emotion/css",
"@npm//classnames",
"@npm//react-use",
"@npm//react",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"//packages/kbn-i18n:npm_module_types",
"//packages/kbn-i18n-react:npm_module_types",
"//packages/kbn-shared-ux-services:npm_module_types",
"//packages/kbn-shared-ux-storybook:npm_module_types",
"//packages/kbn-shared-ux-utility:npm_module_types",
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@types/react",
"@npm//@types/classnames",
"@npm//@emotion/react",
"@npm//@emotion/css",
"@npm//@elastic/eui",
"@npm//react-use",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
additional_args = [
"--copy-files",
"--quiet"
],
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,10 @@
---
id: kibSharedUXComponents
slug: /kibana-dev-docs/shared-ux/packages/kbn-shared-ux-components
title: Shared UX Components
summary:
date: 2022-03-11
tags: ['kibana', 'dev', 'sharedUX']
---
> TODO

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-shared-ux-components'],
};

View file

@ -0,0 +1,8 @@
{
"name": "@kbn/shared-ux-components",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -10,7 +10,7 @@ import React from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { withSuspense } from '../../utility';
import { withSuspense } from '@kbn/shared-ux-utility';
export const LazyDataViewIllustration = React.lazy(() =>
import('../assets/data_view_illustration').then(({ DataViewIllustration }) => ({

View file

@ -10,7 +10,7 @@ exports[`<DocumentationLink /> is rendered correctly 1`] = `
>
<FormattedMessage
defaultMessage="Want to learn more?"
id="sharedUX.noDataViews.learnMore"
id="sharedUXComponents.noDataViews.learnMore"
values={Object {}}
/>
</dt>
@ -26,7 +26,7 @@ exports[`<DocumentationLink /> is rendered correctly 1`] = `
>
<FormattedMessage
defaultMessage="Read the docs"
id="sharedUX.noDataViews.readDocumentation"
id="sharedUXComponents.noDataViews.readDocumentation"
values={Object {}}
/>
</EuiLink>

View file

@ -20,7 +20,7 @@ export function DocumentationLink({ href }: Props) {
<EuiTitle size="xxs">
<dt className="eui-displayInline">
<FormattedMessage
id="sharedUX.noDataViews.learnMore"
id="sharedUXComponents.noDataViews.learnMore"
defaultMessage="Want to learn more?"
/>
</dt>
@ -29,7 +29,7 @@ export function DocumentationLink({ href }: Props) {
<dd className="eui-displayInline">
<EuiLink href={href} target="_blank" external>
<FormattedMessage
id="sharedUX.noDataViews.readDocumentation"
id="sharedUXComponents.noDataViews.readDocumentation"
defaultMessage="Read the docs"
/>
</EuiLink>

View file

@ -23,7 +23,7 @@ export interface Props {
emptyPromptColor?: EuiEmptyPromptProps['color'];
}
const createDataViewText = i18n.translate('sharedUX.noDataViewsPage.addDataViewText', {
const createDataViewText = i18n.translate('sharedUXComponents.noDataViewsPage.addDataViewText', {
defaultMessage: 'Create Data View',
});
@ -62,12 +62,12 @@ export const NoDataViews = ({
title={
<h2>
<FormattedMessage
id="sharedUX.noDataViews.youHaveData"
id="sharedUXComponents.noDataViews.youHaveData"
defaultMessage="You have data in Elasticsearch."
/>
<br />
<FormattedMessage
id="sharedUX.noDataViews.nowCreate"
id="sharedUXComponents.noDataViews.nowCreate"
defaultMessage="Now, create a data view."
/>
</h2>
@ -75,7 +75,7 @@ export const NoDataViews = ({
body={
<p>
<FormattedMessage
id="sharedUX.noDataViews.dataViewExplanation"
id="sharedUXComponents.noDataViews.dataViewExplanation"
defaultMessage="Kibana requires a data view to identify which data streams,
indices, and index aliases you want to explore. A data view can point to a
specific index, for example, your log data from yesterday, or all indices

View file

@ -8,13 +8,15 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { docLinksServiceFactory } from '../../../services/storybook/doc_links';
import { servicesFactory } from '@kbn/shared-ux-storybook';
import { NoDataViews as NoDataViewsComponent, Props } from './no_data_views.component';
import { NoDataViews } from './no_data_views';
import mdx from './no_data_views.mdx';
const services = servicesFactory({});
export default {
title: 'No Data Views',
description: 'A component to display when there are no user-created data views available.',
@ -26,12 +28,7 @@ export default {
};
export const ConnectedComponent = () => {
return (
<NoDataViews
onDataViewCreated={action('onDataViewCreated')}
dataViewsDocLink={docLinksServiceFactory().dataViewsDocsLink}
/>
);
return <NoDataViews onDataViewCreated={action('onDataViewCreated')} />;
};
type Params = Pick<Props, 'canCreateNewDataView' | 'dataViewsDocLink'>;
@ -46,7 +43,7 @@ PureComponent.argTypes = {
defaultValue: true,
},
dataViewsDocLink: {
options: [docLinksServiceFactory().dataViewsDocsLink, undefined],
options: [services.docLinks.dataViewsDocLink, undefined],
control: { type: 'radio' },
},
};

View file

@ -12,18 +12,21 @@ import { ReactWrapper } from 'enzyme';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { EuiButton } from '@elastic/eui';
import { ServicesProvider, SharedUXServices } from '../../../services';
import { servicesFactory } from '../../../services/mocks';
import {
SharedUxServicesProvider,
SharedUxServices,
mockServicesFactory,
} from '@kbn/shared-ux-services';
import { NoDataViews } from './no_data_views';
describe('<NoDataViewsPageTest />', () => {
let services: SharedUXServices;
let services: SharedUxServices;
let mount: (element: JSX.Element) => ReactWrapper;
beforeEach(() => {
services = servicesFactory();
services = mockServicesFactory();
mount = (element: JSX.Element) =>
mountWithIntl(<ServicesProvider {...services}>{element}</ServicesProvider>);
mountWithIntl(<SharedUxServicesProvider {...services}>{element}</SharedUxServicesProvider>);
});
afterEach(() => {
@ -31,12 +34,7 @@ describe('<NoDataViewsPageTest />', () => {
});
test('on dataView created', () => {
const component = mount(
<NoDataViews
onDataViewCreated={jest.fn()}
dataViewsDocLink={services.docLinks.dataViewsDocsLink}
/>
);
const component = mount(<NoDataViews onDataViewCreated={jest.fn()} />);
expect(services.editors.openDataViewEditor).not.toHaveBeenCalled();
component.find(EuiButton).simulate('click');

View file

@ -8,18 +8,17 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { DataView } from '../../../../../data_views/public';
import { useEditors, usePermissions } from '../../../services';
import type { SharedUXEditorsService } from '../../../services/editors';
import { useEditors, usePermissions, useDocLinks } from '@kbn/shared-ux-services';
import type { SharedUxEditorsService } from '@kbn/shared-ux-services';
import { NoDataViews as NoDataViewsComponent } from './no_data_views.component';
// TODO: https://github.com/elastic/kibana/issues/127695
export interface Props {
onDataViewCreated: (dataView: DataView) => void;
dataViewsDocLink: string;
onDataViewCreated: (dataView: unknown) => void;
}
type CloseDataViewEditorFn = ReturnType<SharedUXEditorsService['openDataViewEditor']>;
type CloseDataViewEditorFn = ReturnType<SharedUxEditorsService['openDataViewEditor']>;
/**
* A service-enabled component that provides Kibana-specific functionality to the `NoDataViews`
@ -30,9 +29,10 @@ type CloseDataViewEditorFn = ReturnType<SharedUXEditorsService['openDataViewEdit
*
* See shared-ux/public/services for information.
*/
export const NoDataViews = ({ onDataViewCreated, dataViewsDocLink }: Props) => {
export const NoDataViews = ({ onDataViewCreated }: Props) => {
const { canCreateNewDataView } = usePermissions();
const { openDataViewEditor } = useEditors();
const { dataViewsDocLink } = useDocLinks();
const closeDataViewEditor = useRef<CloseDataViewEditorFn>();
useEffect(() => {

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ExitFullScreenButton /> is rendered 1`] = `
<ServicesProvider
<SharedUxServicesProvider
application={
Object {
"currentAppId$": Observable {
@ -12,7 +12,7 @@ exports[`<ExitFullScreenButton /> is rendered 1`] = `
}
docLinks={
Object {
"dataViewsDocsLink": "dummy link",
"dataViewsDocLink": "dummy link",
}
}
editors={
@ -134,5 +134,5 @@ exports[`<ExitFullScreenButton /> is rendered 1`] = `
</div>
</ExitFullScreenButton>
</ExitFullScreenButton>
</ServicesProvider>
</SharedUxServicesProvider>
`;

View file

@ -21,13 +21,19 @@ import cx from 'classnames';
import './exit_full_screen_button.scss';
const text = i18n.translate('sharedUX.exitFullScreenButton.exitFullScreenModeButtonText', {
defaultMessage: 'Exit full screen',
});
const text = i18n.translate(
'sharedUXComponents.exitFullScreenButton.exitFullScreenModeButtonText',
{
defaultMessage: 'Exit full screen',
}
);
const description = i18n.translate('sharedUX.exitFullScreenButton.fullScreenModeDescription', {
defaultMessage: 'In full screen mode, press ESC to exit.',
});
const description = i18n.translate(
'sharedUXComponents.exitFullScreenButton.fullScreenModeDescription',
{
defaultMessage: 'In full screen mode, press ESC to exit.',
}
);
/**
* Props for the Exit Full Screen button component.

View file

@ -10,18 +10,21 @@ import React from 'react';
import { mount as enzymeMount, ReactWrapper } from 'enzyme';
import { keys } from '@elastic/eui';
import { ServicesProvider, SharedUXServices } from '../../services';
import { servicesFactory } from '../../services/mocks';
import {
SharedUxServicesProvider,
SharedUxServices,
mockServicesFactory,
} from '@kbn/shared-ux-services';
import { ExitFullScreenButton } from './exit_full_screen_button';
describe('<ExitFullScreenButton />', () => {
let services: SharedUXServices;
let services: SharedUxServices;
let mount: (element: JSX.Element) => ReactWrapper;
beforeEach(() => {
services = servicesFactory();
services = mockServicesFactory();
mount = (element: JSX.Element) =>
enzymeMount(<ServicesProvider {...services}>{element}</ServicesProvider>);
enzymeMount(<SharedUxServicesProvider {...services}>{element}</SharedUxServicesProvider>);
});
afterEach(() => {

View file

@ -10,8 +10,9 @@ import React, { useCallback, useEffect } from 'react';
import { useEuiTheme, keys } from '@elastic/eui';
import { css } from '@emotion/react';
import { usePlatformService } from '@kbn/shared-ux-services';
import { ExitFullScreenButton as Component } from './exit_full_screen_button.component';
import { usePlatformService } from '../../services';
/**
* Props for the service-enabled Exit Full Screen button component.

View file

@ -7,7 +7,7 @@
*/
import React from 'react';
import { withSuspense } from './utility';
import { withSuspense } from '@kbn/shared-ux-utility';
/**
* The Lazily-loaded `ExitFullScreenButton` component. Consumers should use `React.Suspennse` or the
@ -45,18 +45,18 @@ export const ToolbarButton = withSuspense(LazyToolbarButton);
* The Lazily-loaded `NoDataViews` component. Consumers should use `React.Suspennse` or the
* `withSuspense` HOC to load this component.
*/
export const LazyNoDataViewsPage = React.lazy(() =>
export const LazyNoDataViews = React.lazy(() =>
import('./empty_state/no_data_views').then(({ NoDataViews }) => ({
default: NoDataViews,
}))
);
/**
* A `NoDataViewsPage` component that is wrapped by the `withSuspense` HOC. This component can
* be used directly by consumers and will load the `LazyNoDataViewsPage` component lazily with
* A `NoDataViews` component that is wrapped by the `withSuspense` HOC. This component can
* be used directly by consumers and will load the `LazyNoDataViews` component lazily with
* a predefined fallback and error boundary.
*/
export const NoDataViewsPage = withSuspense(LazyNoDataViewsPage);
export const NoDataViews = withSuspense(LazyNoDataViews);
/**
* The Lazily-loaded `IconButtonGroup` component. Consumers should use `React.Suspennse` or the

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { NoDataCard, ElasticAgentCard } from './no_data_page/no_data_card';
export { NoDataCard, ElasticAgentCard } from './no_data_page';

View file

@ -6,6 +6,4 @@
* Side Public License, v 1.
*/
import { defaultConfig } from '@kbn/storybook';
module.exports = defaultConfig;
export { NoDataCard, ElasticAgentCard } from './no_data_card';

View file

@ -5,7 +5,7 @@ exports[`ElasticAgentCard renders 1`] = `
max-width: 400px;
}
<ServicesProvider
<SharedUxServicesProvider
application={
Object {
"currentAppId$": Observable {
@ -16,7 +16,7 @@ exports[`ElasticAgentCard renders 1`] = `
}
docLinks={
Object {
"dataViewsDocsLink": "dummy link",
"dataViewsDocLink": "dummy link",
}
}
editors={
@ -316,5 +316,5 @@ exports[`ElasticAgentCard renders 1`] = `
</RedirectAppLinks>
</ElasticAgentCardComponent>
</ElasticAgentCard>
</ServicesProvider>
</SharedUxServicesProvider>
`;

View file

@ -22,25 +22,28 @@ export type ElasticAgentCardComponentProps = ElasticAgentCardProps & {
};
const noPermissionTitle = i18n.translate(
'sharedUX.noDataPage.elasticAgentCard.noPermission.title',
'sharedUXComponents.noDataPage.elasticAgentCard.noPermission.title',
{
defaultMessage: `Contact your administrator`,
}
);
const noPermissionDescription = i18n.translate(
'sharedUX.noDataPage.elasticAgentCard.noPermission.description',
'sharedUXComponents.noDataPage.elasticAgentCard.noPermission.description',
{
defaultMessage: `This integration is not yet enabled. Your administrator has the required permissions to turn it on.`,
}
);
const elasticAgentCardTitle = i18n.translate('sharedUX.noDataPage.elasticAgentCard.title', {
defaultMessage: 'Add Elastic Agent',
});
const elasticAgentCardTitle = i18n.translate(
'sharedUXComponents.noDataPage.elasticAgentCard.title',
{
defaultMessage: 'Add Elastic Agent',
}
);
const elasticAgentCardDescription = i18n.translate(
'sharedUX.noDataPage.elasticAgentCard.description',
'sharedUXComponents.noDataPage.elasticAgentCard.description',
{
defaultMessage: `Use Elastic Agent for a simple, unified way to collect data from your machines.`,
}

View file

@ -7,11 +7,12 @@
*/
import React from 'react';
import { applicationServiceFactory } from '@kbn/shared-ux-storybook';
import {
ElasticAgentCardComponent,
ElasticAgentCardComponentProps,
} from './elastic_agent_card.component';
import { applicationServiceFactory } from '../../../../services/storybook/application';
export default {
title: 'Elastic Agent Data Card',

View file

@ -8,21 +8,24 @@
import { ReactWrapper } from 'enzyme';
import React from 'react';
import { ElasticAgentCard } from './elastic_agent_card';
import { servicesFactory } from '../../../../services/mocks';
import { ServicesProvider, SharedUXServices } from '../../../../services';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import {
SharedUxServicesProvider,
SharedUxServices,
mockServicesFactory,
} from '@kbn/shared-ux-services';
import { ElasticAgentCard } from './elastic_agent_card';
import { ElasticAgentCardComponent } from './elastic_agent_card.component';
describe('ElasticAgentCard', () => {
let services: SharedUXServices;
let services: SharedUxServices;
let mount: (element: JSX.Element) => ReactWrapper;
beforeEach(() => {
services = servicesFactory();
services = mockServicesFactory();
mount = (element: JSX.Element) =>
mountWithIntl(<ServicesProvider {...services}>{element}</ServicesProvider>);
mountWithIntl(<SharedUxServicesProvider {...services}>{element}</SharedUxServicesProvider>);
});
afterEach(() => {

View file

@ -7,8 +7,9 @@
*/
import React from 'react';
import { useApplication, useHttp, usePermissions } from '@kbn/shared-ux-services';
import { ElasticAgentCardProps } from './types';
import { useApplication, useHttp, usePermissions } from '../../../../services';
import { ElasticAgentCardComponent } from './elastic_agent_card.component';
export const ElasticAgentCard = (props: ElasticAgentCardProps) => {

View file

@ -9,16 +9,23 @@
import { i18n } from '@kbn/i18n';
import React, { FunctionComponent } from 'react';
import { EuiButton, EuiCard } from '@elastic/eui';
import type { NoDataCardProps } from './types';
import { NoDataCardStyles } from './no_data_card.styles';
const recommendedLabel = i18n.translate('sharedUX.pageTemplate.noDataPage.recommendedLabel', {
defaultMessage: 'Recommended',
});
const recommendedLabel = i18n.translate(
'sharedUXComponents.pageTemplate.noDataPage.recommendedLabel',
{
defaultMessage: 'Recommended',
}
);
const defaultDescription = i18n.translate('sharedUX.pageTemplate.noDataCard.description', {
defaultMessage: 'Proceed without collecting data',
});
const defaultDescription = i18n.translate(
'sharedUXComponents.pageTemplate.noDataCard.description',
{
defaultMessage: `Proceed without collecting data`,
}
);
export const NoDataCard: FunctionComponent<NoDataCardProps> = ({
recommended,

View file

@ -7,7 +7,6 @@
*/
import { MouseEvent } from 'react';
import { ApplicationStart } from 'src/core/public';
import { createNavigateToUrlClickHandler } from './click_handler';
const createLink = ({
@ -42,9 +41,11 @@ const createEvent = ({
} as unknown as MouseEvent<HTMLElement>;
};
type NavigateToURLFn = (url: string) => Promise<void>;
describe('createNavigateToUrlClickHandler', () => {
let container: HTMLElement;
let navigateToUrl: jest.MockedFunction<ApplicationStart['navigateToUrl']>;
let navigateToUrl: jest.MockedFunction<NavigateToURLFn>;
const createHandler = () =>
createNavigateToUrlClickHandler({

View file

@ -7,11 +7,10 @@
*/
import React from 'react';
import { ApplicationStart } from 'src/core/public';
import { getClosestLink, hasActiveModifierKey } from '../utility/utils';
import { getClosestLink, hasActiveModifierKey } from '@kbn/shared-ux-utility';
interface CreateCrossAppClickHandlerOptions {
navigateToUrl: ApplicationStart['navigateToUrl'];
navigateToUrl(url: string): Promise<void>;
container?: HTMLElement;
}

View file

@ -8,18 +8,30 @@
import React, { MouseEvent } from 'react';
import { mount } from 'enzyme';
import { applicationServiceMock } from '../../../../../core/public/mocks';
import { RedirectAppLinks } from './redirect_app_links';
import { BehaviorSubject } from 'rxjs';
import { RedirectAppLinks } from './redirect_app_links';
export type UnmountCallback = () => void;
export type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
const createServiceMock = () => {
const currentAppId$ = new BehaviorSubject<string>('currentApp');
return {
currentAppId$: currentAppId$.asObservable(),
navigateToApp: jest.fn(),
navigateToUrl: jest.fn(),
};
};
/* eslint-disable jsx-a11y/click-events-have-key-events */
describe('RedirectAppLinks', () => {
let application: ReturnType<typeof applicationServiceMock.createStartContract>;
let application = createServiceMock();
beforeEach(() => {
application = applicationServiceMock.createStartContract();
application.currentAppId$ = new BehaviorSubject<string>('currentApp');
application = createServiceMock();
});
it('intercept click events on children link elements', () => {

View file

@ -6,17 +6,21 @@
* Side Public License, v 1.
*/
import React, { FunctionComponent, useRef, useMemo } from 'react';
import React, { useRef, useMemo } from 'react';
import type { HTMLAttributes, DetailedHTMLProps, FC } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { ApplicationStart } from 'src/core/public';
import { Observable } from 'rxjs';
import { createNavigateToUrlClickHandler } from './click_handler';
type Props = React.HTMLAttributes<HTMLDivElement> &
Pick<ApplicationStart, 'navigateToUrl' | 'currentAppId$'>;
export interface RedirectAppLinksProps extends Props {
className?: string;
'data-test-subj'?: string;
type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
/**
* TODO: this interface recreates props from the `ApplicationStart` interface.
* see: https://github.com/elastic/kibana/issues/127695
*/
export interface RedirectAppLinksProps extends DivProps {
currentAppId$: Observable<string | undefined>;
navigateToUrl(url: string): Promise<void>;
}
/**
@ -36,7 +40,7 @@ export interface RedirectAppLinksProps extends Props {
* require to handle the links. A good practice is to consider it as a context provider and to use it
* at the root level of an application or of the page that require the feature.
*/
export const RedirectAppLinks: FunctionComponent<RedirectAppLinksProps> = ({
export const RedirectAppLinks: FC<RedirectAppLinksProps> = ({
navigateToUrl,
currentAppId$,
children,

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
declare module '*.svg' {
const content: string;
// eslint-disable-next-line import/no-default-export
export default content;
}

View file

@ -16,7 +16,7 @@ exports[`<IconButtonGroup /> is rendered 1`] = `
border-bottom-right-radius: 6px!important;
}
<ServicesProvider
<SharedUxServicesProvider
application={
Object {
"currentAppId$": Observable {
@ -27,7 +27,7 @@ exports[`<IconButtonGroup /> is rendered 1`] = `
}
docLinks={
Object {
"dataViewsDocsLink": "dummy link",
"dataViewsDocLink": "dummy link",
}
}
editors={
@ -212,5 +212,5 @@ exports[`<IconButtonGroup /> is rendered 1`] = `
</fieldset>
</EuiButtonGroup>
</IconButtonGroup>
</ServicesProvider>
</SharedUxServicesProvider>
`;

View file

@ -8,19 +8,22 @@
import React from 'react';
import { mount as enzymeMount, ReactWrapper } from 'enzyme';
import {
mockServicesFactory,
SharedUxServices,
SharedUxServicesProvider,
} from '@kbn/shared-ux-services';
import { ServicesProvider, SharedUXServices } from '../../../../services';
import { servicesFactory } from '../../../../services/mocks';
import { IconButtonGroup } from './icon_button_group';
describe('<IconButtonGroup />', () => {
let services: SharedUXServices;
let services: SharedUxServices;
let mount: (element: JSX.Element) => ReactWrapper;
beforeEach(() => {
services = servicesFactory();
services = mockServicesFactory();
mount = (element: JSX.Element) =>
enzymeMount(<ServicesProvider {...services}>{element}</ServicesProvider>);
enzymeMount(<SharedUxServicesProvider {...services}>{element}</SharedUxServicesProvider>);
});
test('is rendered', () => {

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ToolbarButton /> is rendered 1`] = `
<ServicesProvider
<SharedUxServicesProvider
application={
Object {
"currentAppId$": Observable {
@ -12,7 +12,7 @@ exports[`<ToolbarButton /> is rendered 1`] = `
}
docLinks={
Object {
"dataViewsDocsLink": "dummy link",
"dataViewsDocLink": "dummy link",
}
}
editors={
@ -88,5 +88,5 @@ exports[`<ToolbarButton /> is rendered 1`] = `
</EuiButtonDisplay>
</EuiButton>
</ToolbarButton>
</ServicesProvider>
</SharedUxServicesProvider>
`;

View file

@ -8,19 +8,23 @@
import { mount as enzymeMount, ReactWrapper } from 'enzyme';
import React from 'react';
import { ServicesProvider, SharedUXServices } from '../../../../services';
import { servicesFactory } from '../../../../services/mocks';
import {
SharedUxServicesProvider,
SharedUxServices,
mockServicesFactory,
} from '@kbn/shared-ux-services';
import { ToolbarButton } from './primary';
describe('<ToolbarButton />', () => {
let services: SharedUXServices;
let services: SharedUxServices;
let mount: (element: JSX.Element) => ReactWrapper;
beforeEach(() => {
services = servicesFactory();
services = mockServicesFactory();
mount = (element: JSX.Element) =>
enzymeMount(<ServicesProvider {...services}>{element}</ServicesProvider>);
enzymeMount(<SharedUxServicesProvider {...services}>{element}</SharedUxServicesProvider>);
});
afterEach(() => {

View file

@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"rootDir": "src",
"stripInternal": false,
"types": [
"jest",
"node",
"@emotion/react/types/css-prop"
]
},
"include": [
"src/**/*"
]
}

View file

@ -0,0 +1,126 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "kbn-shared-ux-services"
PKG_REQUIRE_NAME = "@kbn/shared-ux-services"
SOURCE_FILES = glob(
[
"src/**/*.ts",
"src/**/*.tsx",
],
exclude = [
"**/*.test.*",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"@npm//react",
"@npm//rxjs",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@types/react",
"@npm//rxjs",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,203 @@
---
id: kibSharedUXServices
slug: /kibana-dev-docs/shared-ux/packages/kbn-shared-ux-services
title: Shared UX Services
summary: The `@kbn/shared-ux-services` package provides a thin service abstraction for components and solutions created by the Shared UX team.
date: 2022-03-11
tags: ['kibana', 'dev', 'sharedUX']
---
## About Shared UX Services
This package contains a set of services that are used by Shared UX components and solutions. This package serves as a thin abstraction layer between Kibana dependencies and the components in Shared UX that use them. It also allows us to "swap out" different implementations of the interfaces for different environments, (e.g. Storybook, Jest, etc). This decouples the components from what could be complicated or heavily-dependent logic that is difficult to mock.
## Implementations
Several implementations of these interfaces exist:
- `@kbn/shared-ux-services/src/services/stub`: A stub implementation free of dependencies, (and functionality).
- `@kbn/shared-ux-services/src/services/mock`: A Jest mock implementation used in `jest` tests.
- `@kbn/shared-ux-storybook/src/services/`: A Storybook implementation used in Storybook decorators and stories.
- `src/plugins/shared_ux/src/services/`: A Kibana implementation used in Kibana plugins.
Other implementations could easily be written to support other environments.
## Architecture
Lots of components require access to the services provided by other plugins. When we identify a routine that relies on these dependencies, we can write a new method and add it to a namespace, (e.g. `platform`, `user`, etc). These namespaces become interfaces of simple methods stored in `@kbn/shared-ux-services`. From there, we can create implementations for each environment we support.
Suppose we're creating a new service, `SharedUxFooService`:
```ts
interface SharedUxFooService {
getFoo(): string;
setBar(bar: string): void;
isBaz(): boolean;
}
```
Once defined, we create factories to create those services.
### Creating a `ServiceFactory`
A `ServiceFactory` is a simple type that describes 1/ what service is being created, and 2/ what parameters are required to create that service for a given environment.
### Stub and Mock Factories
Given the service definition above, we can create a `ServiceFactory` for a stubbed service that gives the bare minimum of functionality:
```ts
/**
* A factory function for creating a stubbed implementation of `SharedUxFooService`.
*/
export type FooServiceFactory = ServiceFactory<SharedUxFooService>;
/**
* A factory function for creating a stubbed implementation of `SharedUxFooService`.
*/
export const fooServiceFactory: FooServiceFactory = () => ({
getFoo: () => 'foo',
setBar: () => {},
isBaz: () => false,
});
```
We can also create a mock for Jest:
```ts
/**
* A factory function for creating a mock implementation of `SharedUxFooService`.
*/
export type FooServiceFactory = ServiceFactory<SharedUxFooService>;
/**
* A factory function for creating a stubbed implementation of `SharedUxFooService`.
*/
export const fooServiceFactory: FooServiceFactory = () => ({
getFoo: () => jest.fn(),
setBar: () => jest.fn(),
isBaz: () => jest.fn(),
});
```
### Storybook Factories
Storybook is where we can begin to take advantage of `Parameters` for a given service. Since stories can use controls to provide parameters, we can create a `ServiceFactory` that uses the `Parameters` generic and returns a `SharedUxFooService` that uses their values.
```ts
import { action } from '@storybook/addon-actions';
interface FooServiceStorybookParameters {
foo: string;
baz: boolean;
}
/**
* A factory function for creating a Storybook implementation of `SharedUxFooService`.
*/
export type FooServiceFactory = ServiceFactory<SharedUxFooService, StorybookParameters>;
/**
* A factory function for creating a stubbed implementation of `SharedUxFooService`.
*/
export const fooServiceFactory: FooServiceFactory = ({ foo, baz }) => ({
getFoo: () => foo,
setBar: () => action('setBar'),
isBaz: () => baz,
});
```
A story can then optionally provide values for those parameters as part of its controls.
```ts
type Params = Pick<FooServiceStorybookParameters, 'foo'>;
export const ComponentStory = ({ foo }: Params) => {
const service = fooServiceFactory({ foo, baz: false });
return (
<SharedUxServicesContext.Provider value={service}><Component /></SharedUxServicesContext.Provider>;
};
PureComponent.argTypes = {
foo: {
options: ['alpha', 'beta', 'gamma', 'delta'],
control: { type: 'radio' },
},
};
```
### Kibana Factories
Using these services in Kibana is a bit more complex, but is still relatively simple. First, we define what dependencies we'll need, (we use this interface in `src/plugins/shared_ux` as it relies on types found only in plugins, where packages cannot use them):
```ts
/**
* 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>;
initContext?: PluginInitializerContext;
}
/**
* A factory function for creating a Kibana-based service.
*
* The `Service` generic determines the shape of the Service being produced.
* The `Start` generic refers to the specific Plugin `TPluginsStart`.
*/
export type KibanaPluginServiceFactory<Service, Start extends {}> = (
params: KibanaPluginServiceParams<Start>
) => Service;
```
From there, a plugin might have a collection of dependencies on core or other plugins:
```ts
export interface MyPluginStartDeps {
bar: BarPluginStart;
baz: BazPluginStart;
}
```
We'd then use this dependency interface to create a `ServiceFactory` for our service in Kibana:
```ts
export type FooServiceFactory = KibanaPluginServiceFactory<
SharedUxFooService,
MyPluginStartDeps
>;
/**
* A factory function for creating a Kibana-based implementation of `SharedUxFooService`.
*/
export const fooServiceFactory: FooServiceFactory = ({ coreStart, startPlugins }) => ({
getFoo: startPlugins.bar.getSomeOtherFoo,
setBar: startPlugins.baz.setHappyPathBar,
isBaz: () => {
return coreStart.uiSettings.get('someSetting') === 'expectedValue';
}
});
```
From there, the pattern is the same: invoke the service factory with the required dependencies and provide them to the `SharedUxServicesContext` Provider:
```ts
// plugin.tsx
public start(coreStart: CoreStart, startPlugins: SharedUXPluginStartDeps): SharedUXPluginStart {
const service = fooServiceFactory({ coreStart, startPlugins });
const Context = <SharedUxServicesProvider value={service}>{children}</SharedUxServicesProvider>;
// ...wrap React content with the context..
}
```
## Use in Kibana plugins
In order to make consumption of these services easy by Kibana plugins, `src/plugins/shared_ux` provides a pre-wired `SharedUxServicesProvider` component as part of the `start` lifecycle. Plugins can simply make `sharedUX` a dependency and wrap their solution root or any component. See the documentation for `sharedUX` for more details.

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-shared-ux-services'],
};

View file

@ -0,0 +1,8 @@
{
"name": "@kbn/shared-ux-services",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { FC, createContext, useContext } from 'react';
import type { SharedUxServices } from './types';
// The React Context used to provide the services to the SharedUX components.
const SharedUxServicesContext = createContext<SharedUxServices | null>(null);
/**
* The `React.Context` Provider component for the `SharedUxServices` context. Any
* plugin or environment that consumes SharedUX components needs to wrap their React
* tree with this provider.
*
* Within a plugin, you can use use the Shared UX plugin and retrieve a fully-configured
* context from the `start` contract.
*/
export const SharedUxServicesProvider: FC<SharedUxServices> = ({ children, ...services }) => (
<SharedUxServicesContext.Provider value={services}>{children}</SharedUxServicesContext.Provider>
);
/**
* React hook for accessing pre-wired `SharedUxServices`.
*/
export function useSharedUxServices() {
const context = useContext(SharedUxServicesContext);
if (!context) {
throw new Error(
'SharedUxServicesContext missing. Ensure your component or React root is wrapped with SharedUxServicesProvider.'
);
}
return context;
}
/**
* React hook for accessing the pre-wired `SharedUxPlatformService`.
*/
export const usePlatformService = () => useSharedUxServices().platform;
/**
* React hook for accessing the pre-wired `SharedUxPermissionsService`.
*/
export const usePermissions = () => useSharedUxServices().permissions;
/**
* React hook for accessing the pre-wired `SharedUxEditorsService`.
*/
export const useEditors = () => useSharedUxServices().editors;
/**
* React hook for accessing the pre-wired `SharedUxDocLinksService`.
*/
export const useDocLinks = () => useSharedUxServices().docLinks;
export const useHttp = () => useSharedUxServices().http;
export const useApplication = () => useSharedUxServices().application;

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type { ServiceFactory, SharedUxServices, SharedUxServicesContext } from './types';
export type {
SharedUxApplicationService,
SharedUxDocLinksService,
SharedUxEditorsService,
SharedUxHttpService,
SharedUxPlatformService,
SharedUxUserPermissionsService,
} from './services';
export {
SharedUxServicesProvider,
useApplication,
useDocLinks,
useEditors,
useHttp,
usePermissions,
usePlatformService,
useSharedUxServices,
} from './context';
export {
mockServiceFactories,
mockServicesFactory,
stubServiceFactories,
stubServicesFactory,
} from './services';

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Observable } from 'rxjs';
export interface SharedUxApplicationService {
navigateToUrl: (url: string) => Promise<void>;
currentAppId$: Observable<string | undefined>;
}

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* A service providing links to documentation about various features in Kibana.
*/
export interface SharedUxDocLinksService {
/** A link to information about Data Views in Kibana */
dataViewsDocLink: string;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* TODO: `DataView` is a class exported by `src/plugins/data_views/public`. Since this service
* is contained in this package-- and packages can only depend on other packages and never on
* plugins-- we have to set this to `unknown`. If and when `DataView` is exported from a
* stateless package, we can remove this.
*
* @see: https://github.com/elastic/kibana/issues/127695
*/
type DataView = unknown;
/**
* A subset of the `DataViewEditorOptions` interface relevant to our service and components.
*
* @see: src/plugins/data_view_editor/public/types.ts
*/
interface DataViewEditorOptions {
/** Handler to be invoked when the Data View Editor completes a save operation. */
onSave: (dataView: DataView) => void;
}
/**
* A service providing methods to invoke and interact with various editors provided
* in Kibana.
*/
export interface SharedUxEditorsService {
/** A method to open the Data View Editor flow. */
openDataViewEditor: (options: DataViewEditorOptions) => () => void;
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export interface SharedUxHttpService {
addBasePath: (url: string) => string;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type { SharedUxApplicationService } from './application';
export type { SharedUxDocLinksService } from './doc_links';
export type { SharedUxEditorsService } from './editors';
export type { SharedUxHttpService } from './http';
export type { SharedUxUserPermissionsService } from './permissions';
export type { SharedUxPlatformService } from './platform';
export { mockServicesFactory, mockServiceFactories } from './mock';
export { stubServicesFactory, stubServiceFactories } from './stub';

View file

@ -6,11 +6,11 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXApplicationService } from '../application';
import { Observable } from 'rxjs';
import { ServiceFactory } from '../../types';
import { SharedUxApplicationService } from '../application';
export type MockApplicationServiceFactory = PluginServiceFactory<SharedUXApplicationService>;
export type MockApplicationServiceFactory = ServiceFactory<SharedUxApplicationService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUXApplicationService`.

View file

@ -6,14 +6,17 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXDocLinksService } from '../doc_links';
export type MockDockLinksServiceFactory = PluginServiceFactory<SharedUXDocLinksService>;
import type { ServiceFactory } from '../../types';
import type { SharedUxDocLinksService } from '../doc_links';
/**
* A factory function for creating a Jest-based implementation of `SharedUXDocLinksService`.
* A factory function for creating a Jest implementation of `SharedUxDocLinksService`.
*/
export type MockDockLinksServiceFactory = ServiceFactory<SharedUxDocLinksService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUxDocLinksService`.
*/
export const docLinksServiceFactory: MockDockLinksServiceFactory = () => ({
dataViewsDocsLink: 'dummy link',
dataViewsDocLink: 'dummy link',
});

View file

@ -6,13 +6,16 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXEditorsService } from '../editors';
export type MockEditorsServiceFactory = PluginServiceFactory<SharedUXEditorsService>;
import type { ServiceFactory } from '../../types';
import type { SharedUxEditorsService } from '../editors';
/**
* A factory function for creating a Jest-based implementation of `SharedUXEditorsService`.
* A factory function for creating a Jest-based implementation of `SharedUxEditorsService`.
*/
export type MockEditorsServiceFactory = ServiceFactory<SharedUxEditorsService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUxEditorsService`.
*/
export const editorsServiceFactory: MockEditorsServiceFactory = () => ({
openDataViewEditor: jest.fn(),

View file

@ -6,10 +6,10 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXHttpService } from '../http';
import { ServiceFactory } from '../../types';
import { SharedUxHttpService } from '../http';
export type MockHttpServiceFactory = PluginServiceFactory<SharedUXHttpService>;
export type MockHttpServiceFactory = ServiceFactory<SharedUxHttpService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUXHttpService`.

View file

@ -5,27 +5,50 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SharedUxServices, ServiceFactory } from '../../types';
import { applicationServiceFactory } from './application.mock';
import { docLinksServiceFactory } from './doc_links.mock';
export type { MockPlatformServiceFactory } from './platform.mock';
export { platformServiceFactory } from './platform.mock';
import type { SharedUXServices } from '../.';
import { PluginServiceFactory } from '../types';
import { platformServiceFactory } from './platform.mock';
import { userPermissionsServiceFactory } from './permissions.mock';
import { editorsServiceFactory } from './editors.mock';
import { httpServiceFactory } from './http.mock';
import { applicationServiceFactory } from './application.mock';
import { userPermissionsServiceFactory } from './permissions.mock';
import { platformServiceFactory } from './platform.mock';
export type { MockApplicationServiceFactory } from './application.mock';
export type { MockDockLinksServiceFactory } from './doc_links.mock';
export type { MockEditorsServiceFactory } from './editors.mock';
export type { MockHttpServiceFactory } from './http.mock';
export type { MockUserPermissionsServiceFactory } from './permissions.mock';
export type { MockPlatformServiceFactory } from './platform.mock';
export { applicationServiceFactory } from './application.mock';
export { docLinksServiceFactory } from './doc_links.mock';
export { editorsServiceFactory } from './editors.mock';
export { httpServiceFactory } from './http.mock';
export { userPermissionsServiceFactory } from './permissions.mock';
export { platformServiceFactory } from './platform.mock';
/**
* A factory function for creating a Jest-based implementation of `SharedUXServices`.
* A factory function for creating a Jest-based implementation of `SharedUxServices`.
*/
export const servicesFactory: PluginServiceFactory<SharedUXServices> = () => ({
platform: platformServiceFactory(),
permissions: userPermissionsServiceFactory(),
editors: editorsServiceFactory(),
docLinks: docLinksServiceFactory(),
http: httpServiceFactory(),
export const mockServicesFactory: ServiceFactory<SharedUxServices> = () => ({
application: applicationServiceFactory(),
docLinks: docLinksServiceFactory(),
editors: editorsServiceFactory(),
http: httpServiceFactory(),
permissions: userPermissionsServiceFactory(),
platform: platformServiceFactory(),
});
/**
* A collection of mock Service Factories.
*/
export const mockServiceFactories = {
applicationServiceFactory,
docLinksServiceFactory,
editorsServiceFactory,
httpServiceFactory,
platformServiceFactory,
userPermissionsServiceFactory,
};

View file

@ -6,14 +6,16 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXUserPermissionsService } from '../permissions';
export type MockUserPermissionsServiceFactory =
PluginServiceFactory<SharedUXUserPermissionsService>;
import type { ServiceFactory } from '../../types';
import type { SharedUxUserPermissionsService } from '../permissions';
/**
* A factory function for creating a Jest-based implementation of `SharedUXUserPermissionsService`.
* A factory function for creating a Jest-based implementation of `SharedUxUserPermissionsService`.
*/
export type MockUserPermissionsServiceFactory = ServiceFactory<SharedUxUserPermissionsService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUxUserPermissionsService`.
*/
export const userPermissionsServiceFactory: MockUserPermissionsServiceFactory = () => ({
canCreateNewDataView: true,

View file

@ -6,16 +6,16 @@
* Side Public License, v 1.
*/
import type { PluginServiceFactory } from '../types';
import type { SharedUXPlatformService } from '../platform';
import type { ServiceFactory } from '../../types';
import type { SharedUxPlatformService } from '../platform';
/**
* A factory function for creating a Jest-based implementation of `SharedUXPlatformService`.
* A factory function for creating a Jest-based implementation of `SharedUxPlatformService`.
*/
export type MockPlatformServiceFactory = PluginServiceFactory<SharedUXPlatformService>;
export type MockPlatformServiceFactory = ServiceFactory<SharedUxPlatformService>;
/**
* A factory function for creating a Jest-based implementation of `SharedUXPlatformService`.
* A factory function for creating a Jest-based implementation of `SharedUxPlatformService`.
*/
export const platformServiceFactory: MockPlatformServiceFactory = () => ({
setIsFullscreen: jest.fn(),

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* A service providing permissions information, typically for the current user.
*/
export interface SharedUxUserPermissionsService {
/** True if the user has permission to create a new Data View, false otherwise. */
canCreateNewDataView: boolean;
canAccessFleet: boolean;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* A service providing methods to interact with the platform in which this code is
* running, (almost always Kibana).
*
* Rather than provide the entire `CoreStart` contract to components, we provide simplified
* abstractions around a use case specific to Shared UX. This way, we know exactly how the
* `CoreStart` and other plugins are used. This makes mocking and refactoring easier when
* upstream dependencies change.
*/
export interface SharedUxPlatformService {
/**
* Sets the fullscreen state of the chrome.
* @param isFullscreen True if the chrome should be fullscreen, false otherwise.
*/
setIsFullscreen: (isFullscreen: boolean) => void;
}

View file

@ -7,10 +7,10 @@
*/
import { Observable } from 'rxjs';
import { PluginServiceFactory } from '../types';
import { SharedUXApplicationService } from '../application';
import { ServiceFactory } from '../../types';
import { SharedUxApplicationService } from '../application';
export type ApplicationServiceFactory = PluginServiceFactory<SharedUXApplicationService>;
export type ApplicationServiceFactory = ServiceFactory<SharedUxApplicationService>;
/**
* A factory function for creating for creating a simple stubbed implementation of `SharedUXApplicationService`.

View file

@ -6,14 +6,17 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXDocLinksService } from '../doc_links';
export type DockLinksServiceFactory = PluginServiceFactory<SharedUXDocLinksService>;
import type { ServiceFactory } from '../../types';
import type { SharedUxDocLinksService } from '../doc_links';
/**
* A factory function for creating a Jest-based implementation of `SharedUXDocLinksService`.
* A factory function for creating a stubbed implementation of `SharedUxDocLinksService`.
*/
export type DockLinksServiceFactory = ServiceFactory<SharedUxDocLinksService>;
/**
* A factory function for creating a stubbed implementation of `SharedUxDocLinksService`.
*/
export const docLinksServiceFactory: DockLinksServiceFactory = () => ({
dataViewsDocsLink: 'docs',
dataViewsDocLink: 'docs',
});

View file

@ -6,16 +6,16 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXEditorsService } from '../editors';
import type { ServiceFactory } from '../../types';
import type { SharedUxEditorsService } from '../editors';
/**
* A factory function for creating a simple stubbed implementation of `SharedUXEditorsService`.
* A factory function for creating a simple stubbed implementation of `SharedUxEditorsService`.
*/
export type EditorsServiceFactory = PluginServiceFactory<SharedUXEditorsService>;
export type EditorsServiceFactory = ServiceFactory<SharedUxEditorsService>;
/**
* A factory function for creating a simple stubbed implementation of `SharedUXEditorsService`.
* A factory function for creating a simple stubbed implementation of `SharedUxEditorsService`.
*/
export const editorsServiceFactory: EditorsServiceFactory = () => ({
openDataViewEditor: () => () => {},

View file

@ -6,13 +6,13 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXHttpService } from '../http';
import { ServiceFactory } from '../../types';
import { SharedUxHttpService } from '../http';
/**
* A factory function for creating a simple stubbed implementation of `SharedUXHttpService`.
*/
export type HttpServiceFactory = PluginServiceFactory<SharedUXHttpService>;
export type HttpServiceFactory = ServiceFactory<SharedUxHttpService>;
/**
* A factory function for creating a simple stubbed implementation of `SharedUXHttpService`.

View file

@ -6,23 +6,35 @@
* Side Public License, v 1.
*/
import type { SharedUXServices } from '../.';
import { PluginServiceFactory } from '../types';
import type { SharedUxServices, ServiceFactory } from '../../types';
import { applicationServiceFactory } from './application';
import { docLinksServiceFactory } from './doc_links';
import { editorsServiceFactory } from './editors';
import { httpServiceFactory } from './http';
import { platformServiceFactory } from './platform';
import { userPermissionsServiceFactory } from './permissions';
import { editorsServiceFactory } from './editors';
import { docLinksServiceFactory } from './doc_links';
import { httpServiceFactory } from './http';
import { applicationServiceFactory } from './application';
/**
* A factory function for creating a simple stubbed implemetation of `SharedUXServices`.
* A factory function for creating simple stubbed implementations of all `SharedUxServices`.
*/
export const servicesFactory: PluginServiceFactory<SharedUXServices> = () => ({
platform: platformServiceFactory(),
permissions: userPermissionsServiceFactory(),
editors: editorsServiceFactory(),
docLinks: docLinksServiceFactory(),
http: httpServiceFactory(),
export const stubServicesFactory: ServiceFactory<SharedUxServices> = () => ({
application: applicationServiceFactory(),
docLinks: docLinksServiceFactory(),
editors: editorsServiceFactory(),
http: httpServiceFactory(),
permissions: userPermissionsServiceFactory(),
platform: platformServiceFactory(),
});
/**
* A collection of stubbed service factories.
*/
export const stubServiceFactories = {
applicationServiceFactory,
docLinksServiceFactory,
editorsServiceFactory,
httpServiceFactory,
platformServiceFactory,
userPermissionsServiceFactory,
};

View file

@ -6,16 +6,16 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXUserPermissionsService } from '../permissions';
import type { ServiceFactory } from '../../types';
import type { SharedUxUserPermissionsService } from '../permissions';
/**
* A factory function for creating a simple stubbed implementation of `SharedUXUserPermissionsService`.
* A factory function for creating a simple stubbed implementation of `SharedUxUserPermissionsService`.
*/
export type UserPermissionsServiceFactory = PluginServiceFactory<SharedUXUserPermissionsService>;
export type UserPermissionsServiceFactory = ServiceFactory<SharedUxUserPermissionsService>;
/**
* A factory function for creating a simple stubbed implementation of `SharedUXUserPermissionsService`.
* A factory function for creating a simple stubbed implementation of `SharedUxUserPermissionsService`.
*/
export const userPermissionsServiceFactory: UserPermissionsServiceFactory = () => ({
canCreateNewDataView: true,

View file

@ -6,16 +6,16 @@
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '../types';
import { SharedUXPlatformService } from '../platform';
import type { ServiceFactory } from '../../types';
import type { SharedUxPlatformService } from '../platform';
/**
* A factory function for creating a simple stubbed implementation of `SharedUXPlatformService`.
* A factory function for creating a simple stubbed implementation of `SharedUxPlatformService`.
*/
export type PlatformServiceFactory = PluginServiceFactory<SharedUXPlatformService>;
export type PlatformServiceFactory = ServiceFactory<SharedUxPlatformService>;
/**
* A factory function for creating a simple stubbed implementation of `SharedUXPlatformService`.
* A factory function for creating a simple stubbed implementation of `SharedUxPlatformService`.
*/
export const platformServiceFactory: PlatformServiceFactory = () => ({
setIsFullscreen: (_isFullscreen) => {},

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { FC } from 'react';
import {
SharedUxApplicationService,
SharedUxDocLinksService,
SharedUxEditorsService,
SharedUxHttpService,
SharedUxPlatformService,
SharedUxUserPermissionsService,
} from './services';
/**
* A collection of services utilized by SharedUX. This serves as a thin
* abstraction layer between services provided by Kibana and other plugins
* while allowing this plugin to be developed independently of those contracts.
*
* It also allows us to "swap out" differenct implementations of these services
* for different environments, (e.g. Jest, Storybook, etc.)
*/
export interface SharedUxServices {
application: SharedUxApplicationService;
docLinks: SharedUxDocLinksService;
editors: SharedUxEditorsService;
http: SharedUxHttpService;
permissions: SharedUxUserPermissionsService;
platform: SharedUxPlatformService;
}
/**
* A type representing a component that provides the `SharedUxServices` through a
* React Context.
*/
export type SharedUxServicesContext = FC<{}>;
/**
* A factory function for creating one or more services.
*
* The `S` generic determines the shape of the API being produced.
* The `Parameters` generic determines what parameters are expected to
* create the service.
*/
export type ServiceFactory<S, Parameters = void> = (params: Parameters) => S;

View file

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"rootDir": "src",
"stripInternal": false,
"types": [
"jest",
"node"
]
},
"include": [
"src/**/*"
]
}

View file

@ -0,0 +1,130 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "kbn-shared-ux-storybook"
PKG_REQUIRE_NAME = "@kbn/shared-ux-storybook"
SOURCE_FILES = glob(
[
"src/**/*.ts",
"src/**/*.tsx",
],
exclude = [
"**/*.test.*",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"//packages/kbn-shared-ux-services",
"//packages/kbn-storybook",
"@npm//@storybook/react",
"@npm//@storybook/addon-actions",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"//packages/kbn-shared-ux-services:npm_module_types",
"//packages/kbn-storybook:npm_module_types",
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@storybook/react",
"@npm//@storybook/addon-actions",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,34 @@
---
id: kibSharedUXStorybook
slug: /kibana-dev-docs/shared-ux/packages/kbn-shared-ux-storybook
title: Shared UX Storybook
summary: The `@kbn/shared-ux-storybook` package provides Storybook assets for Shared UX and other teams.
date: 2022-03-11
tags: ['kibana', 'dev', 'sharedUX']
---
## About Shared UX Storybook
This package provides the Storybook implementation of `@kbn/shared-ux-services` as well as the configuration for the Shared UX Storybook site.
- `/src/services` The `@kbn/shared-ux-services` implementation.
- `src/config` The Storybook site configuration.
## Storybook site
Run `yarn storybook shared_ux` from `/kibana` to view the site. It pulls in `*.stories.tsx` from all Shared UX packages and plugins and combines them into a single configuration.
## Decorator
If you're writing stories for your own components that compose Shared UX components, you can use a pre-configured [Storybook Decorator](https://storybook.js.org/docs/react/writing-stories/decorators) in your Storybook configuration:
```ts
// preview.ts
import { addDecorator } from '@storybook/react';
import { servicesDecorator } from '@kbn/shared-ux-storybook';
addDecorator(servicesDecorator);
```
This will not only expose parameters, but also wrap your story in a pre-wired `SharedUxServicesProvider`.

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-shared-ux-storybook'],
};

Some files were not shown because too many files have changed in this diff Show more