mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[controls] Edit and save example (#147897)
Part of https://github.com/elastic/kibana/issues/145428 PR does the following: * Removes ControlsCallout * Cleans up ControlGroupContainer API to avoid leaking Dashboard implementation details * Removes getCreateControlButton method * Removes getCreateTimeSliderControlButton * Removes getToolbarButtons * Adds openAddDataControlFlyout * Add Edit and save example <img width="600" alt="Screen Shot 2022-12-21 at 9 29 21 AM" src="https://user-images.githubusercontent.com/373691/208928858-94984880-3fdb-45f4-bb2a-a086bfc440d0.png"> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
750e5e0e95
commit
36a3d6915c
24 changed files with 403 additions and 826 deletions
|
@ -14,6 +14,7 @@ import { AppMountParameters } from '@kbn/core/public';
|
|||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { ControlsExampleStartDeps } from './plugin';
|
||||
import { BasicReduxExample } from './basic_redux_example';
|
||||
import { EditExample } from './edit_example';
|
||||
import { SearchExample } from './search_example';
|
||||
|
||||
export const renderApp = async (
|
||||
|
@ -26,6 +27,8 @@ export const renderApp = async (
|
|||
<>
|
||||
<SearchExample dataView={dataViews[0]} navigation={navigation} data={data} />
|
||||
<EuiSpacer size="xl" />
|
||||
<EditExample />
|
||||
<EuiSpacer size="xl" />
|
||||
<BasicReduxExample dataViewId={dataViews[0].id!} />
|
||||
</>
|
||||
) : (
|
||||
|
|
122
examples/controls_example/public/edit_example.tsx
Normal file
122
examples/controls_example/public/edit_example.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingContent,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { LazyControlGroupRenderer, ControlGroupContainer } from '@kbn/controls-plugin/public';
|
||||
import { withSuspense } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
const ControlGroupRenderer = withSuspense(LazyControlGroupRenderer);
|
||||
|
||||
const INPUT_KEY = 'kbnControls:saveExample:input';
|
||||
|
||||
export const EditExample = () => {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [controlGroup, setControlGroup] = useState<ControlGroupContainer>();
|
||||
|
||||
async function onSave() {
|
||||
setIsSaving(true);
|
||||
|
||||
localStorage.setItem(INPUT_KEY, JSON.stringify(controlGroup!.getInput()));
|
||||
|
||||
// simulated async save await
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
||||
async function onLoad() {
|
||||
setIsLoading(true);
|
||||
|
||||
// simulated async load await
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
let input = {};
|
||||
const inputAsString = localStorage.getItem(INPUT_KEY);
|
||||
if (inputAsString) {
|
||||
try {
|
||||
input = JSON.parse(inputAsString);
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
return input;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle>
|
||||
<h2>Edit and save example</h2>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<p>Customize controls and persist state to local storage.</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPanel hasBorder={true}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
iconType="plusInCircle"
|
||||
isDisabled={controlGroup === undefined}
|
||||
onClick={() => {
|
||||
controlGroup!.openAddDataControlFlyout();
|
||||
}}
|
||||
>
|
||||
Add control
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
isDisabled={controlGroup === undefined || isSaving}
|
||||
fill
|
||||
onClick={onSave}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiLoadingContent lines={1} />
|
||||
</>
|
||||
) : null}
|
||||
<ControlGroupRenderer
|
||||
getInitialInput={async (initialInput, builder) => {
|
||||
const persistedInput = await onLoad();
|
||||
return {
|
||||
...initialInput,
|
||||
...persistedInput,
|
||||
viewMode: ViewMode.EDIT,
|
||||
};
|
||||
}}
|
||||
onLoadComplete={async (newControlGroup) => {
|
||||
setControlGroup(newControlGroup);
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -9,10 +9,6 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ControlGroupStrings = {
|
||||
getControlButtonTitle: () =>
|
||||
i18n.translate('controls.controlGroup.toolbarButtonTitle', {
|
||||
defaultMessage: 'Controls',
|
||||
}),
|
||||
emptyState: {
|
||||
getBadge: () =>
|
||||
i18n.translate('controls.controlGroup.emptyState.badgeText', {
|
||||
|
|
|
@ -1,164 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiContextMenuItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { OverlayRef } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { pluginServices } from '../../services';
|
||||
import { ControlEditor } from './control_editor';
|
||||
import { ControlGroupStrings } from '../control_group_strings';
|
||||
import { ControlWidth, ControlInput, IEditableControlFactory, DataControlInput } from '../../types';
|
||||
import {
|
||||
DEFAULT_CONTROL_WIDTH,
|
||||
DEFAULT_CONTROL_GROW,
|
||||
} from '../../../common/control_group/control_group_constants';
|
||||
import { setFlyoutRef } from '../embeddable/control_group_container';
|
||||
|
||||
export type CreateControlButtonTypes = 'toolbar' | 'callout';
|
||||
export interface CreateControlButtonProps {
|
||||
defaultControlWidth?: ControlWidth;
|
||||
defaultControlGrow?: boolean;
|
||||
updateDefaultWidth: (defaultControlWidth: ControlWidth) => void;
|
||||
updateDefaultGrow: (defaultControlGrow: boolean) => void;
|
||||
addNewEmbeddable: (type: string, input: Omit<ControlInput, 'id'>) => void;
|
||||
setLastUsedDataViewId?: (newDataViewId: string) => void;
|
||||
getRelevantDataViewId?: () => string | undefined;
|
||||
buttonType: CreateControlButtonTypes;
|
||||
closePopover?: () => void;
|
||||
}
|
||||
|
||||
interface CreateControlResult {
|
||||
type: string;
|
||||
controlInput: Omit<ControlInput, 'id'>;
|
||||
}
|
||||
|
||||
export const CreateControlButton = ({
|
||||
buttonType,
|
||||
defaultControlWidth,
|
||||
defaultControlGrow,
|
||||
addNewEmbeddable,
|
||||
closePopover,
|
||||
getRelevantDataViewId,
|
||||
setLastUsedDataViewId,
|
||||
updateDefaultWidth,
|
||||
updateDefaultGrow,
|
||||
}: CreateControlButtonProps) => {
|
||||
// Controls Services Context
|
||||
const {
|
||||
overlays: { openFlyout, openConfirm },
|
||||
controls: { getControlTypes, getControlFactory },
|
||||
theme: { theme$ },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const createNewControl = async () => {
|
||||
const ControlsServicesProvider = pluginServices.getContextProvider();
|
||||
|
||||
const initialInputPromise = new Promise<CreateControlResult>((resolve, reject) => {
|
||||
let inputToReturn: Partial<DataControlInput> = {};
|
||||
|
||||
const onCancel = (ref: OverlayRef) => {
|
||||
if (Object.keys(inputToReturn).length === 0) {
|
||||
reject();
|
||||
ref.close();
|
||||
return;
|
||||
}
|
||||
openConfirm(ControlGroupStrings.management.discardNewControl.getSubtitle(), {
|
||||
confirmButtonText: ControlGroupStrings.management.discardNewControl.getConfirm(),
|
||||
cancelButtonText: ControlGroupStrings.management.discardNewControl.getCancel(),
|
||||
title: ControlGroupStrings.management.discardNewControl.getTitle(),
|
||||
buttonColor: 'danger',
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
reject();
|
||||
ref.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = (ref: OverlayRef, type?: string) => {
|
||||
if (!type) {
|
||||
reject();
|
||||
ref.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const factory = getControlFactory(type) as IEditableControlFactory;
|
||||
if (factory.presaveTransformFunction) {
|
||||
inputToReturn = factory.presaveTransformFunction(inputToReturn);
|
||||
}
|
||||
resolve({ type, controlInput: inputToReturn });
|
||||
ref.close();
|
||||
};
|
||||
|
||||
const flyoutInstance = openFlyout(
|
||||
toMountPoint(
|
||||
<ControlsServicesProvider>
|
||||
<ControlEditor
|
||||
setLastUsedDataViewId={setLastUsedDataViewId}
|
||||
getRelevantDataViewId={getRelevantDataViewId}
|
||||
isCreate={true}
|
||||
width={defaultControlWidth ?? DEFAULT_CONTROL_WIDTH}
|
||||
grow={defaultControlGrow ?? DEFAULT_CONTROL_GROW}
|
||||
updateTitle={(newTitle) => (inputToReturn.title = newTitle)}
|
||||
updateWidth={updateDefaultWidth}
|
||||
updateGrow={updateDefaultGrow}
|
||||
onSave={(type) => onSave(flyoutInstance, type)}
|
||||
onCancel={() => onCancel(flyoutInstance)}
|
||||
onTypeEditorChange={(partialInput) =>
|
||||
(inputToReturn = { ...inputToReturn, ...partialInput })
|
||||
}
|
||||
/>
|
||||
</ControlsServicesProvider>,
|
||||
{ theme$ }
|
||||
),
|
||||
{
|
||||
'aria-label': ControlGroupStrings.manageControl.getFlyoutCreateTitle(),
|
||||
outsideClickCloses: false,
|
||||
onClose: (flyout) => {
|
||||
onCancel(flyout);
|
||||
setFlyoutRef(undefined);
|
||||
},
|
||||
}
|
||||
);
|
||||
setFlyoutRef(flyoutInstance);
|
||||
});
|
||||
|
||||
initialInputPromise.then(
|
||||
async (promise) => {
|
||||
await addNewEmbeddable(promise.type, promise.controlInput);
|
||||
},
|
||||
() => {} // swallow promise rejection because it can be part of normal flow
|
||||
);
|
||||
};
|
||||
|
||||
if (getControlTypes().length === 0) return null;
|
||||
|
||||
const commonButtonProps = {
|
||||
key: 'addControl',
|
||||
onClick: () => {
|
||||
createNewControl();
|
||||
if (closePopover) {
|
||||
closePopover();
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'controls-create-button',
|
||||
'aria-label': ControlGroupStrings.management.getManageButtonTitle(),
|
||||
};
|
||||
|
||||
return buttonType === 'callout' ? (
|
||||
<EuiButton {...commonButtonProps} color="primary" size="s">
|
||||
{ControlGroupStrings.emptyState.getAddControlButtonTitle()}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiContextMenuItem {...commonButtonProps} icon="plusInCircle">
|
||||
{ControlGroupStrings.emptyState.getAddControlButtonTitle()}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
};
|
|
@ -1,48 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { EuiContextMenuItem } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
onCreate: () => void;
|
||||
closePopover?: () => void;
|
||||
hasTimeSliderControl: boolean;
|
||||
}
|
||||
|
||||
export const CreateTimeSliderControlButton = ({
|
||||
onCreate,
|
||||
closePopover,
|
||||
hasTimeSliderControl,
|
||||
}: Props) => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
icon="plusInCircle"
|
||||
onClick={() => {
|
||||
onCreate();
|
||||
if (closePopover) {
|
||||
closePopover();
|
||||
}
|
||||
}}
|
||||
data-test-subj="controls-create-timeslider-button"
|
||||
disabled={hasTimeSliderControl}
|
||||
toolTipContent={
|
||||
hasTimeSliderControl
|
||||
? i18n.translate('controls.controlGroup.onlyOneTimeSliderControlMsg', {
|
||||
defaultMessage: 'Control group already contains time slider control.',
|
||||
})
|
||||
: null
|
||||
}
|
||||
>
|
||||
{i18n.translate('controls.controlGroup.addTimeSliderControlButtonTitle', {
|
||||
defaultMessage: 'Add time slider control',
|
||||
})}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import type {
|
||||
AddDataControlProps,
|
||||
AddOptionsListControlProps,
|
||||
AddRangeSliderControlProps,
|
||||
} from '../control_group_input_builder';
|
||||
import { ControlGroupStrings } from '../control_group_strings';
|
||||
import { ControlGroupContainer, setFlyoutRef } from '../embeddable/control_group_container';
|
||||
import { pluginServices } from '../../services';
|
||||
import { ControlEditor } from './control_editor';
|
||||
import { DataControlInput, OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '../..';
|
||||
import { IEditableControlFactory } from '../../types';
|
||||
import {
|
||||
DEFAULT_CONTROL_GROW,
|
||||
DEFAULT_CONTROL_WIDTH,
|
||||
} from '../../../common/control_group/control_group_constants';
|
||||
|
||||
export function openAddDataControlFlyout(this: ControlGroupContainer) {
|
||||
const {
|
||||
overlays: { openFlyout, openConfirm },
|
||||
controls: { getControlFactory },
|
||||
theme: { theme$ },
|
||||
} = pluginServices.getServices();
|
||||
const ControlsServicesProvider = pluginServices.getContextProvider();
|
||||
|
||||
let controlInput: Partial<DataControlInput> = {};
|
||||
const onCancel = () => {
|
||||
if (Object.keys(controlInput).length === 0) {
|
||||
this.closeAllFlyouts();
|
||||
return;
|
||||
}
|
||||
|
||||
openConfirm(ControlGroupStrings.management.discardNewControl.getSubtitle(), {
|
||||
confirmButtonText: ControlGroupStrings.management.discardNewControl.getConfirm(),
|
||||
cancelButtonText: ControlGroupStrings.management.discardNewControl.getCancel(),
|
||||
title: ControlGroupStrings.management.discardNewControl.getTitle(),
|
||||
buttonColor: 'danger',
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
this.closeAllFlyouts();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const flyoutInstance = openFlyout(
|
||||
toMountPoint(
|
||||
<ControlsServicesProvider>
|
||||
<ControlEditor
|
||||
setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)}
|
||||
getRelevantDataViewId={this.getMostRelevantDataViewId}
|
||||
isCreate={true}
|
||||
width={this.getInput().defaultControlWidth ?? DEFAULT_CONTROL_WIDTH}
|
||||
grow={this.getInput().defaultControlGrow ?? DEFAULT_CONTROL_GROW}
|
||||
updateTitle={(newTitle) => (controlInput.title = newTitle)}
|
||||
updateWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })}
|
||||
updateGrow={(defaultControlGrow: boolean) => this.updateInput({ defaultControlGrow })}
|
||||
onSave={(type) => {
|
||||
this.closeAllFlyouts();
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const factory = getControlFactory(type) as IEditableControlFactory;
|
||||
if (factory.presaveTransformFunction) {
|
||||
controlInput = factory.presaveTransformFunction(controlInput);
|
||||
}
|
||||
|
||||
if (type === OPTIONS_LIST_CONTROL) {
|
||||
this.addOptionsListControl(controlInput as AddOptionsListControlProps);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === RANGE_SLIDER_CONTROL) {
|
||||
this.addRangeSliderControl(controlInput as AddRangeSliderControlProps);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addDataControlFromField(controlInput as AddDataControlProps);
|
||||
}}
|
||||
onCancel={onCancel}
|
||||
onTypeEditorChange={(partialInput) =>
|
||||
(controlInput = { ...controlInput, ...partialInput })
|
||||
}
|
||||
/>
|
||||
</ControlsServicesProvider>,
|
||||
{ theme$ }
|
||||
),
|
||||
{
|
||||
'aria-label': ControlGroupStrings.manageControl.getFlyoutCreateTitle(),
|
||||
outsideClickCloses: false,
|
||||
onClose: () => {
|
||||
onCancel();
|
||||
},
|
||||
}
|
||||
);
|
||||
setFlyoutRef(flyoutInstance);
|
||||
}
|
|
@ -12,13 +12,8 @@ import ReactDOM from 'react-dom';
|
|||
import { compareFilters, COMPARE_ALL_OPTIONS, Filter, uniqFilters } from '@kbn/es-query';
|
||||
import { BehaviorSubject, merge, Subject, Subscription } from 'rxjs';
|
||||
import _ from 'lodash';
|
||||
import { EuiContextMenuPanel } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
ReduxEmbeddablePackage,
|
||||
ReduxEmbeddableTools,
|
||||
SolutionToolbarPopover,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
import { ReduxEmbeddablePackage, ReduxEmbeddableTools } from '@kbn/presentation-util-plugin/public';
|
||||
import { OverlayRef } from '@kbn/core/public';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { Container, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
|
@ -36,14 +31,11 @@ import {
|
|||
controlOrdersAreEqual,
|
||||
} from './control_group_chaining_system';
|
||||
import { pluginServices } from '../../services';
|
||||
import { ControlGroupStrings } from '../control_group_strings';
|
||||
import { openAddDataControlFlyout } from '../editor/open_add_data_control_flyout';
|
||||
import { EditControlGroup } from '../editor/edit_control_group';
|
||||
import { ControlGroup } from '../component/control_group_component';
|
||||
import { controlGroupReducers } from '../state/control_group_reducers';
|
||||
import { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL, TIME_SLIDER_CONTROL } from '../..';
|
||||
import { ControlEmbeddable, ControlInput, ControlOutput } from '../../types';
|
||||
import { CreateControlButton, CreateControlButtonTypes } from '../editor/create_control';
|
||||
import { CreateTimeSliderControlButton } from '../editor/create_time_slider_control';
|
||||
import { getNextPanelOrder } from './control_group_helpers';
|
||||
import type {
|
||||
AddDataControlProps,
|
||||
|
@ -127,68 +119,9 @@ export class ControlGroupContainer extends Container<
|
|||
return this.createAndSaveEmbeddable(panelState.type, panelState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a button that allows controls to be created externally using the embeddable
|
||||
* @param buttonType Controls the button styling
|
||||
* @param closePopover Closes the create control menu popover when flyout opens - only necessary if `buttonType === 'toolbar'`
|
||||
* @return If `buttonType == 'toolbar'`, returns `EuiContextMenuPanel` with input control types as items.
|
||||
* Otherwise, if `buttonType == 'callout'` returns `EuiButton` with popover containing input control types.
|
||||
*/
|
||||
public getCreateControlButton = (
|
||||
buttonType: CreateControlButtonTypes,
|
||||
closePopover?: () => void
|
||||
) => {
|
||||
const ControlsServicesProvider = pluginServices.getContextProvider();
|
||||
public openAddDataControlFlyout = openAddDataControlFlyout;
|
||||
|
||||
return (
|
||||
<ControlsServicesProvider>
|
||||
<CreateControlButton
|
||||
buttonType={buttonType}
|
||||
defaultControlWidth={this.getInput().defaultControlWidth}
|
||||
defaultControlGrow={this.getInput().defaultControlGrow}
|
||||
updateDefaultWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })}
|
||||
updateDefaultGrow={(defaultControlGrow: boolean) =>
|
||||
this.updateInput({ defaultControlGrow })
|
||||
}
|
||||
addNewEmbeddable={(type, input) => {
|
||||
if (type === OPTIONS_LIST_CONTROL) {
|
||||
this.addOptionsListControl(input as AddOptionsListControlProps);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === RANGE_SLIDER_CONTROL) {
|
||||
this.addRangeSliderControl(input as AddRangeSliderControlProps);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addDataControlFromField(input as AddDataControlProps);
|
||||
}}
|
||||
closePopover={closePopover}
|
||||
getRelevantDataViewId={() => this.getMostRelevantDataViewId()}
|
||||
setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)}
|
||||
/>
|
||||
</ControlsServicesProvider>
|
||||
);
|
||||
};
|
||||
|
||||
public getCreateTimeSliderControlButton = (closePopover?: () => void) => {
|
||||
const childIds = this.getChildIds();
|
||||
const hasTimeSliderControl = childIds.some((id) => {
|
||||
const child = this.getChild(id);
|
||||
return child.type === TIME_SLIDER_CONTROL;
|
||||
});
|
||||
return (
|
||||
<CreateTimeSliderControlButton
|
||||
onCreate={() => {
|
||||
this.addTimeSliderControl();
|
||||
}}
|
||||
closePopover={closePopover}
|
||||
hasTimeSliderControl={hasTimeSliderControl}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private getEditControlGroupButton = (closePopover: () => void) => {
|
||||
public getEditControlGroupButton = (closePopover: () => void) => {
|
||||
const ControlsServicesProvider = pluginServices.getContextProvider();
|
||||
|
||||
return (
|
||||
|
@ -198,33 +131,6 @@ export class ControlGroupContainer extends Container<
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the toolbar button that is used for creating controls and managing control settings
|
||||
* @return `SolutionToolbarPopover` button for input controls
|
||||
*/
|
||||
public getToolbarButtons = () => {
|
||||
return (
|
||||
<SolutionToolbarPopover
|
||||
ownFocus
|
||||
label={ControlGroupStrings.getControlButtonTitle()}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
panelPaddingSize="none"
|
||||
data-test-subj="dashboard-controls-menu-button"
|
||||
>
|
||||
{({ closePopover }: { closePopover: () => void }) => (
|
||||
<EuiContextMenuPanel
|
||||
items={[
|
||||
this.getCreateControlButton('toolbar', closePopover),
|
||||
this.getCreateTimeSliderControlButton(closePopover),
|
||||
this.getEditControlGroupButton(closePopover),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</SolutionToolbarPopover>
|
||||
);
|
||||
};
|
||||
|
||||
constructor(
|
||||
reduxEmbeddablePackage: ReduxEmbeddablePackage,
|
||||
initialInput: ControlGroupInput,
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
@include euiBreakpoint('xs', 's') {
|
||||
.controlsIllustration, .emptyStateBadge {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.controlsWrapper {
|
||||
&--empty {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
margin: 0 $euiSizeS 0 $euiSizeS;
|
||||
|
||||
.addControlButton {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('m', 'l', 'xl') {
|
||||
height: $euiSizeS * 6;
|
||||
|
||||
.emptyStateBadge {
|
||||
padding-left: $euiSize * 2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
@include euiBreakpoint('xs', 's') {
|
||||
min-height: $euiSizeS * 6;
|
||||
|
||||
.emptyStateText {
|
||||
padding-left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.controlsIllustration__container {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,76 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiButtonEmpty,
|
||||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
|
||||
import './controls_callout.scss';
|
||||
import { ControlGroupStrings } from '../control_group/control_group_strings';
|
||||
import { ControlsIllustration } from './controls_illustration';
|
||||
|
||||
const CONTROLS_CALLOUT_STATE_KEY = 'dashboard:controlsCalloutDismissed';
|
||||
|
||||
export interface CalloutProps {
|
||||
getCreateControlButton?: () => JSX.Element;
|
||||
}
|
||||
|
||||
export const ControlsCallout = ({ getCreateControlButton }: CalloutProps) => {
|
||||
const [controlsCalloutDismissed, setControlsCalloutDismissed] = useLocalStorage(
|
||||
CONTROLS_CALLOUT_STATE_KEY,
|
||||
false
|
||||
);
|
||||
const dismissControls = () => {
|
||||
setControlsCalloutDismissed(true);
|
||||
};
|
||||
|
||||
if (controlsCalloutDismissed) return null;
|
||||
|
||||
return (
|
||||
<EuiPanel borderRadius="m" color="plain" paddingSize={'s'} className="controlsWrapper--empty">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" data-test-subj="controls-empty">
|
||||
<EuiFlexItem grow={1} className="controlsIllustration__container">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ControlsIllustration />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="emptyStateBadge" grow={false}>
|
||||
<EuiBadge color="accent">{ControlGroupStrings.emptyState.getBadge()}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText className="emptyStateText" size="s">
|
||||
<p>{ControlGroupStrings.emptyState.getCallToAction()}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceAround" responsive={false} gutterSize="xs">
|
||||
{getCreateControlButton && <EuiFlexItem>{getCreateControlButton()}</EuiFlexItem>}
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty size="s" onClick={dismissControls}>
|
||||
{ControlGroupStrings.emptyState.getDismissButton()}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
// required for dynamic import using React.lazy()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ControlsCallout;
|
File diff suppressed because one or more lines are too long
|
@ -54,7 +54,6 @@ export {
|
|||
type RangeSliderEmbeddableInput,
|
||||
} from './range_slider';
|
||||
|
||||
export { LazyControlsCallout, type CalloutProps } from './controls_callout';
|
||||
export {
|
||||
LazyControlGroupRenderer,
|
||||
useControlGroupContainerContext,
|
||||
|
|
|
@ -335,3 +335,23 @@ export const topNavStrings = {
|
|||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const getControlButtonTitle = () =>
|
||||
i18n.translate('dashboard.editingToolbar.controlsButtonTitle', {
|
||||
defaultMessage: 'Controls',
|
||||
});
|
||||
|
||||
export const getAddControlButtonTitle = () =>
|
||||
i18n.translate('dashboard.editingToolbar.addControlButtonTitle', {
|
||||
defaultMessage: 'Add control',
|
||||
});
|
||||
|
||||
export const getOnlyOneTimeSliderControlMsg = () =>
|
||||
i18n.translate('dashboard.editingToolbar.onlyOneTimeSliderControlMsg', {
|
||||
defaultMessage: 'Control group already contains time slider control.',
|
||||
});
|
||||
|
||||
export const getAddTimeSliderControlButtonTitle = () =>
|
||||
i18n.translate('dashboard.editingToolbar.addTimeSliderControlButtonTitle', {
|
||||
defaultMessage: 'Add time slider control',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiContextMenuItem } from '@elastic/eui';
|
||||
import { ControlGroupContainer } from '@kbn/controls-plugin/public';
|
||||
import { getAddControlButtonTitle } from '../../_dashboard_app_strings';
|
||||
|
||||
interface Props {
|
||||
closePopover: () => void;
|
||||
controlGroup: ControlGroupContainer;
|
||||
}
|
||||
|
||||
export const AddDataControlButton = ({ closePopover, controlGroup }: Props) => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key="addControl"
|
||||
icon="plusInCircle"
|
||||
data-test-subj="controls-create-button"
|
||||
aria-label={getAddControlButtonTitle()}
|
||||
onClick={() => {
|
||||
controlGroup.openAddDataControlFlyout();
|
||||
closePopover();
|
||||
}}
|
||||
>
|
||||
{getAddControlButtonTitle()}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiContextMenuItem } from '@elastic/eui';
|
||||
import { ControlGroupContainer, TIME_SLIDER_CONTROL } from '@kbn/controls-plugin/public';
|
||||
import {
|
||||
getAddTimeSliderControlButtonTitle,
|
||||
getOnlyOneTimeSliderControlMsg,
|
||||
} from '../../_dashboard_app_strings';
|
||||
|
||||
interface Props {
|
||||
closePopover: () => void;
|
||||
controlGroup: ControlGroupContainer;
|
||||
}
|
||||
|
||||
export const AddTimeSliderControlButton = ({ closePopover, controlGroup }: Props) => {
|
||||
const [hasTimeSliderControl, setHasTimeSliderControl] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = controlGroup.getInput$().subscribe(() => {
|
||||
const childIds = controlGroup.getChildIds();
|
||||
const nextHasTimeSliderControl = childIds.some((id: string) => {
|
||||
const child = controlGroup.getChild(id);
|
||||
return child.type === TIME_SLIDER_CONTROL;
|
||||
});
|
||||
if (nextHasTimeSliderControl !== hasTimeSliderControl) {
|
||||
setHasTimeSliderControl(nextHasTimeSliderControl);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [controlGroup, hasTimeSliderControl, setHasTimeSliderControl]);
|
||||
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key="addTimeSliderControl"
|
||||
icon="plusInCircle"
|
||||
onClick={() => {
|
||||
controlGroup.addTimeSliderControl();
|
||||
closePopover();
|
||||
}}
|
||||
data-test-subj="controls-create-timeslider-button"
|
||||
disabled={hasTimeSliderControl}
|
||||
toolTipContent={hasTimeSliderControl ? getOnlyOneTimeSliderControlMsg() : null}
|
||||
>
|
||||
{getAddTimeSliderControlButtonTitle()}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiContextMenuPanel } from '@elastic/eui';
|
||||
import { SolutionToolbarPopover } from '@kbn/presentation-util-plugin/public';
|
||||
import type { ControlGroupContainer } from '@kbn/controls-plugin/public';
|
||||
import { getControlButtonTitle } from '../../_dashboard_app_strings';
|
||||
import { AddDataControlButton } from './add_data_control_button';
|
||||
import { AddTimeSliderControlButton } from './add_time_slider_control_button';
|
||||
|
||||
export function ControlsToolbarButton({ controlGroup }: { controlGroup: ControlGroupContainer }) {
|
||||
return (
|
||||
<SolutionToolbarPopover
|
||||
ownFocus
|
||||
label={getControlButtonTitle()}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
panelPaddingSize="none"
|
||||
data-test-subj="dashboard-controls-menu-button"
|
||||
>
|
||||
{({ closePopover }: { closePopover: () => void }) => (
|
||||
<EuiContextMenuPanel
|
||||
items={[
|
||||
<AddDataControlButton controlGroup={controlGroup} closePopover={closePopover} />,
|
||||
<AddTimeSliderControlButton controlGroup={controlGroup} closePopover={closePopover} />,
|
||||
controlGroup.getEditControlGroupButton(closePopover),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</SolutionToolbarPopover>
|
||||
);
|
||||
}
|
|
@ -6,6 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
export const LazyControlsCallout = React.lazy(() => import('./controls_callout'));
|
||||
export type { CalloutProps } from './controls_callout';
|
||||
export { ControlsToolbarButton } from './controls_toolbar_button';
|
|
@ -25,6 +25,7 @@ import { useDashboardContainerContext } from '../../dashboard_container/dashboar
|
|||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
import { ControlsToolbarButton } from './controls_toolbar_button';
|
||||
|
||||
export function DashboardEditingToolbar() {
|
||||
const {
|
||||
|
@ -165,6 +166,17 @@ export function DashboardEditingToolbar() {
|
|||
.map(getVisTypeQuickButton)
|
||||
.filter((button) => button) as QuickButtonProps[];
|
||||
|
||||
const extraButtons = [
|
||||
<EditorMenu createNewVisType={createNewVisType} createNewEmbeddable={createNewEmbeddable} />,
|
||||
<AddFromLibraryButton
|
||||
onClick={() => dashboardContainer.addFromLibrary()}
|
||||
data-test-subj="dashboardAddPanelButton"
|
||||
/>,
|
||||
];
|
||||
if (dashboardContainer.controlGroup) {
|
||||
extraButtons.push(<ControlsToolbarButton controlGroup={dashboardContainer.controlGroup} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
@ -180,17 +192,7 @@ export function DashboardEditingToolbar() {
|
|||
/>
|
||||
),
|
||||
quickButtonGroup: <QuickButtonGroup buttons={quickButtons} />,
|
||||
extraButtons: [
|
||||
<EditorMenu
|
||||
createNewVisType={createNewVisType}
|
||||
createNewEmbeddable={createNewEmbeddable}
|
||||
/>,
|
||||
<AddFromLibraryButton
|
||||
onClick={() => dashboardContainer.addFromLibrary()}
|
||||
data-test-subj="dashboardAddPanelButton"
|
||||
/>,
|
||||
dashboardContainer.controlGroup?.getToolbarButtons(),
|
||||
],
|
||||
extraButtons,
|
||||
}}
|
||||
</SolutionToolbar>
|
||||
</>
|
||||
|
|
|
@ -8,10 +8,8 @@
|
|||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { ExitFullScreenButton } from '@kbn/shared-ux-button-exit-full-screen';
|
||||
import { CalloutProps, LazyControlsCallout } from '@kbn/controls-plugin/public';
|
||||
|
||||
import { DashboardGrid } from '../grid';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
|
@ -19,15 +17,13 @@ import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen';
|
|||
import { useDashboardContainerContext } from '../../dashboard_container_renderer';
|
||||
import { DashboardLoadedInfo } from '../../embeddable/dashboard_container';
|
||||
|
||||
const ControlsCallout = withSuspense<CalloutProps>(LazyControlsCallout);
|
||||
|
||||
export const DashboardViewport = ({
|
||||
onDataLoaded,
|
||||
}: {
|
||||
onDataLoaded?: (data: DashboardLoadedInfo) => void;
|
||||
}) => {
|
||||
const {
|
||||
settings: { isProjectEnabledInLabs, uiSettings },
|
||||
settings: { isProjectEnabledInLabs },
|
||||
} = pluginServices.getServices();
|
||||
const controlsRoot = useRef(null);
|
||||
|
||||
|
@ -60,30 +56,14 @@ export const DashboardViewport = ({
|
|||
const isEmbeddedExternally = select((state) => state.componentState.isEmbeddedExternally);
|
||||
|
||||
const controlsEnabled = isProjectEnabledInLabs('labs:dashboard:dashboardControls');
|
||||
const hideAnnouncements = Boolean(uiSettings.get('hideAnnouncements'));
|
||||
|
||||
return (
|
||||
<>
|
||||
{controlsEnabled && controlGroup ? (
|
||||
<>
|
||||
{!hideAnnouncements &&
|
||||
viewMode === ViewMode.EDIT &&
|
||||
panelCount !== 0 &&
|
||||
controlCount === 0 ? (
|
||||
<ControlsCallout
|
||||
getCreateControlButton={() => {
|
||||
return controlGroup && controlGroup.getCreateControlButton('callout');
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{viewMode !== ViewMode.PRINT && (
|
||||
<div
|
||||
className={controlCount > 0 ? 'dshDashboardViewport-controls' : ''}
|
||||
ref={controlsRoot}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{controlsEnabled && controlGroup && viewMode !== ViewMode.PRINT ? (
|
||||
<div
|
||||
className={controlCount > 0 ? 'dshDashboardViewport-controls' : ''}
|
||||
ref={controlsRoot}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
data-shared-items-count={panelCount}
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
"@kbn/core-saved-objects-common",
|
||||
"@kbn/task-manager-plugin",
|
||||
"@kbn/core-execution-context-common",
|
||||
"@kbn/shared-ux-utility",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -1,103 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { OPTIONS_LIST_CONTROL } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const browser = getService('browser');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
|
||||
const { dashboardControls, timePicker, dashboard } = getPageObjects([
|
||||
'dashboardControls',
|
||||
'timePicker',
|
||||
'dashboard',
|
||||
'common',
|
||||
'header',
|
||||
]);
|
||||
|
||||
describe('Controls callout', () => {
|
||||
describe('callout visibility', async () => {
|
||||
before(async () => {
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
|
||||
await dashboard.clickNewDashboard();
|
||||
await timePicker.setDefaultDataRange();
|
||||
await dashboard.saveDashboard('Test Controls Callout');
|
||||
});
|
||||
|
||||
describe('does not show the empty control callout on an empty dashboard', async () => {
|
||||
before(async () => {
|
||||
const panelCount = await dashboard.getPanelCount();
|
||||
if (panelCount > 0) {
|
||||
const panels = await dashboard.getDashboardPanels();
|
||||
for (const panel of panels) {
|
||||
await dashboardPanelActions.removePanel(panel);
|
||||
}
|
||||
await dashboard.clickQuickSave();
|
||||
}
|
||||
});
|
||||
|
||||
it('in view mode', async () => {
|
||||
await dashboard.clickCancelOutOfEditMode();
|
||||
await testSubjects.missingOrFail('controls-empty');
|
||||
});
|
||||
|
||||
it('in edit mode', async () => {
|
||||
await dashboard.switchToEditMode();
|
||||
await testSubjects.missingOrFail('controls-empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('show the empty control callout on a dashboard with panels', async () => {
|
||||
await dashboard.switchToEditMode();
|
||||
const panelCount = await dashboard.getPanelCount();
|
||||
if (panelCount < 1) {
|
||||
await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie');
|
||||
}
|
||||
await testSubjects.existOrFail('controls-empty');
|
||||
});
|
||||
|
||||
it('adding control hides the empty control callout', async () => {
|
||||
await dashboardControls.createControl({
|
||||
controlType: OPTIONS_LIST_CONTROL,
|
||||
dataViewTitle: 'animals-*',
|
||||
fieldName: 'sound.keyword',
|
||||
});
|
||||
await testSubjects.missingOrFail('controls-empty');
|
||||
});
|
||||
|
||||
it('deleting all controls shows the emoty control callout again', async () => {
|
||||
await dashboardControls.deleteAllControls();
|
||||
await testSubjects.existOrFail('controls-empty');
|
||||
});
|
||||
|
||||
it('hide callout when hide announcement setting is true', async () => {
|
||||
await dashboard.clickQuickSave();
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
await kibanaServer.uiSettings.update({ hideAnnouncements: true });
|
||||
await browser.refresh();
|
||||
|
||||
await dashboard.loadSavedDashboard('Test Controls Callout');
|
||||
await dashboard.switchToEditMode();
|
||||
await testSubjects.missingOrFail('controls-empty');
|
||||
|
||||
await kibanaServer.uiSettings.update({ hideAnnouncements: false });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await dashboard.clickCancelOutOfEditMode();
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -46,7 +46,6 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid
|
|||
describe('Controls', function () {
|
||||
before(setup);
|
||||
after(teardown);
|
||||
loadTestFile(require.resolve('./controls_callout'));
|
||||
loadTestFile(require.resolve('./control_group_settings'));
|
||||
loadTestFile(require.resolve('./options_list'));
|
||||
loadTestFile(require.resolve('./range_slider'));
|
||||
|
|
|
@ -368,7 +368,6 @@
|
|||
"controls.optionsList.popover.invalidSelectionsTooltip": "{selectedOptions} {selectedOptions, plural, one {option sélectionnée} other {options sélectionnées}} {selectedOptions, plural, one {est ignorée} other {sont ignorées}}, car {selectedOptions, plural, one {elle n'est plus présente} other {elles ne sont plus présentes}} dans les données.",
|
||||
"controls.rangeSlider.errors.dataViewNotFound": "Impossible de localiser la vue de données : {dataViewId}",
|
||||
"controls.rangeSlider.errors.fieldNotFound": "Impossible de localiser le champ : {fieldName}",
|
||||
"controls.controlGroup.addTimeSliderControlButtonTitle": "Ajouter un contrôle de curseur temporel",
|
||||
"controls.controlGroup.emptyState.addControlButtonTitle": "Ajouter un contrôle",
|
||||
"controls.controlGroup.emptyState.badgeText": "Nouveauté",
|
||||
"controls.controlGroup.emptyState.callToAction": "Le filtrage des données s'est amélioré grâce aux contrôles, qui vous permettent d'afficher uniquement les données que vous souhaitez explorer.",
|
||||
|
@ -426,10 +425,8 @@
|
|||
"controls.controlGroup.management.query.useAllSearchSettingsTitle": "Assure la synchronisation entre le groupe de contrôle et la barre de requête, en appliquant une plage temporelle, des pilules de filtre et des requêtes de la barre de requête",
|
||||
"controls.controlGroup.management.validate.subtitle": "Ignorez automatiquement toutes les sélections de contrôle qui ne donneraient aucune donnée.",
|
||||
"controls.controlGroup.management.validate.title": "Valider les sélections utilisateur",
|
||||
"controls.controlGroup.onlyOneTimeSliderControlMsg": "Le groupe de contrôle contient déjà un contrôle de curseur temporel.",
|
||||
"controls.controlGroup.timeSlider.title": "Curseur temporel",
|
||||
"controls.controlGroup.title": "Groupe de contrôle",
|
||||
"controls.controlGroup.toolbarButtonTitle": "Contrôles",
|
||||
"controls.frame.error.message": "Une erreur s'est produite. En savoir plus",
|
||||
"controls.optionsList.control.excludeExists": "NE PAS",
|
||||
"controls.optionsList.control.negate": "NON",
|
||||
|
|
|
@ -370,7 +370,6 @@
|
|||
"controls.optionsList.popover.invalidSelectionsTooltip": "{selectedOptions}個の選択した{selectedOptions, plural, other {オプション}} {selectedOptions, plural, other {が}}無視されます。{selectedOptions, plural, other {オプションが}}データに存在しません。",
|
||||
"controls.rangeSlider.errors.dataViewNotFound": "データビュー{dataViewId}が見つかりませんでした",
|
||||
"controls.rangeSlider.errors.fieldNotFound": "フィールド{fieldName}が見つかりませんでした",
|
||||
"controls.controlGroup.addTimeSliderControlButtonTitle": "時間スライダーコントロールを追加",
|
||||
"controls.controlGroup.emptyState.addControlButtonTitle": "コントロールを追加",
|
||||
"controls.controlGroup.emptyState.badgeText": "新規",
|
||||
"controls.controlGroup.emptyState.callToAction": "データのフィルタリングはコントロールによって効果的になりました。探索するデータのみを表示できます。",
|
||||
|
@ -428,10 +427,8 @@
|
|||
"controls.controlGroup.management.query.useAllSearchSettingsTitle": "時間範囲、フィルターピル、クエリバーからのクエリを適用して、コントロールグループを常にクエリと同期します",
|
||||
"controls.controlGroup.management.validate.subtitle": "データがないコントロール選択は自動的に無視されます。",
|
||||
"controls.controlGroup.management.validate.title": "ユーザー選択を検証",
|
||||
"controls.controlGroup.onlyOneTimeSliderControlMsg": "コントロールグループには、すでに時間スライダーコントロールがあります。",
|
||||
"controls.controlGroup.timeSlider.title": "時間スライダー",
|
||||
"controls.controlGroup.title": "コントロールグループ",
|
||||
"controls.controlGroup.toolbarButtonTitle": "コントロール",
|
||||
"controls.frame.error.message": "エラーが発生しました。続きを読む",
|
||||
"controls.optionsList.control.excludeExists": "DOES NOT",
|
||||
"controls.optionsList.control.negate": "NOT",
|
||||
|
|
|
@ -370,7 +370,6 @@
|
|||
"controls.optionsList.popover.invalidSelectionsTooltip": "{selectedOptions} 个选定{selectedOptions, plural, other {选项}} {selectedOptions, plural, other {已}}忽略,因为{selectedOptions, plural, one {其} other {它们}}已不再在数据中。",
|
||||
"controls.rangeSlider.errors.dataViewNotFound": "找不到数据视图:{dataViewId}",
|
||||
"controls.rangeSlider.errors.fieldNotFound": "找不到字段:{fieldName}",
|
||||
"controls.controlGroup.addTimeSliderControlButtonTitle": "添加时间滑块控件",
|
||||
"controls.controlGroup.emptyState.addControlButtonTitle": "添加控件",
|
||||
"controls.controlGroup.emptyState.badgeText": "新建",
|
||||
"controls.controlGroup.emptyState.callToAction": "使用控件可以更有效地筛选数据,允许您仅显示要浏览的数据。",
|
||||
|
@ -428,10 +427,8 @@
|
|||
"controls.controlGroup.management.query.useAllSearchSettingsTitle": "通过从查询栏应用时间范围、筛选胶囊和查询,使控件组与查询栏保持同步",
|
||||
"controls.controlGroup.management.validate.subtitle": "自动忽略所有不会生成数据的控件选择。",
|
||||
"controls.controlGroup.management.validate.title": "验证用户选择",
|
||||
"controls.controlGroup.onlyOneTimeSliderControlMsg": "控件组已包含时间滑块控件。",
|
||||
"controls.controlGroup.timeSlider.title": "时间滑块",
|
||||
"controls.controlGroup.title": "控件组",
|
||||
"controls.controlGroup.toolbarButtonTitle": "控件",
|
||||
"controls.frame.error.message": "发生错误。阅读更多内容",
|
||||
"controls.optionsList.control.excludeExists": "不",
|
||||
"controls.optionsList.control.negate": "非",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue