[8.x] [Embeddable Rebuild] [Controls] Clean up services + TODOs (#193180) (#193429)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Embeddable Rebuild] [Controls] Clean up services + TODOs
(#193180)](https://github.com/elastic/kibana/pull/193180)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Hannah
Mudge","email":"Heenawter@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-09-19T14:02:49Z","message":"[Embeddable
Rebuild] [Controls] Clean up services + TODOs (#193180)\n\nPart of
https://github.com/elastic/kibana/issues/192005\r\nCloses
https://github.com/elastic/kibana/issues/167438\r\n\r\n##
Summary\r\n\r\n\r\n## Summary\r\n\r\nThis PR represents the second major
cleanup task for the control group\r\nembeddable refactor. The tasks
included in this PR can be loosely\r\nsummarized as follows:\r\n1. It
removes the old, cumbersome services implementation and replaces\r\nit
with a much simpler system (which is the same one used in the
`links`\r\n+ `presentation_panel` plugins).\r\n- This it the main reason
for the decrease in lines - the old system\r\nrequired a **huge** amount
of boilerplate code, which is no longer\r\nnecessary with the new method
for storing services.\r\n2. It addresses and/or removes any remaining
TODO comments in the\r\n`controls` plugin\r\n- This includes renaming
`ControlStyle` and `DEFAULT_CONTROL_STYLE` to\r\n`ControlLabelPosition`
and `DEFAULT_CONTROL_LABEL_POSITION`\r\nrespectively, which represents a
bulk of the changes.\r\n3. It moves all compatibility checks for all
control actions to be async\r\nimported.\r\n4. It removes the ability to
register controls from the `controls`\r\nplugin setup
contract.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n### For
maintainers\r\n\r\n- [ ] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"db5557429979b9a0f3420a50a06c7fd69cbdf5b2","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Presentation","release_note:skip","impact:high","v9.0.0","backport:prev-minor"],"title":"[Embeddable
Rebuild] [Controls] Clean up services +
TODOs","number":193180,"url":"https://github.com/elastic/kibana/pull/193180","mergeCommit":{"message":"[Embeddable
Rebuild] [Controls] Clean up services + TODOs (#193180)\n\nPart of
https://github.com/elastic/kibana/issues/192005\r\nCloses
https://github.com/elastic/kibana/issues/167438\r\n\r\n##
Summary\r\n\r\n\r\n## Summary\r\n\r\nThis PR represents the second major
cleanup task for the control group\r\nembeddable refactor. The tasks
included in this PR can be loosely\r\nsummarized as follows:\r\n1. It
removes the old, cumbersome services implementation and replaces\r\nit
with a much simpler system (which is the same one used in the
`links`\r\n+ `presentation_panel` plugins).\r\n- This it the main reason
for the decrease in lines - the old system\r\nrequired a **huge** amount
of boilerplate code, which is no longer\r\nnecessary with the new method
for storing services.\r\n2. It addresses and/or removes any remaining
TODO comments in the\r\n`controls` plugin\r\n- This includes renaming
`ControlStyle` and `DEFAULT_CONTROL_STYLE` to\r\n`ControlLabelPosition`
and `DEFAULT_CONTROL_LABEL_POSITION`\r\nrespectively, which represents a
bulk of the changes.\r\n3. It moves all compatibility checks for all
control actions to be async\r\nimported.\r\n4. It removes the ability to
register controls from the `controls`\r\nplugin setup
contract.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n### For
maintainers\r\n\r\n- [ ] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"db5557429979b9a0f3420a50a06c7fd69cbdf5b2"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193180","number":193180,"mergeCommit":{"message":"[Embeddable
Rebuild] [Controls] Clean up services + TODOs (#193180)\n\nPart of
https://github.com/elastic/kibana/issues/192005\r\nCloses
https://github.com/elastic/kibana/issues/167438\r\n\r\n##
Summary\r\n\r\n\r\n## Summary\r\n\r\nThis PR represents the second major
cleanup task for the control group\r\nembeddable refactor. The tasks
included in this PR can be loosely\r\nsummarized as follows:\r\n1. It
removes the old, cumbersome services implementation and replaces\r\nit
with a much simpler system (which is the same one used in the
`links`\r\n+ `presentation_panel` plugins).\r\n- This it the main reason
for the decrease in lines - the old system\r\nrequired a **huge** amount
of boilerplate code, which is no longer\r\nnecessary with the new method
for storing services.\r\n2. It addresses and/or removes any remaining
TODO comments in the\r\n`controls` plugin\r\n- This includes renaming
`ControlStyle` and `DEFAULT_CONTROL_STYLE` to\r\n`ControlLabelPosition`
and `DEFAULT_CONTROL_LABEL_POSITION`\r\nrespectively, which represents a
bulk of the changes.\r\n3. It moves all compatibility checks for all
control actions to be async\r\nimported.\r\n4. It removes the ability to
register controls from the `controls`\r\nplugin setup
contract.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n### For
maintainers\r\n\r\n- [ ] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"db5557429979b9a0f3420a50a06c7fd69cbdf5b2"}}]}]
BACKPORT-->

Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-09-20 02:26:08 +10:00 committed by GitHub
parent b0853caa12
commit b2e6263d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 399 additions and 1408 deletions

View file

@ -7,11 +7,11 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { ControlStyle, ControlWidth } from './types'; import { ControlLabelPosition, ControlWidth } from './types';
export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'medium'; export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'medium';
export const DEFAULT_CONTROL_GROW: boolean = true; export const DEFAULT_CONTROL_GROW: boolean = true;
export const DEFAULT_CONTROL_STYLE: ControlStyle = 'oneLine'; export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition = 'oneLine';
export const TIME_SLIDER_CONTROL = 'timeSlider'; export const TIME_SLIDER_CONTROL = 'timeSlider';
export const RANGE_SLIDER_CONTROL = 'rangeSliderControl'; export const RANGE_SLIDER_CONTROL = 'rangeSliderControl';

View file

@ -8,7 +8,7 @@
*/ */
import { DataViewField } from '@kbn/data-views-plugin/common'; import { DataViewField } from '@kbn/data-views-plugin/common';
import { ControlStyle, DefaultControlState, ParentIgnoreSettings } from '../types'; import { ControlLabelPosition, DefaultControlState, ParentIgnoreSettings } from '../types';
export const CONTROL_GROUP_TYPE = 'control_group'; export const CONTROL_GROUP_TYPE = 'control_group';
@ -31,7 +31,7 @@ export interface ControlGroupEditorConfig {
export interface ControlGroupRuntimeState<State extends DefaultControlState = DefaultControlState> { export interface ControlGroupRuntimeState<State extends DefaultControlState = DefaultControlState> {
chainingSystem: ControlGroupChainingSystem; chainingSystem: ControlGroupChainingSystem;
labelPosition: ControlStyle; // TODO: Rename this type to ControlLabelPosition labelPosition: ControlLabelPosition;
autoApplySelections: boolean; autoApplySelections: boolean;
ignoreParentSettings?: ParentIgnoreSettings; ignoreParentSettings?: ParentIgnoreSettings;
@ -50,7 +50,7 @@ export interface ControlGroupSerializedState
ignoreParentSettingsJSON: string; ignoreParentSettingsJSON: string;
// In runtime state, we refer to this property as `labelPosition`; // In runtime state, we refer to this property as `labelPosition`;
// to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state // to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state
controlStyle: ControlStyle; controlStyle: ControlLabelPosition;
// In runtime state, we refer to the inverse of this property as `autoApplySelections` // In runtime state, we refer to the inverse of this property as `autoApplySelections`
// to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state // to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state
showApplySelections?: boolean; showApplySelections?: boolean;

View file

@ -8,7 +8,7 @@
*/ */
export type { export type {
ControlStyle, ControlLabelPosition,
ControlWidth, ControlWidth,
DefaultControlState, DefaultControlState,
DefaultDataControlState, DefaultDataControlState,
@ -18,7 +18,7 @@ export type {
export { export {
DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_STYLE, DEFAULT_CONTROL_LABEL_POSITION,
DEFAULT_CONTROL_WIDTH, DEFAULT_CONTROL_WIDTH,
OPTIONS_LIST_CONTROL, OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL, RANGE_SLIDER_CONTROL,

View file

@ -15,8 +15,6 @@ import { OptionsListSortingType } from './suggestions_sorting';
import { DefaultDataControlState } from '../types'; import { DefaultDataControlState } from '../types';
import { OptionsListSearchTechnique } from './suggestions_searching'; import { OptionsListSearchTechnique } from './suggestions_searching';
export const OPTIONS_LIST_CONTROL = 'optionsListControl'; // TODO: Replace with OPTIONS_LIST_CONTROL_TYPE
/** /**
* ---------------------------------------------------------------- * ----------------------------------------------------------------
* Options list state types * Options list state types

View file

@ -8,7 +8,7 @@
*/ */
export type ControlWidth = 'small' | 'medium' | 'large'; export type ControlWidth = 'small' | 'medium' | 'large';
export type ControlStyle = 'twoLine' | 'oneLine'; export type ControlLabelPosition = 'twoLine' | 'oneLine';
export type TimeSlice = [number, number]; export type TimeSlice = [number, number];

View file

@ -8,8 +8,5 @@
*/ */
// Start the services with stubs // Start the services with stubs
import { pluginServices } from './public/services'; import { setStubKibanaServices } from './public/services/mocks';
import { registry } from './public/services/plugin_services.stub'; setStubKibanaServices();
registry.start({});
pluginServices.setRegistry(registry);

View file

@ -9,8 +9,6 @@
"browser": true, "browser": true,
"requiredPlugins": [ "requiredPlugins": [
"presentationUtil", "presentationUtil",
"kibanaReact",
"expressions",
"embeddable", "embeddable",
"dataViews", "dataViews",
"data", "data",
@ -18,6 +16,6 @@
"uiActions" "uiActions"
], ],
"extraPublicDirs": ["common"], "extraPublicDirs": ["common"],
"requiredBundles": ["kibanaUtils"] "requiredBundles": []
} }
} }

View file

@ -11,42 +11,10 @@ import React, { SyntheticEvent } from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { import type { EmbeddableApiContext, HasUniqueId } from '@kbn/presentation-publishing';
apiIsPresentationContainer, import { IncompatibleActionError, type Action } from '@kbn/ui-actions-plugin/public';
type PresentationContainer,
} from '@kbn/presentation-containers';
import {
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
type EmbeddableApiContext,
type HasParentApi,
type HasType,
type HasUniqueId,
} from '@kbn/presentation-publishing';
import { type Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { ACTION_CLEAR_CONTROL } from '.'; import { ACTION_CLEAR_CONTROL } from '.';
import { CONTROL_GROUP_TYPE } from '..';
import { isClearableControl, type CanClearSelections } from '../types';
export type ClearControlActionApi = HasType &
HasUniqueId &
CanClearSelections &
HasParentApi<PresentationContainer & HasType>;
const isApiCompatible = (api: unknown | null): api is ClearControlActionApi =>
Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
isClearableControl(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
export class ClearControlAction implements Action<EmbeddableApiContext> { export class ClearControlAction implements Action<EmbeddableApiContext> {
public readonly type = ACTION_CLEAR_CONTROL; public readonly type = ACTION_CLEAR_CONTROL;
@ -56,12 +24,10 @@ export class ClearControlAction implements Action<EmbeddableApiContext> {
constructor() {} constructor() {}
public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => {
if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError();
return ( return (
<EuiToolTip content={this.getDisplayName(context)}> <EuiToolTip content={this.getDisplayName(context)}>
<EuiButtonIcon <EuiButtonIcon
data-test-subj={`control-action-${context.embeddable.uuid}-erase`} data-test-subj={`control-action-${(context.embeddable as HasUniqueId).uuid}-erase`}
aria-label={this.getDisplayName(context)} aria-label={this.getDisplayName(context)}
iconType={this.getIconType(context)} iconType={this.getIconType(context)}
onClick={(event: SyntheticEvent<HTMLButtonElement>) => { onClick={(event: SyntheticEvent<HTMLButtonElement>) => {
@ -75,23 +41,24 @@ export class ClearControlAction implements Action<EmbeddableApiContext> {
}; };
public getDisplayName({ embeddable }: EmbeddableApiContext) { public getDisplayName({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return i18n.translate('controls.controlGroup.floatingActions.clearTitle', { return i18n.translate('controls.controlGroup.floatingActions.clearTitle', {
defaultMessage: 'Clear', defaultMessage: 'Clear',
}); });
} }
public getIconType({ embeddable }: EmbeddableApiContext) { public getIconType({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return 'eraser'; return 'eraser';
} }
public async isCompatible({ embeddable }: EmbeddableApiContext) { public async isCompatible({ embeddable }: EmbeddableApiContext) {
return isApiCompatible(embeddable); const { isCompatible } = await import('./clear_control_action_compatibility_check');
return isCompatible(embeddable);
} }
public async execute({ embeddable }: EmbeddableApiContext) { public async execute({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); const { compatibilityCheck } = await import('./clear_control_action_compatibility_check');
if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError();
embeddable.clearSelections(); embeddable.clearSelections();
} }
} }

View file

@ -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
* 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PresentationContainer, apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
HasParentApi,
HasType,
HasUniqueId,
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
} from '@kbn/presentation-publishing';
import { CONTROL_GROUP_TYPE } from '../../common';
import { isClearableControl, type CanClearSelections } from '../types';
type ClearControlActionApi = HasType &
HasUniqueId &
CanClearSelections &
HasParentApi<PresentationContainer & HasType>;
export const compatibilityCheck = (api: unknown | null): api is ClearControlActionApi =>
Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
isClearableControl(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
export function isCompatible(api: unknown) {
return compatibilityCheck(api);
}

View file

@ -9,22 +9,15 @@
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { ViewMode } from '@kbn/presentation-publishing'; import { ViewMode } from '@kbn/presentation-publishing';
import { getOptionsListControlFactory } from '../react_controls/controls/data_controls/options_list_control/get_options_list_control_factory'; import { getOptionsListControlFactory } from '../react_controls/controls/data_controls/options_list_control/get_options_list_control_factory';
import { OptionsListControlApi } from '../react_controls/controls/data_controls/options_list_control/types'; import { OptionsListControlApi } from '../react_controls/controls/data_controls/options_list_control/types';
import { import {
getMockedBuildApi, getMockedBuildApi,
getMockedControlGroupApi, getMockedControlGroupApi,
} from '../react_controls/controls/mocks/control_mocks'; } from '../react_controls/controls/mocks/control_mocks';
import { pluginServices } from '../services';
import { DeleteControlAction } from './delete_control_action'; import { DeleteControlAction } from './delete_control_action';
import { coreServices } from '../services/kibana_services';
const mockDataViews = dataViewPluginMocks.createStartContract();
const mockCore = coreMock.createStart();
const dashboardApi = { const dashboardApi = {
viewMode: new BehaviorSubject<ViewMode>('view'), viewMode: new BehaviorSubject<ViewMode>('view'),
@ -38,11 +31,7 @@ const controlGroupApi = getMockedControlGroupApi(dashboardApi, {
let controlApi: OptionsListControlApi; let controlApi: OptionsListControlApi;
beforeAll(async () => { beforeAll(async () => {
const controlFactory = getOptionsListControlFactory({ const controlFactory = getOptionsListControlFactory();
core: mockCore,
data: dataPluginMock.createStartContract(),
dataViews: mockDataViews,
});
const uuid = 'testControl'; const uuid = 'testControl';
const control = await controlFactory.buildControl( const control = await controlFactory.buildControl(
@ -72,7 +61,7 @@ test('Execute throws an error when called with an embeddable not in a parent', a
describe('Execute should open a confirm modal', () => { describe('Execute should open a confirm modal', () => {
test('Canceling modal will keep control', async () => { test('Canceling modal will keep control', async () => {
const spyOn = jest.fn().mockResolvedValue(false); const spyOn = jest.fn().mockResolvedValue(false);
pluginServices.getServices().overlays.openConfirm = spyOn; coreServices.overlays.openConfirm = spyOn;
const deleteControlAction = new DeleteControlAction(); const deleteControlAction = new DeleteControlAction();
await deleteControlAction.execute({ embeddable: controlApi }); await deleteControlAction.execute({ embeddable: controlApi });
@ -83,7 +72,7 @@ describe('Execute should open a confirm modal', () => {
test('Confirming modal will delete control', async () => { test('Confirming modal will delete control', async () => {
const spyOn = jest.fn().mockResolvedValue(true); const spyOn = jest.fn().mockResolvedValue(true);
pluginServices.getServices().overlays.openConfirm = spyOn; coreServices.overlays.openConfirm = spyOn;
const deleteControlAction = new DeleteControlAction(); const deleteControlAction = new DeleteControlAction();
await deleteControlAction.execute({ embeddable: controlApi }); await deleteControlAction.execute({ embeddable: controlApi });

View file

@ -10,65 +10,25 @@
import React from 'react'; import React from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { import type { HasUniqueId, EmbeddableApiContext } from '@kbn/presentation-publishing';
apiIsPresentationContainer,
type PresentationContainer,
} from '@kbn/presentation-containers';
import {
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
getInheritedViewMode,
type EmbeddableApiContext,
type HasParentApi,
type HasType,
type HasUniqueId,
type PublishesViewMode,
} from '@kbn/presentation-publishing';
import { IncompatibleActionError, type Action } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError, type Action } from '@kbn/ui-actions-plugin/public';
import { ACTION_DELETE_CONTROL } from '.'; import { ACTION_DELETE_CONTROL } from '.';
import { CONTROL_GROUP_TYPE } from '..'; import { coreServices } from '../services/kibana_services';
import { pluginServices } from '../services';
export type DeleteControlActionApi = HasType &
HasUniqueId &
HasParentApi<PresentationContainer & PublishesViewMode & HasType>;
const isApiCompatible = (api: unknown | null): api is DeleteControlActionApi =>
Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
export class DeleteControlAction implements Action<EmbeddableApiContext> { export class DeleteControlAction implements Action<EmbeddableApiContext> {
public readonly type = ACTION_DELETE_CONTROL; public readonly type = ACTION_DELETE_CONTROL;
public readonly id = ACTION_DELETE_CONTROL; public readonly id = ACTION_DELETE_CONTROL;
public order = 100; // should always be last public order = 100; // should always be last
private openConfirm; constructor() {}
constructor() {
({
overlays: { openConfirm: this.openConfirm },
} = pluginServices.getServices());
}
public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => {
if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError();
return ( return (
<EuiToolTip content={this.getDisplayName(context)}> <EuiToolTip content={this.getDisplayName(context)}>
<EuiButtonIcon <EuiButtonIcon
data-test-subj={`control-action-${context.embeddable.uuid}-delete`} data-test-subj={`control-action-${(context.embeddable as HasUniqueId).uuid}-delete`}
aria-label={this.getDisplayName(context)} aria-label={this.getDisplayName(context)}
iconType={this.getIconType(context)} iconType={this.getIconType(context)}
onClick={() => this.execute(context)} onClick={() => this.execute(context)}
@ -79,27 +39,26 @@ export class DeleteControlAction implements Action<EmbeddableApiContext> {
}; };
public getDisplayName({ embeddable }: EmbeddableApiContext) { public getDisplayName({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return i18n.translate('controls.controlGroup.floatingActions.removeTitle', { return i18n.translate('controls.controlGroup.floatingActions.removeTitle', {
defaultMessage: 'Delete', defaultMessage: 'Delete',
}); });
} }
public getIconType({ embeddable }: EmbeddableApiContext) { public getIconType({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return 'trash'; return 'trash';
} }
public async isCompatible({ embeddable }: EmbeddableApiContext) { public async isCompatible({ embeddable }: EmbeddableApiContext) {
return ( const { isCompatible } = await import('./delete_control_action_compatibility_check');
isApiCompatible(embeddable) && getInheritedViewMode(embeddable.parentApi) === ViewMode.EDIT return isCompatible(embeddable);
);
} }
public async execute({ embeddable }: EmbeddableApiContext) { public async execute({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); const { compatibilityCheck } = await import('./delete_control_action_compatibility_check');
if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError();
this.openConfirm( coreServices.overlays
.openConfirm(
i18n.translate('controls.controlGroup.management.delete.sub', { i18n.translate('controls.controlGroup.management.delete.sub', {
defaultMessage: 'Controls are not recoverable once removed.', defaultMessage: 'Controls are not recoverable once removed.',
}), }),
@ -115,7 +74,8 @@ export class DeleteControlAction implements Action<EmbeddableApiContext> {
}), }),
buttonColor: 'danger', buttonColor: 'danger',
} }
).then((confirmed) => { )
.then((confirmed) => {
if (confirmed) { if (confirmed) {
embeddable.parentApi.removePanel(embeddable.uuid); embeddable.parentApi.removePanel(embeddable.uuid);
} }

View file

@ -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
* 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { PresentationContainer, apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
HasParentApi,
HasType,
HasUniqueId,
PublishesViewMode,
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
getInheritedViewMode,
} from '@kbn/presentation-publishing';
import { CONTROL_GROUP_TYPE } from '../../common';
type DeleteControlActionApi = HasType &
HasUniqueId &
HasParentApi<PresentationContainer & PublishesViewMode & HasType>;
export const compatibilityCheck = (api: unknown | null): api is DeleteControlActionApi =>
Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
export function isCompatible(api: unknown) {
return compatibilityCheck(api) && getInheritedViewMode(api.parentApi) === ViewMode.EDIT;
}

View file

@ -9,9 +9,6 @@
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import dateMath from '@kbn/datemath'; import dateMath from '@kbn/datemath';
import type { TimeRange } from '@kbn/es-query'; import type { TimeRange } from '@kbn/es-query';
import type { ViewMode } from '@kbn/presentation-publishing'; import type { ViewMode } from '@kbn/presentation-publishing';
@ -23,12 +20,10 @@ import {
getMockedControlGroupApi, getMockedControlGroupApi,
} from '../react_controls/controls/mocks/control_mocks'; } from '../react_controls/controls/mocks/control_mocks';
import { getTimesliderControlFactory } from '../react_controls/controls/timeslider_control/get_timeslider_control_factory'; import { getTimesliderControlFactory } from '../react_controls/controls/timeslider_control/get_timeslider_control_factory';
import { dataService } from '../services/kibana_services';
import { EditControlAction } from './edit_control_action'; import { EditControlAction } from './edit_control_action';
const mockDataViews = dataViewPluginMocks.createStartContract(); dataService.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => {
const mockCore = coreMock.createStart();
const dataStartServiceMock = dataPluginMock.createStartContract();
dataStartServiceMock.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => {
const now = new Date(); const now = new Date();
return { return {
min: dateMath.parse(timeRange.from, { forceNow: now }), min: dateMath.parse(timeRange.from, { forceNow: now }),
@ -48,11 +43,7 @@ const controlGroupApi = getMockedControlGroupApi(dashboardApi, {
let optionsListApi: OptionsListControlApi; let optionsListApi: OptionsListControlApi;
beforeAll(async () => { beforeAll(async () => {
const controlFactory = getOptionsListControlFactory({ const controlFactory = getOptionsListControlFactory();
core: mockCore,
data: dataStartServiceMock,
dataViews: mockDataViews,
});
const optionsListUuid = 'optionsListControl'; const optionsListUuid = 'optionsListControl';
const optionsListControl = await controlFactory.buildControl( const optionsListControl = await controlFactory.buildControl(
@ -73,10 +64,7 @@ beforeAll(async () => {
describe('Incompatible embeddables', () => { describe('Incompatible embeddables', () => {
test('Action is incompatible with embeddables that are not editable', async () => { test('Action is incompatible with embeddables that are not editable', async () => {
const timeSliderFactory = getTimesliderControlFactory({ const timeSliderFactory = getTimesliderControlFactory();
core: mockCore,
data: dataStartServiceMock,
});
const timeSliderUuid = 'timeSliderControl'; const timeSliderUuid = 'timeSliderControl';
const timeSliderControl = await timeSliderFactory.buildControl( const timeSliderControl = await timeSliderFactory.buildControl(
{}, {},

View file

@ -21,7 +21,6 @@ export { ACTION_CLEAR_CONTROL, ACTION_DELETE_CONTROL, ACTION_EDIT_CONTROL } from
export type { export type {
DataControlApi, DataControlApi,
DataControlFactory, DataControlFactory,
DataControlServices,
} from './react_controls/controls/data_controls/types'; } from './react_controls/controls/data_controls/types';
export { export {

View file

@ -10,63 +10,36 @@
import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { PANEL_HOVER_TRIGGER } from '@kbn/embeddable-plugin/public'; import { PANEL_HOVER_TRIGGER } from '@kbn/embeddable-plugin/public';
import { ClearControlAction } from './actions/clear_control_action';
import { DeleteControlAction } from './actions/delete_control_action';
import { EditControlAction } from './actions/edit_control_action';
import { registerControlGroupEmbeddable } from './react_controls/control_group/register_control_group_embeddable'; import { registerControlGroupEmbeddable } from './react_controls/control_group/register_control_group_embeddable';
import { registerOptionsListControl } from './react_controls/controls/data_controls/options_list_control/register_options_list_control'; import { registerOptionsListControl } from './react_controls/controls/data_controls/options_list_control/register_options_list_control';
import { registerRangeSliderControl } from './react_controls/controls/data_controls/range_slider/register_range_slider_control'; import { registerRangeSliderControl } from './react_controls/controls/data_controls/range_slider/register_range_slider_control';
import { registerTimeSliderControl } from './react_controls/controls/timeslider_control/register_timeslider_control'; import { registerTimeSliderControl } from './react_controls/controls/timeslider_control/register_timeslider_control';
import { controlsService } from './services/controls/controls_service'; import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services';
import type { import type { ControlsPluginSetupDeps, ControlsPluginStartDeps } from './types';
ControlsPluginSetup,
ControlsPluginSetupDeps,
ControlsPluginStart,
ControlsPluginStartDeps,
} from './types';
export class ControlsPlugin export class ControlsPlugin
implements implements Plugin<void, void, ControlsPluginSetupDeps, ControlsPluginStartDeps>
Plugin<
ControlsPluginSetup,
ControlsPluginStart,
ControlsPluginSetupDeps,
ControlsPluginStartDeps
>
{ {
private async startControlsKibanaServices(
coreStart: CoreStart,
startPlugins: ControlsPluginStartDeps
) {
const { registry, pluginServices } = await import('./services/plugin_services');
pluginServices.setRegistry(registry.start({ coreStart, startPlugins }));
}
public setup( public setup(
_coreSetup: CoreSetup<ControlsPluginStartDeps, ControlsPluginStart>, _coreSetup: CoreSetup<ControlsPluginStartDeps>,
_setupPlugins: ControlsPluginSetupDeps _setupPlugins: ControlsPluginSetupDeps
): ControlsPluginSetup { ) {
const { registerControlFactory } = controlsService;
const { embeddable } = _setupPlugins; const { embeddable } = _setupPlugins;
registerControlGroupEmbeddable(_coreSetup, embeddable); registerControlGroupEmbeddable(embeddable);
registerOptionsListControl(_coreSetup); registerOptionsListControl();
registerRangeSliderControl(_coreSetup); registerRangeSliderControl();
registerTimeSliderControl(_coreSetup); registerTimeSliderControl();
return {
registerControlFactory,
};
} }
public start(coreStart: CoreStart, startPlugins: ControlsPluginStartDeps): ControlsPluginStart { public start(coreStart: CoreStart, startPlugins: ControlsPluginStartDeps) {
this.startControlsKibanaServices(coreStart, startPlugins).then(async () => {
const { uiActions } = startPlugins; const { uiActions } = startPlugins;
setKibanaServices(coreStart, startPlugins);
const [{ DeleteControlAction }, { EditControlAction }, { ClearControlAction }] = untilPluginStartServicesReady().then(() => {
await Promise.all([
import('./actions/delete_control_action'),
import('./actions/edit_control_action'),
import('./actions/clear_control_action'),
]);
const deleteControlAction = new DeleteControlAction(); const deleteControlAction = new DeleteControlAction();
uiActions.registerAction(deleteControlAction); uiActions.registerAction(deleteControlAction);
uiActions.attachAction(PANEL_HOVER_TRIGGER, deleteControlAction.id); uiActions.attachAction(PANEL_HOVER_TRIGGER, deleteControlAction.id);
@ -79,12 +52,6 @@ export class ControlsPlugin
uiActions.registerAction(clearControlAction); uiActions.registerAction(clearControlAction);
uiActions.attachAction(PANEL_HOVER_TRIGGER, clearControlAction.id); uiActions.attachAction(PANEL_HOVER_TRIGGER, clearControlAction.id);
}); });
const { getControlFactory, getAllControlTypes } = controlsService;
return {
getControlFactory,
getAllControlTypes,
};
} }
public stop() {} public stop() {}

View file

@ -13,8 +13,6 @@ import { EuiButtonEmpty, EuiPopover } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { Markdown } from '@kbn/shared-ux-markdown'; import { Markdown } from '@kbn/shared-ux-markdown';
/** TODO: This file is duplicated from the controls plugin to avoid exporting it */
interface ControlErrorProps { interface ControlErrorProps {
error: Error | string; error: Error | string;
} }

View file

@ -30,7 +30,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiToolTip } from '
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import type { ControlStyle } from '../../../../common'; import type { ControlLabelPosition } from '../../../../common';
import type { DefaultControlApi } from '../../controls/types'; import type { DefaultControlApi } from '../../controls/types';
import { ControlGroupStrings } from '../control_group_strings'; import { ControlGroupStrings } from '../control_group_strings';
import { ControlsInOrder } from '../init_controls_manager'; import { ControlsInOrder } from '../init_controls_manager';
@ -49,7 +49,7 @@ interface Props {
setControlApi: (uuid: string, controlApi: DefaultControlApi) => void; setControlApi: (uuid: string, controlApi: DefaultControlApi) => void;
}; };
hasUnappliedSelections: boolean; hasUnappliedSelections: boolean;
labelPosition: ControlStyle; labelPosition: ControlLabelPosition;
} }
export function ControlGroup({ export function ControlGroup({

View file

@ -15,8 +15,8 @@ import { render } from '@testing-library/react';
import { ControlGroupApi } from '../../..'; import { ControlGroupApi } from '../../..';
import { import {
ControlGroupChainingSystem, ControlGroupChainingSystem,
ControlStyle, ControlLabelPosition,
DEFAULT_CONTROL_STYLE, DEFAULT_CONTROL_LABEL_POSITION,
ParentIgnoreSettings, ParentIgnoreSettings,
} from '../../../../common'; } from '../../../../common';
import { DefaultControlApi } from '../../controls/types'; import { DefaultControlApi } from '../../controls/types';
@ -33,7 +33,7 @@ describe('render', () => {
onDeleteAll: () => {}, onDeleteAll: () => {},
stateManager: { stateManager: {
chainingSystem: new BehaviorSubject<ControlGroupChainingSystem>('HIERARCHICAL'), chainingSystem: new BehaviorSubject<ControlGroupChainingSystem>('HIERARCHICAL'),
labelPosition: new BehaviorSubject<ControlStyle>(DEFAULT_CONTROL_STYLE), labelPosition: new BehaviorSubject<ControlLabelPosition>(DEFAULT_CONTROL_LABEL_POSITION),
autoApplySelections: new BehaviorSubject<boolean>(true), autoApplySelections: new BehaviorSubject<boolean>(true),
ignoreParentSettings: new BehaviorSubject<ParentIgnoreSettings | undefined>(undefined), ignoreParentSettings: new BehaviorSubject<ParentIgnoreSettings | undefined>(undefined),
}, },

View file

@ -27,7 +27,7 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import type { ControlStyle, ParentIgnoreSettings } from '../../../../common'; import type { ControlLabelPosition, ParentIgnoreSettings } from '../../../../common';
import { CONTROL_LAYOUT_OPTIONS } from '../../controls/data_controls/editor_constants'; import { CONTROL_LAYOUT_OPTIONS } from '../../controls/data_controls/editor_constants';
import type { ControlStateManager } from '../../controls/types'; import type { ControlStateManager } from '../../controls/types';
import { ControlGroupStrings } from '../control_group_strings'; import { ControlGroupStrings } from '../control_group_strings';
@ -86,7 +86,7 @@ export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager
idSelected={selectedLabelPosition} idSelected={selectedLabelPosition}
legend={ControlGroupStrings.management.labelPosition.getLabelPositionLegend()} legend={ControlGroupStrings.management.labelPosition.getLabelPositionLegend()}
onChange={(newPosition: string) => { onChange={(newPosition: string) => {
stateManager.labelPosition.next(newPosition as ControlStyle); stateManager.labelPosition.next(newPosition as ControlLabelPosition);
}} }}
/> />
</EuiFormRow> </EuiFormRow>

View file

@ -14,7 +14,7 @@ import { pluginServices as presentationUtilPluginServices } from '@kbn/presentat
import { registry as presentationUtilServicesRegistry } from '@kbn/presentation-util-plugin/public/services/plugin_services.story'; import { registry as presentationUtilServicesRegistry } from '@kbn/presentation-util-plugin/public/services/plugin_services.story';
import { render, waitFor } from '@testing-library/react'; import { render, waitFor } from '@testing-library/react';
import type { ControlStyle, ControlWidth } from '../../../../common'; import type { ControlLabelPosition, ControlWidth } from '../../../../common';
import { ControlPanel } from './control_panel'; import { ControlPanel } from './control_panel';
describe('render', () => { describe('render', () => {
@ -74,7 +74,7 @@ describe('render', () => {
mockApi = { mockApi = {
uuid: 'control1', uuid: 'control1',
parentApi: { parentApi: {
labelPosition: new BehaviorSubject<ControlStyle>('oneLine'), labelPosition: new BehaviorSubject<ControlLabelPosition>('oneLine'),
}, },
}; };
const controlPanel = render(<ControlPanel uuid="control1" Component={Component} />); const controlPanel = render(<ControlPanel uuid="control1" Component={Component} />);
@ -92,7 +92,7 @@ describe('render', () => {
mockApi = { mockApi = {
uuid: 'control1', uuid: 'control1',
parentApi: { parentApi: {
labelPosition: new BehaviorSubject<ControlStyle>('twoLine'), labelPosition: new BehaviorSubject<ControlLabelPosition>('twoLine'),
}, },
}; };
const controlPanel = render(<ControlPanel uuid="control1" Component={Component} />); const controlPanel = render(<ControlPanel uuid="control1" Component={Component} />);

View file

@ -11,9 +11,7 @@ import fastIsEqual from 'fast-deep-equal';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { CoreStart } from '@kbn/core/public';
import { DataView } from '@kbn/data-views-plugin/common'; import { DataView } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { import {
@ -31,11 +29,11 @@ import type {
ControlGroupChainingSystem, ControlGroupChainingSystem,
ControlGroupRuntimeState, ControlGroupRuntimeState,
ControlGroupSerializedState, ControlGroupSerializedState,
ControlLabelPosition,
ControlPanelsState, ControlPanelsState,
ControlStyle,
ParentIgnoreSettings, ParentIgnoreSettings,
} from '../../../common'; } from '../../../common';
import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_STYLE } from '../../../common'; import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_LABEL_POSITION } from '../../../common';
import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor'; import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor';
import { ControlGroup } from './components/control_group'; import { ControlGroup } from './components/control_group';
import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch'; import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch';
@ -45,13 +43,11 @@ import { openEditControlGroupFlyout } from './open_edit_control_group_flyout';
import { initSelectionsManager } from './selections_manager'; import { initSelectionsManager } from './selections_manager';
import type { ControlGroupApi } from './types'; import type { ControlGroupApi } from './types';
import { deserializeControlGroup } from './utils/serialization_utils'; import { deserializeControlGroup } from './utils/serialization_utils';
import { coreServices, dataViewsService } from '../../services/kibana_services';
const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL'; const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL';
export const getControlGroupEmbeddableFactory = (services: { export const getControlGroupEmbeddableFactory = () => {
core: CoreStart;
dataViews: DataViewsPublicPluginStart;
}) => {
const controlGroupEmbeddableFactory: ReactEmbeddableFactory< const controlGroupEmbeddableFactory: ReactEmbeddableFactory<
ControlGroupSerializedState, ControlGroupSerializedState,
ControlGroupRuntimeState, ControlGroupRuntimeState,
@ -75,7 +71,7 @@ export const getControlGroupEmbeddableFactory = (services: {
} = initialRuntimeState; } = initialRuntimeState;
const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections); const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections);
const defaultDataViewId = await services.dataViews.getDefaultId(); const defaultDataViewId = await dataViewsService.getDefaultId();
const lastSavedControlsState$ = new BehaviorSubject<ControlPanelsState>( const lastSavedControlsState$ = new BehaviorSubject<ControlPanelsState>(
lastSavedRuntimeState.initialChildControlState lastSavedRuntimeState.initialChildControlState
); );
@ -94,15 +90,12 @@ export const getControlGroupEmbeddableFactory = (services: {
const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>( const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>(
ignoreParentSettings ignoreParentSettings
); );
const labelPosition$ = new BehaviorSubject<ControlStyle>( // TODO: Rename `ControlStyle` const labelPosition$ = new BehaviorSubject<ControlLabelPosition>(
initialLabelPosition ?? DEFAULT_CONTROL_STYLE // TODO: Rename `DEFAULT_CONTROL_STYLE` initialLabelPosition ?? DEFAULT_CONTROL_LABEL_POSITION
); );
const allowExpensiveQueries$ = new BehaviorSubject<boolean>(true); const allowExpensiveQueries$ = new BehaviorSubject<boolean>(true);
const disabledActionIds$ = new BehaviorSubject<string[] | undefined>(undefined); const disabledActionIds$ = new BehaviorSubject<string[] | undefined>(undefined);
/** TODO: Handle loading; loading should be true if any child is loading */
const dataLoading$ = new BehaviorSubject<boolean | undefined>(false);
const unsavedChanges = initializeControlGroupUnsavedChanges( const unsavedChanges = initializeControlGroupUnsavedChanges(
selectionsManager.applySelections, selectionsManager.applySelections,
controlsManager.api.children$, controlsManager.api.children$,
@ -122,7 +115,10 @@ export const getControlGroupEmbeddableFactory = (services: {
(next: ParentIgnoreSettings | undefined) => ignoreParentSettings$.next(next), (next: ParentIgnoreSettings | undefined) => ignoreParentSettings$.next(next),
fastIsEqual, fastIsEqual,
], ],
labelPosition: [labelPosition$, (next: ControlStyle) => labelPosition$.next(next)], labelPosition: [
labelPosition$,
(next: ControlLabelPosition) => labelPosition$.next(next),
],
}, },
controlsManager.snapshotControlsRuntimeState, controlsManager.snapshotControlsRuntimeState,
controlsManager.resetControlsUnsavedChanges, controlsManager.resetControlsUnsavedChanges,
@ -157,18 +153,13 @@ export const getControlGroupEmbeddableFactory = (services: {
initialChildControlState: controlsManager.snapshotControlsRuntimeState(), initialChildControlState: controlsManager.snapshotControlsRuntimeState(),
}; };
}, },
dataLoading: dataLoading$,
onEdit: async () => { onEdit: async () => {
openEditControlGroupFlyout( openEditControlGroupFlyout(api, {
api,
{
chainingSystem: chainingSystem$, chainingSystem: chainingSystem$,
labelPosition: labelPosition$, labelPosition: labelPosition$,
autoApplySelections: autoApplySelections$, autoApplySelections: autoApplySelections$,
ignoreParentSettings: ignoreParentSettings$, ignoreParentSettings: ignoreParentSettings$,
}, });
{ core: services.core }
);
}, },
isEditingEnabled: () => true, isEditingEnabled: () => true,
openAddDataControlFlyout: (settings) => { openAddDataControlFlyout: (settings) => {
@ -193,7 +184,6 @@ export const getControlGroupEmbeddableFactory = (services: {
settings?.onSave?.(); settings?.onSave?.();
}, },
controlGroupApi: api, controlGroupApi: api,
services,
}); });
}, },
serializeState: () => { serializeState: () => {
@ -201,7 +191,7 @@ export const getControlGroupEmbeddableFactory = (services: {
return { return {
rawState: { rawState: {
chainingSystem: chainingSystem$.getValue(), chainingSystem: chainingSystem$.getValue(),
controlStyle: labelPosition$.getValue(), // Rename "labelPosition" to "controlStyle" controlStyle: labelPosition$.getValue(),
showApplySelections: !autoApplySelections$.getValue(), showApplySelections: !autoApplySelections$.getValue(),
ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()), ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()),
panelsJSON, panelsJSON,
@ -265,10 +255,9 @@ export const getControlGroupEmbeddableFactory = (services: {
/** Fetch the allowExpensiveQuries setting for the children to use if necessary */ /** Fetch the allowExpensiveQuries setting for the children to use if necessary */
const fetchAllowExpensiveQueries = async () => { const fetchAllowExpensiveQueries = async () => {
try { try {
const { allowExpensiveQueries } = await services.core.http.get<{ const { allowExpensiveQueries } = await coreServices.http.get<{
allowExpensiveQueries: boolean; allowExpensiveQueries: boolean;
// TODO: Rename this route as part of https://github.com/elastic/kibana/issues/174961 }>('/internal/controls/getExpensiveQueriesSetting', {
}>('/internal/controls/optionsList/getExpensiveQueriesSetting', {
version: '1', version: '1',
}); });
if (!allowExpensiveQueries) { if (!allowExpensiveQueries) {

View file

@ -8,7 +8,6 @@
*/ */
import { OverlayRef } from '@kbn/core-mount-utils-browser'; import { OverlayRef } from '@kbn/core-mount-utils-browser';
import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { tracksOverlays } from '@kbn/presentation-containers'; import { tracksOverlays } from '@kbn/presentation-containers';
import { apiHasParentApi } from '@kbn/presentation-publishing'; import { apiHasParentApi } from '@kbn/presentation-publishing';
@ -19,13 +18,11 @@ import { BehaviorSubject } from 'rxjs';
import { ControlStateManager } from '../controls/types'; import { ControlStateManager } from '../controls/types';
import { ControlGroupEditor } from './components/control_group_editor'; import { ControlGroupEditor } from './components/control_group_editor';
import { ControlGroupApi, ControlGroupEditorState } from './types'; import { ControlGroupApi, ControlGroupEditorState } from './types';
import { coreServices } from '../../services/kibana_services';
export const openEditControlGroupFlyout = ( export const openEditControlGroupFlyout = (
controlGroupApi: ControlGroupApi, controlGroupApi: ControlGroupApi,
stateManager: ControlStateManager<ControlGroupEditorState>, stateManager: ControlStateManager<ControlGroupEditorState>
services: {
core: CoreStart;
}
) => { ) => {
/** /**
* Duplicate all state into a new manager because we do not want to actually apply the changes * Duplicate all state into a new manager because we do not want to actually apply the changes
@ -50,7 +47,7 @@ export const openEditControlGroupFlyout = (
}; };
const onDeleteAll = (ref: OverlayRef) => { const onDeleteAll = (ref: OverlayRef) => {
services.core.overlays coreServices.overlays
.openConfirm( .openConfirm(
i18n.translate('controls.controlGroup.management.delete.sub', { i18n.translate('controls.controlGroup.management.delete.sub', {
defaultMessage: 'Controls are not recoverable once removed.', defaultMessage: 'Controls are not recoverable once removed.',
@ -77,7 +74,7 @@ export const openEditControlGroupFlyout = (
}); });
}; };
const overlay = services.core.overlays.openFlyout( const overlay = coreServices.overlays.openFlyout(
toMountPoint( toMountPoint(
<ControlGroupEditor <ControlGroupEditor
api={controlGroupApi} api={controlGroupApi}
@ -96,8 +93,8 @@ export const openEditControlGroupFlyout = (
onCancel={() => closeOverlay(overlay)} onCancel={() => closeOverlay(overlay)}
/>, />,
{ {
theme: services.core.theme, theme: coreServices.theme,
i18n: services.core.i18n, i18n: coreServices.i18n,
} }
), ),
{ {

View file

@ -7,23 +7,16 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import type { CoreSetup } from '@kbn/core/public';
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import type { ControlsPluginStartDeps } from '../../types';
import { CONTROL_GROUP_TYPE } from '../../../common'; import { CONTROL_GROUP_TYPE } from '../../../common';
import { untilPluginStartServicesReady } from '../../services/kibana_services';
export function registerControlGroupEmbeddable( export function registerControlGroupEmbeddable(embeddableSetup: EmbeddableSetup) {
coreSetup: CoreSetup<ControlsPluginStartDeps>,
embeddableSetup: EmbeddableSetup
) {
embeddableSetup.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => { embeddableSetup.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => {
const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([ const [{ getControlGroupEmbeddableFactory }] = await Promise.all([
import('./get_control_group_factory'), import('./get_control_group_factory'),
coreSetup.getStartServices(), untilPluginStartServicesReady(),
]); ]);
return getControlGroupEmbeddableFactory({ return getControlGroupEmbeddableFactory();
core: coreStart,
dataViews: depsStart.data.dataViews,
});
}); });
} }

View file

@ -19,7 +19,6 @@ import {
import { import {
HasEditCapabilities, HasEditCapabilities,
HasParentApi, HasParentApi,
PublishesDataLoading,
PublishesDisabledActionIds, PublishesDisabledActionIds,
PublishesFilters, PublishesFilters,
PublishesTimeslice, PublishesTimeslice,
@ -35,8 +34,8 @@ import {
ControlGroupEditorConfig, ControlGroupEditorConfig,
ControlGroupRuntimeState, ControlGroupRuntimeState,
ControlGroupSerializedState, ControlGroupSerializedState,
ControlLabelPosition,
ControlPanelState, ControlPanelState,
ControlStyle,
DefaultControlState, DefaultControlState,
ParentIgnoreSettings, ParentIgnoreSettings,
} from '../../../common'; } from '../../../common';
@ -54,7 +53,6 @@ export type ControlGroupApi = PresentationContainer &
PublishesDataViews & PublishesDataViews &
HasSerializedChildState<ControlPanelState> & HasSerializedChildState<ControlPanelState> &
HasEditCapabilities & HasEditCapabilities &
PublishesDataLoading &
Pick<PublishesUnsavedChanges<ControlGroupRuntimeState>, 'unsavedChanges'> & Pick<PublishesUnsavedChanges<ControlGroupRuntimeState>, 'unsavedChanges'> &
PublishesTimeslice & PublishesTimeslice &
PublishesDisabledActionIds & PublishesDisabledActionIds &
@ -62,7 +60,7 @@ export type ControlGroupApi = PresentationContainer &
allowExpensiveQueries$: PublishingSubject<boolean>; allowExpensiveQueries$: PublishingSubject<boolean>;
autoApplySelections$: PublishingSubject<boolean>; autoApplySelections$: PublishingSubject<boolean>;
ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>; ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>;
labelPosition: PublishingSubject<ControlStyle>; labelPosition: PublishingSubject<ControlLabelPosition>;
asyncResetUnsavedChanges: () => Promise<void>; asyncResetUnsavedChanges: () => Promise<void>;
controlFetch$: (controlUuid: string) => Observable<ControlFetchContext>; controlFetch$: (controlUuid: string) => Observable<ControlFetchContext>;

View file

@ -13,11 +13,12 @@ import {
OPTIONS_LIST_CONTROL, OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL, RANGE_SLIDER_CONTROL,
TIME_SLIDER_CONTROL, TIME_SLIDER_CONTROL,
type ControlGroupRuntimeState,
type ControlPanelsState,
type DefaultDataControlState, type DefaultDataControlState,
} from '../../../../common'; } from '../../../../common';
import { type ControlGroupRuntimeState, type ControlPanelsState } from '../../../../common';
import type { OptionsListControlState } from '../../../../common/options_list'; import type { OptionsListControlState } from '../../../../common/options_list';
import { pluginServices } from '../../../services'; import { dataViewsService } from '../../../services/kibana_services';
import { getDataControlFieldRegistry } from '../../controls/data_controls/data_control_editor_utils'; import { getDataControlFieldRegistry } from '../../controls/data_controls/data_control_editor_utils';
import type { RangesliderControlState } from '../../controls/data_controls/range_slider/types'; import type { RangesliderControlState } from '../../controls/data_controls/range_slider/types';
@ -82,7 +83,7 @@ export const controlGroupStateBuilder = {
}; };
async function getCompatibleControlType(dataViewId: string, fieldName: string) { async function getCompatibleControlType(dataViewId: string, fieldName: string) {
const dataView = await pluginServices.getServices().dataViews.get(dataViewId); const dataView = await dataViewsService.get(dataViewId);
const fieldRegistry = await getDataControlFieldRegistry(dataView); const fieldRegistry = await getDataControlFieldRegistry(dataView);
const field = fieldRegistry[fieldName]; const field = fieldRegistry[fieldName];
if (field.compatibleControlTypes.length === 0) { if (field.compatibleControlTypes.length === 0) {

View file

@ -7,11 +7,11 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { type ControlGroupRuntimeState, DEFAULT_CONTROL_STYLE } from '../../../../common'; import { DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState } from '../../../../common';
export const getDefaultControlGroupRuntimeState = (): ControlGroupRuntimeState => ({ export const getDefaultControlGroupRuntimeState = (): ControlGroupRuntimeState => ({
initialChildControlState: {}, initialChildControlState: {},
labelPosition: DEFAULT_CONTROL_STYLE, labelPosition: DEFAULT_CONTROL_LABEL_POSITION,
chainingSystem: 'HIERARCHICAL', chainingSystem: 'HIERARCHICAL',
autoApplySelections: true, autoApplySelections: true,
ignoreParentSettings: { ignoreParentSettings: {

View file

@ -12,7 +12,6 @@ import { BehaviorSubject } from 'rxjs';
import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub';
import { stubFieldSpecMap } from '@kbn/data-views-plugin/common/field.stub'; import { stubFieldSpecMap } from '@kbn/data-views-plugin/common/field.stub';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { TimeRange } from '@kbn/es-query'; import { TimeRange } from '@kbn/es-query';
import { I18nProvider } from '@kbn/i18n-react'; import { I18nProvider } from '@kbn/i18n-react';
import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react';
@ -22,6 +21,7 @@ import {
DEFAULT_CONTROL_WIDTH, DEFAULT_CONTROL_WIDTH,
type DefaultDataControlState, type DefaultDataControlState,
} from '../../../../common'; } from '../../../../common';
import { dataViewsService } from '../../../services/kibana_services';
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
import type { ControlGroupApi } from '../../control_group/types'; import type { ControlGroupApi } from '../../control_group/types';
import type { ControlFactory } from '../types'; import type { ControlFactory } from '../types';
@ -39,7 +39,6 @@ jest.mock('../../control_factory_registry', () => ({
getControlFactory: jest.fn(), getControlFactory: jest.fn(),
})); }));
const mockDataViews = dataViewPluginMocks.createStartContract();
const mockDataView = createStubDataView({ const mockDataView = createStubDataView({
spec: { spec: {
id: 'logstash-*', id: 'logstash-*',
@ -58,7 +57,6 @@ const mockDataView = createStubDataView({
timeFieldName: '@timestamp', timeFieldName: '@timestamp',
}, },
}); });
mockDataViews.get = jest.fn().mockResolvedValue(mockDataView);
const dashboardApi = { const dashboardApi = {
timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined), timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined),
@ -82,7 +80,7 @@ describe('Data control editor', () => {
controlType?: string; controlType?: string;
initialDefaultPanelTitle?: string; initialDefaultPanelTitle?: string;
}) => { }) => {
mockDataViews.get = jest.fn().mockResolvedValue(mockDataView); dataViewsService.get = jest.fn().mockResolvedValue(mockDataView);
const controlEditor = render( const controlEditor = render(
<I18nProvider> <I18nProvider>
@ -97,13 +95,12 @@ describe('Data control editor', () => {
controlId={controlId} controlId={controlId}
controlType={controlType} controlType={controlType}
initialDefaultPanelTitle={initialDefaultPanelTitle} initialDefaultPanelTitle={initialDefaultPanelTitle}
services={{ dataViews: mockDataViews }}
/> />
</I18nProvider> </I18nProvider>
); );
await waitFor(() => { await waitFor(() => {
expect(mockDataViews.get).toHaveBeenCalledTimes(1); expect(dataViewsService.get).toHaveBeenCalledTimes(1);
}); });
return controlEditor; return controlEditor;

View file

@ -33,7 +33,6 @@ import {
EuiToolTip, EuiToolTip,
} from '@elastic/eui'; } from '@elastic/eui';
import { DataViewField } from '@kbn/data-views-plugin/common'; import { DataViewField } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { import {
LazyDataViewPicker, LazyDataViewPicker,
LazyFieldPicker, LazyFieldPicker,
@ -46,6 +45,7 @@ import {
type ControlWidth, type ControlWidth,
type DefaultDataControlState, type DefaultDataControlState,
} from '../../../../common'; } from '../../../../common';
import { dataViewsService } from '../../../services/kibana_services';
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
import type { ControlGroupApi } from '../../control_group/types'; import type { ControlGroupApi } from '../../control_group/types';
import { DataControlEditorStrings } from './data_control_constants'; import { DataControlEditorStrings } from './data_control_constants';
@ -67,9 +67,6 @@ export interface ControlEditorProps<
controlGroupApi: ControlGroupApi; // controls must always have a parent API controlGroupApi: ControlGroupApi; // controls must always have a parent API
onCancel: (newState: Partial<State>) => void; onCancel: (newState: Partial<State>) => void;
onSave: (newState: Partial<State>, type: string) => void; onSave: (newState: Partial<State>, type: string) => void;
services: {
dataViews: DataViewsPublicPluginStart;
};
} }
const FieldPicker = withSuspense(LazyFieldPicker, null); const FieldPicker = withSuspense(LazyFieldPicker, null);
@ -151,8 +148,6 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
onSave, onSave,
onCancel, onCancel,
controlGroupApi, controlGroupApi,
/** TODO: These should not be props */
services: { dataViews: dataViewService },
}: ControlEditorProps<State>) => { }: ControlEditorProps<State>) => {
const [editorState, setEditorState] = useState<Partial<State>>(initialState); const [editorState, setEditorState] = useState<Partial<State>>(initialState);
const [defaultPanelTitle, setDefaultPanelTitle] = useState<string>( const [defaultPanelTitle, setDefaultPanelTitle] = useState<string>(
@ -163,16 +158,14 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
const [controlOptionsValid, setControlOptionsValid] = useState<boolean>(true); const [controlOptionsValid, setControlOptionsValid] = useState<boolean>(true);
const editorConfig = useMemo(() => controlGroupApi.getEditorConfig(), [controlGroupApi]); const editorConfig = useMemo(() => controlGroupApi.getEditorConfig(), [controlGroupApi]);
// TODO: Maybe remove `useAsync` - see https://github.com/elastic/kibana/pull/182842#discussion_r1624909709
const { const {
loading: dataViewListLoading, loading: dataViewListLoading,
value: dataViewListItems = [], value: dataViewListItems = [],
error: dataViewListError, error: dataViewListError,
} = useAsync(async () => { } = useAsync(async () => {
return dataViewService.getIdsWithTitle(); return dataViewsService.getIdsWithTitle();
}); });
// TODO: Maybe remove `useAsync` - see https://github.com/elastic/kibana/pull/182842#discussion_r1624909709
const { const {
loading: dataViewLoading, loading: dataViewLoading,
value: { selectedDataView, fieldRegistry } = { value: { selectedDataView, fieldRegistry } = {
@ -185,7 +178,7 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
return; return;
} }
const dataView = await dataViewService.get(editorState.dataViewId); const dataView = await dataViewsService.get(editorState.dataViewId);
const registry = await getDataControlFieldRegistry(dataView); const registry = await getDataControlFieldRegistry(dataView);
return { return {
selectedDataView: dataView, selectedDataView: dataView,

View file

@ -13,7 +13,6 @@ import type { DataView } from '@kbn/data-views-plugin/common';
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
import { isDataControlFactory, type DataControlFieldRegistry } from './types'; import { isDataControlFactory, type DataControlFieldRegistry } from './types';
/** TODO: This funciton is duplicated from the controls plugin to avoid exporting it */
export const getDataControlFieldRegistry = memoize( export const getDataControlFieldRegistry = memoize(
async (dataView: DataView) => { async (dataView: DataView) => {
return await loadFieldRegistryFromDataView(dataView); return await loadFieldRegistryFromDataView(dataView);
@ -21,7 +20,6 @@ export const getDataControlFieldRegistry = memoize(
(dataView: DataView) => [dataView.id, JSON.stringify(dataView.fields.getAll())].join('|') (dataView: DataView) => [dataView.id, JSON.stringify(dataView.fields.getAll())].join('|')
); );
/** TODO: This function is duplicated from the controls plugin to avoid exporting it */
const loadFieldRegistryFromDataView = async ( const loadFieldRegistryFromDataView = async (
dataView: DataView dataView: DataView
): Promise<DataControlFieldRegistry> => { ): Promise<DataControlFieldRegistry> => {

View file

@ -7,10 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { coreMock } from '@kbn/core/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public';
import { first, skip } from 'rxjs'; import { first, skip } from 'rxjs';
import { dataViewsService } from '../../../services/kibana_services';
import { ControlGroupApi } from '../../control_group/types'; import { ControlGroupApi } from '../../control_group/types';
import { initializeDataControl } from './initialize_data_control'; import { initializeDataControl } from './initialize_data_control';
@ -21,9 +20,8 @@ describe('initializeDataControl', () => {
}; };
const editorStateManager = {}; const editorStateManager = {};
const controlGroupApi = {} as unknown as ControlGroupApi; const controlGroupApi = {} as unknown as ControlGroupApi;
const mockDataViews = dataViewPluginMocks.createStartContract();
// @ts-ignore dataViewsService.get = async (id: string): Promise<DataView> => {
mockDataViews.get = async (id: string): Promise<DataView> => {
if (id !== 'myDataViewId') { if (id !== 'myDataViewId') {
throw new Error(`Simulated error: no data view found for id ${id}`); throw new Error(`Simulated error: no data view found for id ${id}`);
} }
@ -40,10 +38,6 @@ describe('initializeDataControl', () => {
}, },
} as unknown as DataView; } as unknown as DataView;
}; };
const services = {
core: coreMock.createStart(),
dataViews: mockDataViews,
};
describe('dataViewId subscription', () => { describe('dataViewId subscription', () => {
describe('no blocking errors', () => { describe('no blocking errors', () => {
@ -55,8 +49,7 @@ describe('initializeDataControl', () => {
'referenceNameSuffix', 'referenceNameSuffix',
dataControlState, dataControlState,
editorStateManager, editorStateManager,
controlGroupApi, controlGroupApi
services
); );
dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => { dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => {
@ -90,8 +83,7 @@ describe('initializeDataControl', () => {
dataViewId: 'notGonnaFindMeDataViewId', dataViewId: 'notGonnaFindMeDataViewId',
}, },
editorStateManager, editorStateManager,
controlGroupApi, controlGroupApi
services
); );
dataControl.api.dataViews.pipe(skip(1), first()).subscribe(() => { dataControl.api.dataViews.pipe(skip(1), first()).subscribe(() => {
@ -129,8 +121,7 @@ describe('initializeDataControl', () => {
fieldName: 'notGonnaFindMeFieldName', fieldName: 'notGonnaFindMeFieldName',
}, },
editorStateManager, editorStateManager,
controlGroupApi, controlGroupApi
services
); );
dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => { dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => {

View file

@ -10,19 +10,18 @@
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, debounceTime, first, skip, switchMap, tap } from 'rxjs'; import { BehaviorSubject, combineLatest, debounceTime, first, skip, switchMap, tap } from 'rxjs';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { import {
DATA_VIEW_SAVED_OBJECT_TYPE, DATA_VIEW_SAVED_OBJECT_TYPE,
DataView, DataView,
DataViewField, DataViewField,
} from '@kbn/data-views-plugin/common'; } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { Filter } from '@kbn/es-query'; import { Filter } from '@kbn/es-query';
import { SerializedPanelState } from '@kbn/presentation-containers'; import { SerializedPanelState } from '@kbn/presentation-containers';
import { StateComparators } from '@kbn/presentation-publishing'; import { StateComparators } from '@kbn/presentation-publishing';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { DefaultControlState, DefaultDataControlState } from '../../../../common'; import type { DefaultControlState, DefaultDataControlState } from '../../../../common';
import { dataViewsService } from '../../../services/kibana_services';
import type { ControlGroupApi } from '../../control_group/types'; import type { ControlGroupApi } from '../../control_group/types';
import { initializeDefaultControlApi } from '../initialize_default_control_api'; import { initializeDefaultControlApi } from '../initialize_default_control_api';
import type { ControlApiInitialization, ControlStateManager } from '../types'; import type { ControlApiInitialization, ControlStateManager } from '../types';
@ -40,11 +39,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
* responsible for managing * responsible for managing
*/ */
editorStateManager: ControlStateManager<EditorState>, editorStateManager: ControlStateManager<EditorState>,
controlGroupApi: ControlGroupApi, controlGroupApi: ControlGroupApi
services: {
core: CoreStart;
dataViews: DataViewsPublicPluginStart;
}
): { ): {
api: ControlApiInitialization<DataControlApi>; api: ControlApiInitialization<DataControlApi>;
cleanup: () => void; cleanup: () => void;
@ -88,7 +83,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
switchMap(async (currentDataViewId) => { switchMap(async (currentDataViewId) => {
let dataView: DataView | undefined; let dataView: DataView | undefined;
try { try {
dataView = await services.dataViews.get(currentDataViewId); dataView = await dataViewsService.get(currentDataViewId);
return { dataView }; return { dataView };
} catch (error) { } catch (error) {
return { error }; return { error };
@ -156,7 +151,6 @@ export const initializeDataControl = <EditorState extends object = {}>(
// open the editor to get the new state // open the editor to get the new state
openDataControlEditor<DefaultDataControlState & EditorState>({ openDataControlEditor<DefaultDataControlState & EditorState>({
services,
onSave: ({ type: newType, state: newState }) => { onSave: ({ type: newType, state: newState }) => {
if (newType === controlType) { if (newType === controlType) {
// apply the changes from the new state via the state manager // apply the changes from the new state via the state manager

View file

@ -10,14 +10,14 @@
import React from 'react'; import React from 'react';
import deepEqual from 'react-fast-compare'; import deepEqual from 'react-fast-compare';
import { CoreStart, OverlayRef } from '@kbn/core/public'; import { OverlayRef } from '@kbn/core/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { tracksOverlays } from '@kbn/presentation-containers'; import { tracksOverlays } from '@kbn/presentation-containers';
import { apiHasParentApi } from '@kbn/presentation-publishing'; import { apiHasParentApi } from '@kbn/presentation-publishing';
import { toMountPoint } from '@kbn/react-kibana-mount'; import { toMountPoint } from '@kbn/react-kibana-mount';
import type { DefaultDataControlState } from '../../../../common'; import type { DefaultDataControlState } from '../../../../common';
import { coreServices } from '../../../services/kibana_services';
import type { ControlGroupApi } from '../../control_group/types'; import type { ControlGroupApi } from '../../control_group/types';
import { DataControlEditor } from './data_control_editor'; import { DataControlEditor } from './data_control_editor';
@ -30,7 +30,6 @@ export const openDataControlEditor = <
initialDefaultPanelTitle, initialDefaultPanelTitle,
onSave, onSave,
controlGroupApi, controlGroupApi,
services,
}: { }: {
initialState: Partial<State>; initialState: Partial<State>;
controlType?: string; controlType?: string;
@ -38,10 +37,6 @@ export const openDataControlEditor = <
initialDefaultPanelTitle?: string; initialDefaultPanelTitle?: string;
onSave: ({ type, state }: { type: string; state: Partial<State> }) => void; onSave: ({ type, state }: { type: string; state: Partial<State> }) => void;
controlGroupApi: ControlGroupApi; controlGroupApi: ControlGroupApi;
services: {
core: CoreStart;
dataViews: DataViewsPublicPluginStart;
};
}): void => { }): void => {
const closeOverlay = (overlayRef: OverlayRef) => { const closeOverlay = (overlayRef: OverlayRef) => {
if (apiHasParentApi(controlGroupApi) && tracksOverlays(controlGroupApi.parentApi)) { if (apiHasParentApi(controlGroupApi) && tracksOverlays(controlGroupApi.parentApi)) {
@ -55,7 +50,7 @@ export const openDataControlEditor = <
closeOverlay(overlay); closeOverlay(overlay);
return; return;
} }
services.core.overlays coreServices.overlays
.openConfirm( .openConfirm(
i18n.translate('controls.controlGroup.management.discard.sub', { i18n.translate('controls.controlGroup.management.discard.sub', {
defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`,
@ -80,7 +75,7 @@ export const openDataControlEditor = <
}); });
}; };
const overlay = services.core.overlays.openFlyout( const overlay = coreServices.overlays.openFlyout(
toMountPoint( toMountPoint(
<DataControlEditor<State> <DataControlEditor<State>
controlGroupApi={controlGroupApi} controlGroupApi={controlGroupApi}
@ -95,11 +90,10 @@ export const openDataControlEditor = <
closeOverlay(overlay); closeOverlay(overlay);
onSave({ type: selectedControlType, state }); onSave({ type: selectedControlType, state });
}} }}
services={{ dataViews: services.dataViews }}
/>, />,
{ {
theme: services.core.theme, theme: coreServices.theme,
i18n: services.core.i18n, i18n: coreServices.i18n,
} }
), ),
{ {

View file

@ -26,13 +26,11 @@ import { isValidSearch } from '../../../../../common/options_list/is_valid_searc
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlStateManager } from '../../types'; import { ControlStateManager } from '../../types';
import { DataControlServices } from '../types';
import { OptionsListFetchCache } from './options_list_fetch_cache'; import { OptionsListFetchCache } from './options_list_fetch_cache';
import { OptionsListComponentApi, OptionsListComponentState, OptionsListControlApi } from './types'; import { OptionsListComponentApi, OptionsListComponentState, OptionsListControlApi } from './types';
export function fetchAndValidate$({ export function fetchAndValidate$({
api, api,
services,
stateManager, stateManager,
}: { }: {
api: Pick<OptionsListControlApi, 'dataViews' | 'field$' | 'setBlockingError' | 'parentApi'> & api: Pick<OptionsListControlApi, 'dataViews' | 'field$' | 'setBlockingError' | 'parentApi'> &
@ -41,7 +39,6 @@ export function fetchAndValidate$({
loadingSuggestions$: BehaviorSubject<boolean>; loadingSuggestions$: BehaviorSubject<boolean>;
debouncedSearchString: Observable<string>; debouncedSearchString: Observable<string>;
}; };
services: DataControlServices;
stateManager: ControlStateManager< stateManager: ControlStateManager<
Pick<OptionsListComponentState, 'requestSize' | 'runPastTimeout' | 'searchTechnique' | 'sort'> Pick<OptionsListComponentState, 'requestSize' | 'runPastTimeout' | 'searchTechnique' | 'sort'>
> & { > & {
@ -126,7 +123,7 @@ export function fetchAndValidate$({
const newAbortController = new AbortController(); const newAbortController = new AbortController();
abortController = newAbortController; abortController = newAbortController;
try { try {
return await requestCache.runFetchRequest(request, newAbortController.signal, services); return await requestCache.runFetchRequest(request, newAbortController.signal);
} catch (error) { } catch (error) {
return { error }; return { error };
} }

View file

@ -9,26 +9,22 @@
import React from 'react'; import React from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { DataView } from '@kbn/data-views-plugin/common'; import { DataView } from '@kbn/data-views-plugin/common';
import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { act, render, waitFor } from '@testing-library/react'; import { act, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { coreServices, dataViewsService } from '../../../../services/kibana_services';
import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks';
import { getOptionsListControlFactory } from './get_options_list_control_factory'; import { getOptionsListControlFactory } from './get_options_list_control_factory';
describe('Options List Control Api', () => { describe('Options List Control Api', () => {
const uuid = 'myControl1'; const uuid = 'myControl1';
const controlGroupApi = getMockedControlGroupApi(); const controlGroupApi = getMockedControlGroupApi();
const mockDataViews = dataViewPluginMocks.createStartContract();
const mockCore = coreMock.createStart();
const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0))); const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0)));
mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => { dataViewsService.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => {
if (id !== 'myDataViewId') { if (id !== 'myDataViewId') {
throw new Error(`Simulated error: no data view found for id ${id}`); throw new Error(`Simulated error: no data view found for id ${id}`);
} }
@ -60,11 +56,7 @@ describe('Options List Control Api', () => {
return stubDataView; return stubDataView;
}); });
const factory = getOptionsListControlFactory({ const factory = getOptionsListControlFactory();
core: mockCore,
data: dataPluginMock.createStartContract(),
dataViews: mockDataViews,
});
describe('filters$', () => { describe('filters$', () => {
test('should not set filters$ when selectedOptions is not provided', async () => { test('should not set filters$ when selectedOptions is not provided', async () => {
@ -177,7 +169,7 @@ describe('Options List Control Api', () => {
describe('make selection', () => { describe('make selection', () => {
beforeAll(() => { beforeAll(() => {
mockCore.http.fetch = jest.fn().mockResolvedValue({ coreServices.http.fetch = jest.fn().mockResolvedValue({
suggestions: [ suggestions: [
{ value: 'woof', docCount: 10 }, { value: 'woof', docCount: 10 },
{ value: 'bark', docCount: 15 }, { value: 'bark', docCount: 15 },

View file

@ -9,7 +9,7 @@
import fastIsEqual from 'fast-deep-equal'; import fastIsEqual from 'fast-deep-equal';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { BehaviorSubject, combineLatest, debounceTime, filter, skip } from 'rxjs'; import { BehaviorSubject, combineLatest, debounceTime, filter, map, skip } from 'rxjs';
import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, Filter } from '@kbn/es-query'; import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, Filter } from '@kbn/es-query';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
@ -25,7 +25,7 @@ import type {
} from '../../../../../common/options_list'; } from '../../../../../common/options_list';
import { getSelectionAsFieldType, isValidSearch } from '../../../../../common/options_list'; import { getSelectionAsFieldType, isValidSearch } from '../../../../../common/options_list';
import { initializeDataControl } from '../initialize_data_control'; import { initializeDataControl } from '../initialize_data_control';
import type { DataControlFactory, DataControlServices } from '../types'; import type { DataControlFactory } from '../types';
import { OptionsListControl } from './components/options_list_control'; import { OptionsListControl } from './components/options_list_control';
import { OptionsListEditorOptions } from './components/options_list_editor_options'; import { OptionsListEditorOptions } from './components/options_list_editor_options';
import { import {
@ -39,9 +39,10 @@ import { initializeOptionsListSelections } from './options_list_control_selectio
import { OptionsListStrings } from './options_list_strings'; import { OptionsListStrings } from './options_list_strings';
import type { OptionsListControlApi } from './types'; import type { OptionsListControlApi } from './types';
export const getOptionsListControlFactory = ( export const getOptionsListControlFactory = (): DataControlFactory<
services: DataControlServices OptionsListControlState,
): DataControlFactory<OptionsListControlState, OptionsListControlApi> => { OptionsListControlApi
> => {
return { return {
type: OPTIONS_LIST_CONTROL, type: OPTIONS_LIST_CONTROL,
order: 3, // should always be first, since this is the most popular control order: 3, // should always be first, since this is the most popular control
@ -78,6 +79,7 @@ export const getOptionsListControlFactory = (
const searchStringValid$ = new BehaviorSubject<boolean>(true); const searchStringValid$ = new BehaviorSubject<boolean>(true);
const requestSize$ = new BehaviorSubject<number>(MIN_OPTIONS_LIST_REQUEST_SIZE); const requestSize$ = new BehaviorSubject<number>(MIN_OPTIONS_LIST_REQUEST_SIZE);
const dataLoading$ = new BehaviorSubject<boolean | undefined>(undefined);
const availableOptions$ = new BehaviorSubject<OptionsListSuggestions | undefined>(undefined); const availableOptions$ = new BehaviorSubject<OptionsListSuggestions | undefined>(undefined);
const invalidSelections$ = new BehaviorSubject<Set<OptionsListSelection>>(new Set()); const invalidSelections$ = new BehaviorSubject<Set<OptionsListSelection>>(new Set());
const totalCardinality$ = new BehaviorSubject<number>(0); const totalCardinality$ = new BehaviorSubject<number>(0);
@ -90,8 +92,7 @@ export const getOptionsListControlFactory = (
'optionsListDataView', 'optionsListDataView',
initialState, initialState,
{ searchTechnique: searchTechnique$, singleSelect: singleSelect$ }, { searchTechnique: searchTechnique$, singleSelect: singleSelect$ },
controlGroupApi, controlGroupApi
services
); );
const selections = initializeOptionsListSelections( const selections = initializeOptionsListSelections(
@ -115,12 +116,16 @@ export const getOptionsListControlFactory = (
/** Handle loading state; since suggestion fetching and validation are tied, only need one loading subject */ /** Handle loading state; since suggestion fetching and validation are tied, only need one loading subject */
const loadingSuggestions$ = new BehaviorSubject<boolean>(false); const loadingSuggestions$ = new BehaviorSubject<boolean>(false);
const dataLoadingSubscription = loadingSuggestions$ const dataLoadingSubscription = combineLatest([
loadingSuggestions$,
dataControl.api.dataLoading,
])
.pipe( .pipe(
debounceTime(100) // debounce set loading so that it doesn't flash as the user types debounceTime(100), // debounce set loading so that it doesn't flash as the user types
map((values) => values.some((value) => value))
) )
.subscribe((isLoading) => { .subscribe((isLoading) => {
dataControl.api.setDataLoading(isLoading); dataLoading$.next(isLoading);
}); });
/** Debounce the search string changes to reduce the number of fetch requests */ /** Debounce the search string changes to reduce the number of fetch requests */
@ -161,7 +166,6 @@ export const getOptionsListControlFactory = (
/** Fetch the suggestions and perform validation */ /** Fetch the suggestions and perform validation */
const loadMoreSubject = new BehaviorSubject<null>(null); const loadMoreSubject = new BehaviorSubject<null>(null);
const fetchSubscription = fetchAndValidate$({ const fetchSubscription = fetchAndValidate$({
services,
api: { api: {
...dataControl.api, ...dataControl.api,
loadMoreSubject, loadMoreSubject,
@ -235,6 +239,7 @@ export const getOptionsListControlFactory = (
const api = buildApi( const api = buildApi(
{ {
...dataControl.api, ...dataControl.api,
dataLoading: dataLoading$,
getTypeDisplayName: OptionsListStrings.control.getDisplayName, getTypeDisplayName: OptionsListStrings.control.getDisplayName,
serializeState: () => { serializeState: () => {
const { rawState: dataControlState, references } = dataControl.serialize(); const { rawState: dataControlState, references } = dataControl.serialize();

View file

@ -20,7 +20,7 @@ import type {
OptionsListResponse, OptionsListResponse,
OptionsListSuccessResponse, OptionsListSuccessResponse,
} from '../../../../../common/options_list/types'; } from '../../../../../common/options_list/types';
import type { DataControlServices } from '../types'; import { coreServices, dataService } from '../../../../services/kibana_services';
const REQUEST_CACHE_SIZE = 50; // only store a max of 50 responses const REQUEST_CACHE_SIZE = 50; // only store a max of 50 responses
const REQUEST_CACHE_TTL = 1000 * 60; // time to live = 1 minute const REQUEST_CACHE_TTL = 1000 * 60; // time to live = 1 minute
@ -80,8 +80,7 @@ export class OptionsListFetchCache {
public async runFetchRequest( public async runFetchRequest(
request: OptionsListRequest, request: OptionsListRequest,
abortSignal: AbortSignal, abortSignal: AbortSignal
services: DataControlServices
): Promise<OptionsListResponse> { ): Promise<OptionsListResponse> {
const requestHash = this.getRequestHash(request); const requestHash = this.getRequestHash(request);
@ -90,11 +89,11 @@ export class OptionsListFetchCache {
} else { } else {
const index = request.dataView.getIndexPattern(); const index = request.dataView.getIndexPattern();
const timeService = services.data.query.timefilter.timefilter; const timeService = dataService.query.timefilter.timefilter;
const { query, filters, dataView, timeRange, field, ...passThroughProps } = request; const { query, filters, dataView, timeRange, field, ...passThroughProps } = request;
const timeFilter = timeRange ? timeService.createFilter(dataView, timeRange) : undefined; const timeFilter = timeRange ? timeService.createFilter(dataView, timeRange) : undefined;
const filtersToUse = [...(filters ?? []), ...(timeFilter ? [timeFilter] : [])]; const filtersToUse = [...(filters ?? []), ...(timeFilter ? [timeFilter] : [])];
const config = getEsQueryConfig(services.core.uiSettings); const config = getEsQueryConfig(coreServices.uiSettings);
const esFilters = [buildEsQuery(dataView, query ?? [], filtersToUse ?? [], config)]; const esFilters = [buildEsQuery(dataView, query ?? [], filtersToUse ?? [], config)];
const requestBody = { const requestBody = {
@ -105,7 +104,7 @@ export class OptionsListFetchCache {
runtimeFieldMap: dataView.toSpec?.().runtimeFieldMap, runtimeFieldMap: dataView.toSpec?.().runtimeFieldMap,
}; };
const result = await services.core.http.fetch<OptionsListResponse>( const result = await coreServices.http.fetch<OptionsListResponse>(
`/internal/controls/optionsList/${index}`, `/internal/controls/optionsList/${index}`,
{ {
version: '1', version: '1',

View file

@ -7,21 +7,16 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import type { CoreSetup } from '@kbn/core/public';
import type { ControlsPluginStartDeps } from '../../../../types';
import { registerControlFactory } from '../../../control_factory_registry';
import { OPTIONS_LIST_CONTROL } from '../../../../../common'; import { OPTIONS_LIST_CONTROL } from '../../../../../common';
import { untilPluginStartServicesReady } from '../../../../services/kibana_services';
import { registerControlFactory } from '../../../control_factory_registry';
export function registerOptionsListControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { export function registerOptionsListControl() {
registerControlFactory(OPTIONS_LIST_CONTROL, async () => { registerControlFactory(OPTIONS_LIST_CONTROL, async () => {
const [{ getOptionsListControlFactory }, [coreStart, depsStart]] = await Promise.all([ const [{ getOptionsListControlFactory }] = await Promise.all([
import('./get_options_list_control_factory'), import('./get_options_list_control_factory'),
coreSetup.getStartServices(), untilPluginStartServicesReady(),
]); ]);
return getOptionsListControlFactory({ return getOptionsListControlFactory();
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
}); });
} }

View file

@ -11,13 +11,11 @@ import React from 'react';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { estypes } from '@elastic/elasticsearch'; import { estypes } from '@elastic/elasticsearch';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { DataViewField } from '@kbn/data-views-plugin/common'; import { DataViewField } from '@kbn/data-views-plugin/common';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { SerializedPanelState } from '@kbn/presentation-containers'; import { SerializedPanelState } from '@kbn/presentation-containers';
import { fireEvent, render, waitFor } from '@testing-library/react'; import { fireEvent, render, waitFor } from '@testing-library/react';
import { dataService, dataViewsService } from '../../../../services/kibana_services';
import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks';
import { getRangesliderControlFactory } from './get_range_slider_control_factory'; import { getRangesliderControlFactory } from './get_range_slider_control_factory';
import { RangesliderControlState } from './types'; import { RangesliderControlState } from './types';
@ -31,11 +29,10 @@ describe('RangesliderControlApi', () => {
const controlGroupApi = getMockedControlGroupApi(); const controlGroupApi = getMockedControlGroupApi();
const dataStartServiceMock = dataPluginMock.createStartContract();
let totalResults = DEFAULT_TOTAL_RESULTS; let totalResults = DEFAULT_TOTAL_RESULTS;
let min: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MIN; let min: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MIN;
let max: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MAX; let max: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MAX;
dataStartServiceMock.search.searchSource.create = jest.fn().mockImplementation(() => { dataService.search.searchSource.create = jest.fn().mockImplementation(() => {
let isAggsRequest = false; let isAggsRequest = false;
return { return {
setField: (key: string) => { setField: (key: string) => {
@ -54,9 +51,8 @@ describe('RangesliderControlApi', () => {
}, },
}; };
}); });
const mockDataViews = dataViewPluginMocks.createStartContract();
mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => { dataViewsService.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => {
if (id !== 'myDataViewId') { if (id !== 'myDataViewId') {
throw new Error(`no data view found for id ${id}`); throw new Error(`no data view found for id ${id}`);
} }
@ -82,11 +78,7 @@ describe('RangesliderControlApi', () => {
} as unknown as DataView; } as unknown as DataView;
}); });
const factory = getRangesliderControlFactory({ const factory = getRangesliderControlFactory();
core: coreMock.createStart(),
data: dataStartServiceMock,
dataViews: mockDataViews,
});
beforeEach(() => { beforeEach(() => {
totalResults = DEFAULT_TOTAL_RESULTS; totalResults = DEFAULT_TOTAL_RESULTS;

View file

@ -16,7 +16,7 @@ import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { RANGE_SLIDER_CONTROL } from '../../../../../common'; import { RANGE_SLIDER_CONTROL } from '../../../../../common';
import { initializeDataControl } from '../initialize_data_control'; import { initializeDataControl } from '../initialize_data_control';
import type { DataControlFactory, DataControlServices } from '../types'; import type { DataControlFactory } from '../types';
import { RangeSliderControl } from './components/range_slider_control'; import { RangeSliderControl } from './components/range_slider_control';
import { hasNoResults$ } from './has_no_results'; import { hasNoResults$ } from './has_no_results';
import { minMax$ } from './min_max'; import { minMax$ } from './min_max';
@ -24,9 +24,10 @@ import { initializeRangeControlSelections } from './range_control_selections';
import { RangeSliderStrings } from './range_slider_strings'; import { RangeSliderStrings } from './range_slider_strings';
import type { RangesliderControlApi, RangesliderControlState } from './types'; import type { RangesliderControlApi, RangesliderControlState } from './types';
export const getRangesliderControlFactory = ( export const getRangesliderControlFactory = (): DataControlFactory<
services: DataControlServices RangesliderControlState,
): DataControlFactory<RangesliderControlState, RangesliderControlApi> => { RangesliderControlApi
> => {
return { return {
type: RANGE_SLIDER_CONTROL, type: RANGE_SLIDER_CONTROL,
getIconType: () => 'controlsHorizontal', getIconType: () => 'controlsHorizontal',
@ -71,8 +72,7 @@ export const getRangesliderControlFactory = (
{ {
step: step$, step: step$,
}, },
controlGroupApi, controlGroupApi
services
); );
const selections = initializeRangeControlSelections( const selections = initializeRangeControlSelections(
@ -111,13 +111,14 @@ export const getRangesliderControlFactory = (
} }
); );
const dataLoadingSubscription = combineLatest([loadingMinMax$, loadingHasNoResults$]) const dataLoadingSubscription = combineLatest([
loadingMinMax$,
loadingHasNoResults$,
dataControl.api.dataLoading,
])
.pipe( .pipe(
map((values) => { debounceTime(100),
return values.some((value) => { map((values) => values.some((value) => value))
return value;
});
})
) )
.subscribe((isLoading) => { .subscribe((isLoading) => {
dataLoading$.next(isLoading); dataLoading$.next(isLoading);
@ -138,7 +139,6 @@ export const getRangesliderControlFactory = (
const min$ = new BehaviorSubject<number | undefined>(undefined); const min$ = new BehaviorSubject<number | undefined>(undefined);
const minMaxSubscription = minMax$({ const minMaxSubscription = minMax$({
controlFetch$, controlFetch$,
data: services.data,
dataViews$: dataControl.api.dataViews, dataViews$: dataControl.api.dataViews,
fieldName$: dataControl.stateManager.fieldName, fieldName$: dataControl.stateManager.fieldName,
setIsLoading: (isLoading: boolean) => { setIsLoading: (isLoading: boolean) => {
@ -198,7 +198,6 @@ export const getRangesliderControlFactory = (
const selectionHasNoResults$ = new BehaviorSubject(false); const selectionHasNoResults$ = new BehaviorSubject(false);
const hasNotResultsSubscription = hasNoResults$({ const hasNotResultsSubscription = hasNoResults$({
controlFetch$, controlFetch$,
data: services.data,
dataViews$: dataControl.api.dataViews, dataViews$: dataControl.api.dataViews,
rangeFilters$: dataControl.api.filters$, rangeFilters$: dataControl.api.filters$,
ignoreParentSettings$: controlGroupApi.ignoreParentSettings$, ignoreParentSettings$: controlGroupApi.ignoreParentSettings$,

View file

@ -8,25 +8,23 @@
*/ */
import { estypes } from '@elastic/elasticsearch'; import { estypes } from '@elastic/elasticsearch';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesDataViews } from '@kbn/presentation-publishing'; import { PublishesDataViews } from '@kbn/presentation-publishing';
import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs'; import { Observable, combineLatest, lastValueFrom, switchMap, tap } from 'rxjs';
import { dataService } from '../../../../services/kibana_services';
import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlGroupApi } from '../../../control_group/types'; import { ControlGroupApi } from '../../../control_group/types';
import { DataControlApi } from '../types'; import { DataControlApi } from '../types';
export function hasNoResults$({ export function hasNoResults$({
controlFetch$, controlFetch$,
data,
dataViews$, dataViews$,
rangeFilters$, rangeFilters$,
ignoreParentSettings$, ignoreParentSettings$,
setIsLoading, setIsLoading,
}: { }: {
controlFetch$: Observable<ControlFetchContext>; controlFetch$: Observable<ControlFetchContext>;
data: DataPublicPluginStart;
dataViews$?: PublishesDataViews['dataViews']; dataViews$?: PublishesDataViews['dataViews'];
rangeFilters$: DataControlApi['filters$']; rangeFilters$: DataControlApi['filters$'];
ignoreParentSettings$: ControlGroupApi['ignoreParentSettings$']; ignoreParentSettings$: ControlGroupApi['ignoreParentSettings$'];
@ -53,7 +51,6 @@ export function hasNoResults$({
prevRequestAbortController = abortController; prevRequestAbortController = abortController;
return await hasNoResults({ return await hasNoResults({
abortSignal: abortController.signal, abortSignal: abortController.signal,
data,
dataView, dataView,
rangeFilter, rangeFilter,
...controlFetchContext, ...controlFetchContext,
@ -71,7 +68,6 @@ export function hasNoResults$({
async function hasNoResults({ async function hasNoResults({
abortSignal, abortSignal,
data,
dataView, dataView,
filters, filters,
query, query,
@ -79,14 +75,13 @@ async function hasNoResults({
timeRange, timeRange,
}: { }: {
abortSignal: AbortSignal; abortSignal: AbortSignal;
data: DataPublicPluginStart;
dataView: DataView; dataView: DataView;
filters?: Filter[]; filters?: Filter[];
query?: Query | AggregateQuery; query?: Query | AggregateQuery;
rangeFilter: Filter; rangeFilter: Filter;
timeRange?: TimeRange; timeRange?: TimeRange;
}): Promise<boolean> { }): Promise<boolean> {
const searchSource = await data.search.searchSource.create(); const searchSource = await dataService.search.searchSource.create();
searchSource.setField('size', 0); searchSource.setField('size', 0);
searchSource.setField('index', dataView); searchSource.setField('index', dataView);
// Tracking total hits accurately has a performance cost // Tracking total hits accurately has a performance cost
@ -97,7 +92,7 @@ async function hasNoResults({
const allFilters = filters ? [...filters] : []; const allFilters = filters ? [...filters] : [];
allFilters.push(rangeFilter); allFilters.push(rangeFilter);
if (timeRange) { if (timeRange) {
const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); const timeFilter = dataService.query.timefilter.timefilter.createFilter(dataView, timeRange);
if (timeFilter) allFilters.push(timeFilter); if (timeFilter) allFilters.push(timeFilter);
} }
if (allFilters.length) { if (allFilters.length) {

View file

@ -8,26 +8,24 @@
*/ */
import { estypes } from '@elastic/elasticsearch'; import { estypes } from '@elastic/elasticsearch';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing'; import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing';
import { combineLatest, lastValueFrom, Observable, of, startWith, switchMap, tap } from 'rxjs';
import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload'; import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload';
import { Observable, combineLatest, lastValueFrom, of, startWith, switchMap, tap } from 'rxjs';
import { dataService } from '../../../../services/kibana_services';
import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlGroupApi } from '../../../control_group/types'; import { ControlGroupApi } from '../../../control_group/types';
export function minMax$({ export function minMax$({
controlFetch$, controlFetch$,
controlGroupApi, controlGroupApi,
data,
dataViews$, dataViews$,
fieldName$, fieldName$,
setIsLoading, setIsLoading,
}: { }: {
controlFetch$: Observable<ControlFetchContext>; controlFetch$: Observable<ControlFetchContext>;
controlGroupApi: ControlGroupApi; controlGroupApi: ControlGroupApi;
data: DataPublicPluginStart;
dataViews$: PublishesDataViews['dataViews']; dataViews$: PublishesDataViews['dataViews'];
fieldName$: PublishingSubject<string>; fieldName$: PublishingSubject<string>;
setIsLoading: (isLoading: boolean) => void; setIsLoading: (isLoading: boolean) => void;
@ -60,7 +58,6 @@ export function minMax$({
prevRequestAbortController = abortController; prevRequestAbortController = abortController;
return await getMinMax({ return await getMinMax({
abortSignal: abortController.signal, abortSignal: abortController.signal,
data,
dataView, dataView,
field: dataViewField, field: dataViewField,
...controlFetchContext, ...controlFetchContext,
@ -77,7 +74,6 @@ export function minMax$({
export async function getMinMax({ export async function getMinMax({
abortSignal, abortSignal,
data,
dataView, dataView,
field, field,
filters, filters,
@ -85,20 +81,19 @@ export async function getMinMax({
timeRange, timeRange,
}: { }: {
abortSignal: AbortSignal; abortSignal: AbortSignal;
data: DataPublicPluginStart;
dataView: DataView; dataView: DataView;
field: DataViewField; field: DataViewField;
filters?: Filter[]; filters?: Filter[];
query?: Query | AggregateQuery; query?: Query | AggregateQuery;
timeRange?: TimeRange; timeRange?: TimeRange;
}): Promise<{ min: number | undefined; max: number | undefined }> { }): Promise<{ min: number | undefined; max: number | undefined }> {
const searchSource = await data.search.searchSource.create(); const searchSource = await dataService.search.searchSource.create();
searchSource.setField('size', 0); searchSource.setField('size', 0);
searchSource.setField('index', dataView); searchSource.setField('index', dataView);
const allFilters = filters ? [...filters] : []; const allFilters = filters ? [...filters] : [];
if (timeRange) { if (timeRange) {
const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); const timeFilter = dataService.query.timefilter.timefilter.createFilter(dataView, timeRange);
if (timeFilter) allFilters.push(timeFilter); if (timeFilter) allFilters.push(timeFilter);
} }
if (allFilters.length) { if (allFilters.length) {

View file

@ -7,22 +7,17 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import type { CoreSetup } from '@kbn/core/public';
import type { ControlsPluginStartDeps } from '../../../../types';
import { registerControlFactory } from '../../../control_factory_registry';
import { RANGE_SLIDER_CONTROL } from '../../../../../common'; import { RANGE_SLIDER_CONTROL } from '../../../../../common';
import { untilPluginStartServicesReady } from '../../../../services/kibana_services';
import { registerControlFactory } from '../../../control_factory_registry';
export function registerRangeSliderControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { export function registerRangeSliderControl() {
registerControlFactory(RANGE_SLIDER_CONTROL, async () => { registerControlFactory(RANGE_SLIDER_CONTROL, async () => {
const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ const [{ getRangesliderControlFactory }] = await Promise.all([
import('./get_range_slider_control_factory'), import('./get_range_slider_control_factory'),
coreSetup.getStartServices(), untilPluginStartServicesReady(),
]); ]);
return getRangesliderControlFactory({ return getRangesliderControlFactory();
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
}); });
} }

View file

@ -7,10 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { CoreStart } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewField } from '@kbn/data-views-plugin/common'; import { DataViewField } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { FieldFormatConvertFunction } from '@kbn/field-formats-plugin/common'; import { FieldFormatConvertFunction } from '@kbn/field-formats-plugin/common';
import { import {
HasEditCapabilities, HasEditCapabilities,
@ -62,12 +59,6 @@ export const isDataControlFactory = (
return typeof (factory as DataControlFactory).isFieldCompatible === 'function'; return typeof (factory as DataControlFactory).isFieldCompatible === 'function';
}; };
export interface DataControlServices {
core: CoreStart;
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
}
interface DataControlField { interface DataControlField {
field: DataViewField; field: DataViewField;
compatibleControlTypes: string[]; compatibleControlTypes: string[];

View file

@ -9,6 +9,7 @@
import { EuiRangeTick } from '@elastic/eui'; import { EuiRangeTick } from '@elastic/eui';
import { TimeRange } from '@kbn/es-query'; import { TimeRange } from '@kbn/es-query';
import { coreServices, dataService } from '../../../services/kibana_services';
import { import {
FROM_INDEX, FROM_INDEX,
getStepSize, getStepSize,
@ -17,7 +18,6 @@ import {
roundUpToNextStepSizeFactor, roundUpToNextStepSizeFactor,
TO_INDEX, TO_INDEX,
} from './time_utils'; } from './time_utils';
import { Services } from './types';
export interface TimeRangeMeta { export interface TimeRangeMeta {
format: string; format: string;
@ -29,12 +29,9 @@ export interface TimeRangeMeta {
timeRangeMin: number; timeRangeMin: number;
} }
export function getTimeRangeMeta( export function getTimeRangeMeta(timeRange: TimeRange | undefined): TimeRangeMeta {
timeRange: TimeRange | undefined, const nextBounds = timeRangeToBounds(timeRange ?? getDefaultTimeRange());
services: Services const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], getTimezone());
): TimeRangeMeta {
const nextBounds = timeRangeToBounds(timeRange ?? getDefaultTimeRange(services), services);
const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], getTimezone(services));
const { format, stepSize } = getStepSize(ticks); const { format, stepSize } = getStepSize(ticks);
return { return {
format, format,
@ -47,17 +44,17 @@ export function getTimeRangeMeta(
}; };
} }
export function getTimezone(services: Services) { export function getTimezone() {
return services.core.uiSettings.get('dateFormat:tz', 'Browser'); return coreServices.uiSettings.get('dateFormat:tz', 'Browser');
} }
function getDefaultTimeRange(services: Services) { function getDefaultTimeRange() {
const defaultTimeRange = services.core.uiSettings.get('timepicker:timeDefaults'); const defaultTimeRange = coreServices.uiSettings.get('timepicker:timeDefaults');
return defaultTimeRange ? defaultTimeRange : { from: 'now-15m', to: 'now' }; return defaultTimeRange ? defaultTimeRange : { from: 'now-15m', to: 'now' };
} }
function timeRangeToBounds(timeRange: TimeRange, services: Services): [number, number] { function timeRangeToBounds(timeRange: TimeRange): [number, number] {
const timeRangeBounds = services.data.query.timefilter.timefilter.calculateBounds(timeRange); const timeRangeBounds = dataService.query.timefilter.timefilter.calculateBounds(timeRange);
return timeRangeBounds.min === undefined || timeRangeBounds.max === undefined return timeRangeBounds.min === undefined || timeRangeBounds.max === undefined
? [Date.now() - 1000 * 60 * 15, Date.now()] ? [Date.now() - 1000 * 60 * 15, Date.now()]
: [timeRangeBounds.min.valueOf(), timeRangeBounds.max.valueOf()]; : [timeRangeBounds.min.valueOf(), timeRangeBounds.max.valueOf()];

View file

@ -7,14 +7,15 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { coreMock } from '@kbn/core/public/mocks'; import React from 'react';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { BehaviorSubject } from 'rxjs';
import dateMath from '@kbn/datemath'; import dateMath from '@kbn/datemath';
import { TimeRange } from '@kbn/es-query'; import { TimeRange } from '@kbn/es-query';
import { StateComparators } from '@kbn/presentation-publishing'; import { StateComparators } from '@kbn/presentation-publishing';
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { BehaviorSubject } from 'rxjs'; import { dataService } from '../../../services/kibana_services';
import { getMockedControlGroupApi } from '../mocks/control_mocks'; import { getMockedControlGroupApi } from '../mocks/control_mocks';
import { ControlApiRegistration } from '../types'; import { ControlApiRegistration } from '../types';
import { getTimesliderControlFactory } from './get_timeslider_control_factory'; import { getTimesliderControlFactory } from './get_timeslider_control_factory';
@ -28,18 +29,14 @@ describe('TimesliderControlApi', () => {
}; };
const controlGroupApi = getMockedControlGroupApi(dashboardApi); const controlGroupApi = getMockedControlGroupApi(dashboardApi);
const dataStartServiceMock = dataPluginMock.createStartContract(); dataService.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => {
dataStartServiceMock.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => {
const now = new Date(); const now = new Date();
return { return {
min: dateMath.parse(timeRange.from, { forceNow: now }), min: dateMath.parse(timeRange.from, { forceNow: now }),
max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: now }), max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: now }),
}; };
}; };
const factory = getTimesliderControlFactory({ const factory = getTimesliderControlFactory();
core: coreMock.createStart(),
data: dataStartServiceMock,
});
let comparators: StateComparators<TimesliderControlState> | undefined; let comparators: StateComparators<TimesliderControlState> | undefined;
function buildApiMock( function buildApiMock(
api: ControlApiRegistration<TimesliderControlApi>, api: ControlApiRegistration<TimesliderControlApi>,

View file

@ -36,22 +36,23 @@ import {
roundDownToNextStepSizeFactor, roundDownToNextStepSizeFactor,
roundUpToNextStepSizeFactor, roundUpToNextStepSizeFactor,
} from './time_utils'; } from './time_utils';
import { Services, Timeslice, TimesliderControlApi, TimesliderControlState } from './types'; import { Timeslice, TimesliderControlApi, TimesliderControlState } from './types';
const displayName = i18n.translate('controls.timesliderControl.displayName', { const displayName = i18n.translate('controls.timesliderControl.displayName', {
defaultMessage: 'Time slider', defaultMessage: 'Time slider',
}); });
export const getTimesliderControlFactory = ( export const getTimesliderControlFactory = (): ControlFactory<
services: Services TimesliderControlState,
): ControlFactory<TimesliderControlState, TimesliderControlApi> => { TimesliderControlApi
> => {
return { return {
type: TIME_SLIDER_CONTROL, type: TIME_SLIDER_CONTROL,
getIconType: () => 'search', getIconType: () => 'search',
getDisplayName: () => displayName, getDisplayName: () => displayName,
buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { buildControl: async (initialState, buildApi, uuid, controlGroupApi) => {
const { timeRangeMeta$, formatDate, cleanupTimeRangeSubscription } = const { timeRangeMeta$, formatDate, cleanupTimeRangeSubscription } =
initTimeRangeSubscription(controlGroupApi, services); initTimeRangeSubscription(controlGroupApi);
const timeslice$ = new BehaviorSubject<[number, number] | undefined>(undefined); const timeslice$ = new BehaviorSubject<[number, number] | undefined>(undefined);
const isAnchored$ = new BehaviorSubject<boolean | undefined>(initialState.isAnchored); const isAnchored$ = new BehaviorSubject<boolean | undefined>(initialState.isAnchored);
const isPopoverOpen$ = new BehaviorSubject(false); const isPopoverOpen$ = new BehaviorSubject(false);

View file

@ -14,26 +14,23 @@ import moment from 'moment';
import { BehaviorSubject, skip } from 'rxjs'; import { BehaviorSubject, skip } from 'rxjs';
import { getTimeRangeMeta, getTimezone, TimeRangeMeta } from './get_time_range_meta'; import { getTimeRangeMeta, getTimezone, TimeRangeMeta } from './get_time_range_meta';
import { getMomentTimezone } from './time_utils'; import { getMomentTimezone } from './time_utils';
import { Services } from './types';
export function initTimeRangeSubscription(controlGroupApi: unknown, services: Services) { export function initTimeRangeSubscription(controlGroupApi: unknown) {
const timeRange$ = const timeRange$ =
apiHasParentApi(controlGroupApi) && apiPublishesTimeRange(controlGroupApi.parentApi) apiHasParentApi(controlGroupApi) && apiPublishesTimeRange(controlGroupApi.parentApi)
? controlGroupApi.parentApi.timeRange$ ? controlGroupApi.parentApi.timeRange$
: new BehaviorSubject<TimeRange | undefined>(undefined); : new BehaviorSubject<TimeRange | undefined>(undefined);
const timeRangeMeta$ = new BehaviorSubject<TimeRangeMeta>( const timeRangeMeta$ = new BehaviorSubject<TimeRangeMeta>(getTimeRangeMeta(timeRange$.value));
getTimeRangeMeta(timeRange$.value, services)
);
const timeRangeSubscription = timeRange$.pipe(skip(1)).subscribe((timeRange) => { const timeRangeSubscription = timeRange$.pipe(skip(1)).subscribe((timeRange) => {
timeRangeMeta$.next(getTimeRangeMeta(timeRange, services)); timeRangeMeta$.next(getTimeRangeMeta(timeRange));
}); });
return { return {
timeRangeMeta$, timeRangeMeta$,
formatDate: (epoch: number) => { formatDate: (epoch: number) => {
return moment return moment
.tz(epoch, getMomentTimezone(getTimezone(services))) .tz(epoch, getMomentTimezone(getTimezone()))
.locale(i18n.getLocale()) .locale(i18n.getLocale())
.format(timeRangeMeta$.value.format); .format(timeRangeMeta$.value.format);
}, },

View file

@ -7,20 +7,16 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import type { CoreSetup } from '@kbn/core/public';
import type { ControlsPluginStartDeps } from '../../../types';
import { registerControlFactory } from '../../control_factory_registry';
import { TIME_SLIDER_CONTROL } from '../../../../common'; import { TIME_SLIDER_CONTROL } from '../../../../common';
import { untilPluginStartServicesReady } from '../../../services/kibana_services';
import { registerControlFactory } from '../../control_factory_registry';
export function registerTimeSliderControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { export function registerTimeSliderControl() {
registerControlFactory(TIME_SLIDER_CONTROL, async () => { registerControlFactory(TIME_SLIDER_CONTROL, async () => {
const [{ getTimesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ const [{ getTimesliderControlFactory }] = await Promise.all([
import('./get_timeslider_control_factory'), import('./get_timeslider_control_factory'),
coreSetup.getStartServices(), untilPluginStartServicesReady(),
]); ]);
return getTimesliderControlFactory({ return getTimesliderControlFactory();
core: coreStart,
data: depsStart.data,
});
}); });
} }

View file

@ -7,8 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
import { CoreStart } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { PublishesPanelTitle, PublishesTimeslice } from '@kbn/presentation-publishing'; import type { PublishesPanelTitle, PublishesTimeslice } from '@kbn/presentation-publishing';
import type { DefaultControlState } from '../../../../common'; import type { DefaultControlState } from '../../../../common';
import type { DefaultControlApi } from '../types'; import type { DefaultControlApi } from '../types';
@ -25,8 +23,3 @@ export interface TimesliderControlState extends DefaultControlState {
export type TimesliderControlApi = DefaultControlApi & export type TimesliderControlApi = DefaultControlApi &
Pick<PublishesPanelTitle, 'defaultPanelTitle'> & Pick<PublishesPanelTitle, 'defaultPanelTitle'> &
PublishesTimeslice; PublishesTimeslice;
export interface Services {
core: CoreStart;
data: DataPublicPluginStart;
}

View file

@ -40,15 +40,15 @@ export type DefaultControlApi = PublishesDataLoading &
HasType & HasType &
HasUniqueId & HasUniqueId &
HasParentApi<ControlGroupApi> & { HasParentApi<ControlGroupApi> & {
// Can not use HasSerializableState interface
// HasSerializableState types serializeState as function returning 'MaybePromise'
// Controls serializeState is sync
serializeState: () => SerializedPanelState<DefaultControlState>;
/** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */
setDataLoading: (loading: boolean) => void; setDataLoading: (loading: boolean) => void;
setBlockingError: (error: Error | undefined) => void; setBlockingError: (error: Error | undefined) => void;
grow: PublishingSubject<boolean | undefined>; grow: PublishingSubject<boolean | undefined>;
width: PublishingSubject<ControlWidth | undefined>; width: PublishingSubject<ControlWidth | undefined>;
// Can not use HasSerializableState interface
// HasSerializableState types serializeState as function returning 'MaybePromise'
// Controls serializeState is sync
serializeState: () => SerializedPanelState<DefaultControlState>;
}; };
export type ControlApiRegistration<ControlApi extends DefaultControlApi = DefaultControlApi> = Omit< export type ControlApiRegistration<ControlApi extends DefaultControlApi = DefaultControlApi> = Omit<
@ -62,7 +62,6 @@ export type ControlApiInitialization<ControlApi extends DefaultControlApi = Defa
'serializeState' | 'getTypeDisplayName' | 'clearSelections' 'serializeState' | 'getTypeDisplayName' | 'clearSelections'
>; >;
// TODO: Move this to the Control plugin's setup contract
export interface ControlFactory< export interface ControlFactory<
State extends DefaultControlState = DefaultControlState, State extends DefaultControlState = DefaultControlState,
ControlApi extends DefaultControlApi = DefaultControlApi ControlApi extends DefaultControlApi = DefaultControlApi

View file

@ -9,26 +9,22 @@
import React from 'react'; import React from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
import { Filter } from '@kbn/es-query'; import { Filter } from '@kbn/es-query';
import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing'; import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing';
import { act, render, waitFor } from '@testing-library/react'; import { act, render, waitFor } from '@testing-library/react';
import { ControlGroupRendererApi } from '.'; import { ControlGroupRendererApi } from '.';
import { CONTROL_GROUP_TYPE } from '../..';
import { getControlGroupEmbeddableFactory } from '../control_group/get_control_group_factory'; import { getControlGroupEmbeddableFactory } from '../control_group/get_control_group_factory';
import { ControlGroupRenderer, ControlGroupRendererProps } from './control_group_renderer'; import { ControlGroupRenderer, ControlGroupRendererProps } from './control_group_renderer';
import { CONTROL_GROUP_TYPE } from '../..';
type ParentApiType = PublishesUnifiedSearch & { type ParentApiType = PublishesUnifiedSearch & {
unifiedSearchFilters$?: PublishingSubject<Filter[] | undefined>; unifiedSearchFilters$?: PublishingSubject<Filter[] | undefined>;
}; };
describe('control group renderer', () => { describe('control group renderer', () => {
const core = coreMock.createStart(); const factory = getControlGroupEmbeddableFactory();
const dataViews = dataViewPluginMocks.createStartContract();
const factory = getControlGroupEmbeddableFactory({ core, dataViews });
const buildControlGroupSpy = jest.spyOn(factory, 'buildEmbeddable'); const buildControlGroupSpy = jest.spyOn(factory, 'buildEmbeddable');
const mountControlGroupRenderer = async ( const mountControlGroupRenderer = async (

View file

@ -1,46 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlFactory, DefaultControlApi } from '../../react_controls/controls/types';
import { ControlsServiceType } from './types';
export type ControlsServiceFactory = PluginServiceFactory<ControlsServiceType>;
export const controlsServiceFactory = () => getStubControlsService();
export const getStubControlsService = () => {
const controlsFactoriesMap: { [key: string]: ControlFactory<any, any> } = {};
const mockRegisterControlFactory = async <
State extends object = object,
ApiType extends DefaultControlApi = DefaultControlApi
>(
controlType: string,
getFactory: () => Promise<ControlFactory<State, ApiType>>
) => {
controlsFactoriesMap[controlType] = (await getFactory()) as ControlFactory<any, any>;
};
const mockGetControlFactory = <
State extends object = object,
ApiType extends DefaultControlApi = DefaultControlApi
>(
type: string
) => {
return controlsFactoriesMap[type] as ControlFactory<State, ApiType>;
};
const getAllControlTypes = () => Object.keys(controlsFactoriesMap);
return {
registerControlFactory: mockRegisterControlFactory,
getControlFactory: mockGetControlFactory,
getAllControlTypes,
};
};

View file

@ -1,24 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
getAllControlTypes,
getControlFactory,
registerControlFactory,
} from '../../react_controls/control_factory_registry';
import { ControlsServiceType } from './types';
export const controlsServiceFactory = () => controlsService;
// export controls service directly for use in plugin setup lifecycle
export const controlsService: ControlsServiceType = {
registerControlFactory,
getControlFactory,
getAllControlTypes,
};

View file

@ -1,23 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import {
getAllControlTypes,
getControlFactory,
registerControlFactory,
} from '../../react_controls/control_factory_registry';
export type ControlsServiceFactory = PluginServiceFactory<ControlsServiceType>;
export interface ControlsServiceType {
registerControlFactory: typeof registerControlFactory;
getControlFactory: typeof getControlFactory;
getAllControlTypes: typeof getAllControlTypes;
}

View file

@ -1,24 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { analyticsServiceMock, coreMock, themeServiceMock } from '@kbn/core/public/mocks';
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsCoreService } from './types';
export type CoreServiceFactory = PluginServiceFactory<ControlsCoreService>;
export const coreServiceFactory: CoreServiceFactory = () => {
const corePluginMock = coreMock.createStart();
return {
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
theme: themeServiceMock.createSetupContract(),
i18n: corePluginMock.i18n,
notifications: corePluginMock.notifications,
};
};

View file

@ -1,28 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsCoreService } from './types';
import { ControlsPluginStartDeps } from '../../types';
export type CoreServiceFactory = KibanaPluginServiceFactory<
ControlsCoreService,
ControlsPluginStartDeps
>;
export const coreServiceFactory: CoreServiceFactory = ({ coreStart }) => {
const { analytics, theme, i18n, notifications } = coreStart;
return {
analytics,
theme,
i18n,
notifications,
};
};

View file

@ -1,17 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { CoreStart } from '@kbn/core/public';
export interface ControlsCoreService {
analytics: CoreStart['analytics'];
i18n: CoreStart['i18n'];
theme: CoreStart['theme'];
notifications: CoreStart['notifications'];
}

View file

@ -1,31 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { of } from 'rxjs';
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { ControlsDataService } from './types';
export type DataServiceFactory = PluginServiceFactory<ControlsDataService>;
export const dataServiceFactory: DataServiceFactory = () => ({
query: {} as unknown as DataPublicPluginStart['query'],
searchSource: {
create: () => ({
setField: () => {},
fetch$: () =>
of({
rawResponse: { aggregations: { minAgg: { value: 0 }, maxAgg: { value: 1000 } } },
}),
}),
} as unknown as DataPublicPluginStart['search']['searchSource'],
timefilter: {
createFilter: () => {},
} as unknown as DataPublicPluginStart['query']['timefilter']['timefilter'],
fetchFieldRange: () => Promise.resolve({ min: 0, max: 100 }),
});

View file

@ -1,29 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStartDeps } from '../../types';
import { ControlsDataService } from './types';
export type DataServiceFactory = KibanaPluginServiceFactory<
ControlsDataService,
ControlsPluginStartDeps
>;
export const dataServiceFactory: DataServiceFactory = ({ startPlugins }) => {
const {
data: { query: queryPlugin, search },
} = startPlugins;
return {
query: queryPlugin,
searchSource: search.searchSource,
timefilter: queryPlugin.timefilter.timefilter,
};
};

View file

@ -1,15 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
export interface ControlsDataService {
query: DataPublicPluginStart['query'];
searchSource: DataPublicPluginStart['search']['searchSource'];
timefilter: DataPublicPluginStart['query']['timefilter']['timefilter'];
}

View file

@ -1,52 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DataView } from '@kbn/data-views-plugin/common';
import { ControlsDataViewsService } from './types';
export type DataViewsServiceFactory = PluginServiceFactory<ControlsDataViewsService>;
let currentDataView: DataView | undefined;
export const injectStorybookDataView = (dataView?: DataView) => (currentDataView = dataView);
export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({
get: ((dataViewId) =>
new Promise((resolve, reject) =>
setTimeout(() => {
if (!currentDataView) {
reject(
new Error(
'mock DataViews service currentDataView is undefined, call injectStorybookDataView to set'
)
);
} else if (currentDataView.id === dataViewId) {
resolve(currentDataView);
} else {
reject(
new Error(
`mock DataViews service currentDataView.id: ${currentDataView.id} does not match requested dataViewId: ${dataViewId}`
)
);
}
}, 100)
) as unknown) as DataViewsPublicPluginStart['get'],
getIdsWithTitle: (() =>
new Promise((resolve) =>
setTimeout(() => {
const idsWithTitle: Array<{ id: string | undefined; title: string }> = [];
if (currentDataView) {
idsWithTitle.push({ id: currentDataView.id, title: currentDataView.title });
}
resolve(idsWithTitle);
}, 100)
) as unknown) as DataViewsPublicPluginStart['getIdsWithTitle'],
getDefaultId: () => Promise.resolve(currentDataView?.id ?? null),
});

View file

@ -1,29 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStartDeps } from '../../types';
import { ControlsDataViewsService } from './types';
export type DataViewsServiceFactory = KibanaPluginServiceFactory<
ControlsDataViewsService,
ControlsPluginStartDeps
>;
export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }) => {
const {
dataViews: { get, getIdsWithTitle, getDefaultId },
} = startPlugins;
return {
get,
getDefaultId,
getIdsWithTitle,
};
};

View file

@ -1,16 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
export interface ControlsDataViewsService {
get: DataViewsPublicPluginStart['get'];
getDefaultId: DataViewsPublicPluginStart['getDefaultId'];
getIdsWithTitle: DataViewsPublicPluginStart['getIdsWithTitle'];
}

View file

@ -1,19 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
import { ControlsEmbeddableService } from './types';
export type EmbeddableServiceFactory = PluginServiceFactory<ControlsEmbeddableService>;
export const embeddableServiceFactory: EmbeddableServiceFactory = () => {
const { doStart } = embeddablePluginMock.createInstance();
const start = doStart();
return { getEmbeddableFactory: start.getEmbeddableFactory };
};

View file

@ -1,23 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsEmbeddableService } from './types';
import { ControlsPluginStartDeps } from '../../types';
export type EmbeddableServiceFactory = KibanaPluginServiceFactory<
ControlsEmbeddableService,
ControlsPluginStartDeps
>;
export const embeddableServiceFactory: EmbeddableServiceFactory = ({ startPlugins }) => {
return {
getEmbeddableFactory: startPlugins.embeddable.getEmbeddableFactory,
};
};

View file

@ -1,14 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
export interface ControlsEmbeddableService {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
}

View file

@ -1,19 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { HttpResponse } from '@kbn/core/public';
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsHTTPService } from './types';
type HttpServiceFactory = PluginServiceFactory<ControlsHTTPService>;
export const httpServiceFactory: HttpServiceFactory = () => ({
get: async () => ({} as unknown as HttpResponse),
fetch: async () => ({} as unknown as HttpResponse),
});

View file

@ -1,27 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsHTTPService } from './types';
import { ControlsPluginStartDeps } from '../../types';
export type HttpServiceFactory = KibanaPluginServiceFactory<
ControlsHTTPService,
ControlsPluginStartDeps
>;
export const httpServiceFactory: HttpServiceFactory = ({ coreStart }) => {
const {
http: { get, fetch },
} = coreStart;
return {
get,
fetch,
};
};

View file

@ -1,15 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { CoreSetup } from '@kbn/core/public';
export interface ControlsHTTPService {
get: CoreSetup['http']['get'];
fetch: CoreSetup['http']['fetch'];
}

View file

@ -1,10 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export { pluginServices } from './plugin_services';

View file

@ -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
* 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { BehaviorSubject } from 'rxjs';
import { CoreStart } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { ControlsPluginStartDeps } from '../types';
export let coreServices: CoreStart;
export let dataService: DataPublicPluginStart;
export let dataViewsService: DataViewsPublicPluginStart;
const servicesReady$ = new BehaviorSubject(false);
export const setKibanaServices = (kibanaCore: CoreStart, deps: ControlsPluginStartDeps) => {
coreServices = kibanaCore;
dataService = deps.data;
dataViewsService = deps.dataViews;
servicesReady$.next(true);
};
export const untilPluginStartServicesReady = () => {
if (servicesReady$.value) return Promise.resolve();
return new Promise<void>((resolve) => {
const subscription = servicesReady$.subscribe((isInitialized) => {
if (isInitialized) {
subscription.unsubscribe();
resolve();
}
});
});
};

View file

@ -0,0 +1,23 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { setKibanaServices } from './kibana_services';
export const setStubKibanaServices = () => {
setKibanaServices(coreMock.createStart(), {
data: dataPluginMock.createStartContract(),
dataViews: dataViewPluginMocks.createStartContract(),
uiActions: uiActionsPluginMock.createStartContract(),
});
};

View file

@ -1,33 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
MountPoint,
OverlayFlyoutOpenOptions,
OverlayModalConfirmOptions,
OverlayRef,
} from '@kbn/core/public';
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsOverlaysService } from './types';
type OverlaysServiceFactory = PluginServiceFactory<ControlsOverlaysService>;
class StubRef implements OverlayRef {
public readonly onClose: Promise<void> = Promise.resolve();
public close(): Promise<void> {
return this.onClose;
}
}
export const overlaysServiceFactory: OverlaysServiceFactory = () => ({
openFlyout: (mount: MountPoint, options?: OverlayFlyoutOpenOptions) => new StubRef(),
openConfirm: (message: MountPoint | string, options?: OverlayModalConfirmOptions) =>
Promise.resolve(true),
});

View file

@ -1,27 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStartDeps } from '../../types';
import { ControlsOverlaysService } from './types';
export type OverlaysServiceFactory = KibanaPluginServiceFactory<
ControlsOverlaysService,
ControlsPluginStartDeps
>;
export const overlaysServiceFactory: OverlaysServiceFactory = ({ coreStart }) => {
const {
overlays: { openFlyout, openConfirm },
} = coreStart;
return {
openFlyout,
openConfirm,
};
};

View file

@ -1,20 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
MountPoint,
OverlayFlyoutOpenOptions,
OverlayModalConfirmOptions,
OverlayRef,
} from '@kbn/core/public';
export interface ControlsOverlaysService {
openFlyout(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef;
openConfirm(message: MountPoint | string, options?: OverlayModalConfirmOptions): Promise<boolean>;
}

View file

@ -1,54 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
PluginServiceProvider,
PluginServiceProviders,
PluginServiceRegistry,
PluginServices,
} from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStart } from '../types';
import { ControlsServices } from './types';
import { controlsServiceFactory } from './controls/controls.stub';
import { coreServiceFactory } from './core/core.stub';
import { dataServiceFactory } from './data/data.stub';
import { dataViewsServiceFactory } from './data_views/data_views.stub';
import { embeddableServiceFactory } from './embeddable/embeddable.stub';
import { httpServiceFactory } from './http/http.stub';
import { overlaysServiceFactory } from './overlays/overlays.stub';
import { settingsServiceFactory } from './settings/settings.stub';
import { unifiedSearchServiceFactory } from './unified_search/unified_search.stub';
import { storageServiceFactory } from './storage/storage_service.stub';
export const providers: PluginServiceProviders<ControlsServices> = {
embeddable: new PluginServiceProvider(embeddableServiceFactory),
controls: new PluginServiceProvider(controlsServiceFactory),
data: new PluginServiceProvider(dataServiceFactory),
dataViews: new PluginServiceProvider(dataViewsServiceFactory),
http: new PluginServiceProvider(httpServiceFactory),
overlays: new PluginServiceProvider(overlaysServiceFactory),
settings: new PluginServiceProvider(settingsServiceFactory),
core: new PluginServiceProvider(coreServiceFactory),
storage: new PluginServiceProvider(storageServiceFactory),
unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory),
};
export const pluginServices = new PluginServices<ControlsServices>();
export const registry = new PluginServiceRegistry<ControlsServices>(providers);
export const getStubPluginServices = (): ControlsPluginStart => {
pluginServices.setRegistry(registry.start({}));
return {
getControlFactory: pluginServices.getServices().controls.getControlFactory,
getAllControlTypes: pluginServices.getServices().controls.getAllControlTypes,
};
};

View file

@ -1,52 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
KibanaPluginServiceParams,
PluginServiceProvider,
PluginServiceProviders,
PluginServiceRegistry,
PluginServices,
} from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStartDeps } from '../types';
import { ControlsServices } from './types';
import { controlsServiceFactory } from './controls/controls_service';
import { coreServiceFactory } from './core/core_service';
import { dataServiceFactory } from './data/data_service';
import { dataViewsServiceFactory } from './data_views/data_views_service';
import { embeddableServiceFactory } from './embeddable/embeddable_service';
import { httpServiceFactory } from './http/http_service';
import { overlaysServiceFactory } from './overlays/overlays_service';
import { settingsServiceFactory } from './settings/settings_service';
import { controlsStorageServiceFactory } from './storage/storage_service';
import { unifiedSearchServiceFactory } from './unified_search/unified_search_service';
export const providers: PluginServiceProviders<
ControlsServices,
KibanaPluginServiceParams<ControlsPluginStartDeps>
> = {
controls: new PluginServiceProvider(controlsServiceFactory),
data: new PluginServiceProvider(dataServiceFactory),
dataViews: new PluginServiceProvider(dataViewsServiceFactory),
embeddable: new PluginServiceProvider(embeddableServiceFactory),
http: new PluginServiceProvider(httpServiceFactory),
overlays: new PluginServiceProvider(overlaysServiceFactory),
settings: new PluginServiceProvider(settingsServiceFactory),
storage: new PluginServiceProvider(controlsStorageServiceFactory),
core: new PluginServiceProvider(coreServiceFactory),
unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory),
};
export const pluginServices = new PluginServices<ControlsServices>();
export const registry = new PluginServiceRegistry<
ControlsServices,
KibanaPluginServiceParams<ControlsPluginStartDeps>
>(providers);

View file

@ -1,18 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsSettingsService } from './types';
export type SettingsServiceFactory = PluginServiceFactory<ControlsSettingsService>;
export const settingsServiceFactory: SettingsServiceFactory = () => ({
getTimezone: () => 'Browser',
getDateFormat: () => 'MMM D, YYYY @ HH:mm:ss.SSS',
getDefaultTimeRange: () => ({ from: 'now-15m', to: 'now' }),
});

View file

@ -1,32 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsSettingsService } from './types';
import { ControlsPluginStartDeps } from '../../types';
export type SettingsServiceFactory = KibanaPluginServiceFactory<
ControlsSettingsService,
ControlsPluginStartDeps
>;
export const settingsServiceFactory: SettingsServiceFactory = ({ coreStart }) => {
return {
getDateFormat: () => {
return coreStart.uiSettings.get('dateFormat', 'MMM D, YYYY @ HH:mm:ss.SSS');
},
getTimezone: () => {
return coreStart.uiSettings.get('dateFormat:tz', 'Browser');
},
getDefaultTimeRange: () => {
const defaultTimeRange = coreStart.uiSettings.get('timepicker:timeDefaults');
return defaultTimeRange ? defaultTimeRange : { from: 'now-15m', to: 'now' };
},
};
};

View file

@ -1,16 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import type { TimeRange } from '@kbn/es-query';
export interface ControlsSettingsService {
getTimezone: () => string;
getDateFormat: () => string;
getDefaultTimeRange: () => TimeRange;
}

View file

@ -1,20 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsStorageService } from './types';
type StorageServiceFactory = PluginServiceFactory<ControlsStorageService>;
export const storageServiceFactory: StorageServiceFactory = () => {
return {
getShowInvalidSelectionWarning: () => false,
setShowInvalidSelectionWarning: (value: boolean) => null,
};
};

View file

@ -1,31 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { ControlsStorageService } from './types';
const STORAGE_KEY = 'controls:showInvalidSelectionWarning';
class StorageService implements ControlsStorageService {
private storage: Storage;
constructor() {
this.storage = new Storage(localStorage);
}
getShowInvalidSelectionWarning = () => {
return this.storage.get(STORAGE_KEY);
};
setShowInvalidSelectionWarning = (value: boolean) => {
this.storage.set(STORAGE_KEY, value);
};
}
export const controlsStorageServiceFactory = () => new StorageService();

View file

@ -1,13 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export interface ControlsStorageService {
getShowInvalidSelectionWarning: () => boolean;
setShowInvalidSelectionWarning: (value: boolean) => void;
}

View file

@ -1,35 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ControlsServiceType } from './controls/types';
import { ControlsCoreService } from './core/types';
import { ControlsDataService } from './data/types';
import { ControlsDataViewsService } from './data_views/types';
import { ControlsEmbeddableService } from './embeddable/types';
import { ControlsHTTPService } from './http/types';
import { ControlsOverlaysService } from './overlays/types';
import { ControlsSettingsService } from './settings/types';
import { ControlsStorageService } from './storage/types';
import { ControlsUnifiedSearchService } from './unified_search/types';
export interface ControlsServices {
// dependency services
dataViews: ControlsDataViewsService;
overlays: ControlsOverlaysService;
embeddable: ControlsEmbeddableService;
data: ControlsDataService;
unifiedSearch: ControlsUnifiedSearchService;
http: ControlsHTTPService;
settings: ControlsSettingsService;
core: ControlsCoreService;
// controls plugin's own services
controls: ControlsServiceType;
storage: ControlsStorageService;
}

View file

@ -1,14 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
export interface ControlsUnifiedSearchService {
autocomplete: UnifiedSearchPublicPluginStart['autocomplete'];
}

View file

@ -1,27 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { DataViewField } from '@kbn/data-views-plugin/common';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { ControlsUnifiedSearchService } from './types';
let valueSuggestionMethod = ({ field, query }: { field: DataViewField; query: string }) =>
Promise.resolve(['storybook', 'default', 'values']);
export const replaceValueSuggestionMethod = (
newMethod: ({ field, query }: { field: DataViewField; query: string }) => Promise<string[]>
) => (valueSuggestionMethod = newMethod);
export type UnifiedSearchServiceFactory = PluginServiceFactory<ControlsUnifiedSearchService>;
export const unifiedSearchServiceFactory: UnifiedSearchServiceFactory = () => ({
autocomplete: {
getValueSuggestions: valueSuggestionMethod,
} as unknown as UnifiedSearchPublicPluginStart['autocomplete'],
});

View file

@ -1,27 +0,0 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ControlsPluginStartDeps } from '../../types';
import { ControlsUnifiedSearchService } from './types';
export type UnifiedSearchServiceFactory = KibanaPluginServiceFactory<
ControlsUnifiedSearchService,
ControlsPluginStartDeps
>;
export const unifiedSearchServiceFactory: UnifiedSearchServiceFactory = ({ startPlugins }) => {
const {
unifiedSearch: { autocomplete },
} = startPlugins;
return {
autocomplete,
};
};

View file

@ -9,11 +9,8 @@
import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { ControlsServiceType } from './services/controls/types';
export interface CanClearSelections { export interface CanClearSelections {
clearSelections: () => void; clearSelections: () => void;
@ -26,22 +23,11 @@ export const isClearableControl = (control: unknown): control is CanClearSelecti
/** /**
* Plugin types * Plugin types
*/ */
export interface ControlsPluginSetup {
registerControlFactory: ControlsServiceType['registerControlFactory'];
}
export interface ControlsPluginStart {
getControlFactory: ControlsServiceType['getControlFactory'];
getAllControlTypes: ControlsServiceType['getAllControlTypes'];
}
export interface ControlsPluginSetupDeps { export interface ControlsPluginSetupDeps {
embeddable: EmbeddableSetup; embeddable: EmbeddableSetup;
} }
export interface ControlsPluginStartDeps { export interface ControlsPluginStartDeps {
uiActions: UiActionsStart; uiActions: UiActionsStart;
embeddable: EmbeddableStart;
data: DataPublicPluginStart; data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart; dataViews: DataViewsPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
} }

View file

@ -10,7 +10,7 @@
import { SerializableRecord } from '@kbn/utility-types'; import { SerializableRecord } from '@kbn/utility-types';
import { import {
DEFAULT_CONTROL_STYLE, DEFAULT_CONTROL_LABEL_POSITION,
type ControlGroupRuntimeState, type ControlGroupRuntimeState,
type ControlGroupSerializedState, type ControlGroupSerializedState,
type ControlPanelState, type ControlPanelState,
@ -19,7 +19,7 @@ import {
export const getDefaultControlGroupState = (): SerializableControlGroupState => ({ export const getDefaultControlGroupState = (): SerializableControlGroupState => ({
panels: {}, panels: {},
labelPosition: DEFAULT_CONTROL_STYLE, labelPosition: DEFAULT_CONTROL_LABEL_POSITION,
chainingSystem: 'HIERARCHICAL', chainingSystem: 'HIERARCHICAL',
autoApplySelections: true, autoApplySelections: true,
ignoreParentSettings: { ignoreParentSettings: {

View file

@ -15,7 +15,7 @@ export const setupOptionsListClusterSettingsRoute = ({ http }: CoreSetup) => {
router.versioned router.versioned
.get({ .get({
access: 'internal', access: 'internal',
path: '/internal/controls/optionsList/getExpensiveQueriesSetting', path: '/internal/controls/getExpensiveQueriesSetting',
}) })
.addVersion( .addVersion(
{ {

View file

@ -35,7 +35,6 @@
"@kbn/presentation-containers", "@kbn/presentation-containers",
"@kbn/presentation-publishing", "@kbn/presentation-publishing",
"@kbn/content-management-utils", "@kbn/content-management-utils",
"@kbn/core-lifecycle-browser",
"@kbn/field-formats-plugin", "@kbn/field-formats-plugin",
"@kbn/presentation-panel-plugin", "@kbn/presentation-panel-plugin",
"@kbn/shared-ux-utility" "@kbn/shared-ux-utility"