mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Dashboard] Add filter details to panel settings (#162913)
This commit is contained in:
parent
db1cec4c94
commit
3720270232
17 changed files with 333 additions and 54 deletions
|
@ -52,7 +52,11 @@ export function FiltersNotificationPopoverContents({
|
|||
setFilters(embeddableFilters);
|
||||
if (embeddableQuery) {
|
||||
if (isOfQueryType(embeddableQuery)) {
|
||||
setQueryString(embeddableQuery.query as string);
|
||||
if (typeof embeddableQuery.query === 'string') {
|
||||
setQueryString(embeddableQuery.query);
|
||||
} else {
|
||||
setQueryString(JSON.stringify(embeddableQuery.query, null, 2));
|
||||
}
|
||||
} else {
|
||||
const language = getAggregateQueryMode(embeddableQuery);
|
||||
setQueryLanguage(language);
|
||||
|
@ -79,8 +83,7 @@ export function FiltersNotificationPopoverContents({
|
|||
>
|
||||
<EuiCodeBlock
|
||||
language={queryLanguage}
|
||||
paddingSize="none"
|
||||
transparentBackground
|
||||
paddingSize="s"
|
||||
aria-labelledby={`${dashboardFilterNotificationActionStrings.getQueryTitle()}: ${queryString}`}
|
||||
tabIndex={0} // focus so that keyboard controls will not skip over the code block
|
||||
>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"contentManagement"
|
||||
],
|
||||
"optionalPlugins": ["savedObjectsTaggingOss", "usageCollection"],
|
||||
"requiredBundles": ["savedObjects", "kibanaReact", "kibanaUtils"],
|
||||
"requiredBundles": ["savedObjects", "kibanaReact", "kibanaUtils", "unifiedSearch"],
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,21 +71,23 @@ export const EmbeddablePanel = (panelProps: UnwrappedEmbeddablePanelProps) => {
|
|||
const commonlyUsedRanges = core.uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES);
|
||||
const dateFormat = core.uiSettings.get(UI_SETTINGS.DATE_FORMAT);
|
||||
const stateTransfer = embeddableStart.getStateTransfer();
|
||||
const editPanel = new EditPanelAction(
|
||||
embeddableStart.getEmbeddableFactory,
|
||||
core.application,
|
||||
stateTransfer,
|
||||
containerContext?.getCurrentPath
|
||||
);
|
||||
|
||||
const actions: PanelUniversalActions = {
|
||||
customizePanel: new CustomizePanelAction(
|
||||
core.overlays,
|
||||
core.theme,
|
||||
editPanel,
|
||||
commonlyUsedRanges,
|
||||
dateFormat
|
||||
),
|
||||
removePanel: new RemovePanelAction(),
|
||||
editPanel: new EditPanelAction(
|
||||
embeddableStart.getEmbeddableFactory,
|
||||
core.application,
|
||||
stateTransfer,
|
||||
containerContext?.getCurrentPath
|
||||
),
|
||||
editPanel,
|
||||
};
|
||||
if (!hideInspector) actions.inspectPanel = new InspectPanelAction(inspector);
|
||||
return actions;
|
||||
|
|
|
@ -15,6 +15,11 @@ import {
|
|||
TIME_RANGE_EMBEDDABLE,
|
||||
} from '../../../lib/test_samples/embeddables';
|
||||
import { CustomTimeRangeBadge } from './custom_time_range_badge';
|
||||
import { EditPanelAction } from '../edit_panel_action/edit_panel_action';
|
||||
|
||||
const editPanelAction = {
|
||||
execute: jest.fn(),
|
||||
} as unknown as EditPanelAction;
|
||||
|
||||
test(`badge is not compatible with embeddable that inherits from parent`, async () => {
|
||||
const container = new TimeRangeContainer(
|
||||
|
@ -40,6 +45,7 @@ test(`badge is not compatible with embeddable that inherits from parent`, async
|
|||
const compatible = await new CustomTimeRangeBadge(
|
||||
overlayServiceMock.createStartContract(),
|
||||
themeServiceMock.createStartContract(),
|
||||
editPanelAction,
|
||||
[],
|
||||
'MM YYYY'
|
||||
).isCompatible({
|
||||
|
@ -73,6 +79,7 @@ test(`badge is compatible with embeddable that has custom time range`, async ()
|
|||
const compatible = await new CustomTimeRangeBadge(
|
||||
overlayServiceMock.createStartContract(),
|
||||
themeServiceMock.createStartContract(),
|
||||
editPanelAction,
|
||||
[],
|
||||
'MM YYYY'
|
||||
).isCompatible({
|
||||
|
@ -105,6 +112,7 @@ test('Attempting to execute on incompatible embeddable throws an error', async (
|
|||
const badge = await new CustomTimeRangeBadge(
|
||||
overlayServiceMock.createStartContract(),
|
||||
themeServiceMock.createStartContract(),
|
||||
editPanelAction,
|
||||
[],
|
||||
'MM YYYY'
|
||||
);
|
||||
|
|
|
@ -21,11 +21,13 @@ import {
|
|||
} from '../../../lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory';
|
||||
import { HelloWorldContainer } from '../../../lib/test_samples/embeddables/hello_world_container';
|
||||
import { embeddablePluginMock } from '../../../mocks';
|
||||
import { EditPanelAction } from '../edit_panel_action/edit_panel_action';
|
||||
|
||||
let container: Container;
|
||||
let embeddable: ContactCardEmbeddable;
|
||||
const overlays = overlayServiceMock.createStartContract();
|
||||
const theme = themeServiceMock.createStartContract();
|
||||
const editPanelActionMock = { execute: jest.fn() } as unknown as EditPanelAction;
|
||||
|
||||
function createHelloWorldContainer(input = { id: '123', panels: {} }) {
|
||||
const { setup, doStart } = embeddablePluginMock.createInstance();
|
||||
|
@ -57,7 +59,7 @@ beforeAll(async () => {
|
|||
});
|
||||
|
||||
test('execute should open flyout', async () => {
|
||||
const customizePanelAction = new CustomizePanelAction(overlays, theme);
|
||||
const customizePanelAction = new CustomizePanelAction(overlays, theme, editPanelActionMock);
|
||||
const spy = jest.spyOn(overlays, 'openFlyout');
|
||||
await customizePanelAction.execute({ embeddable });
|
||||
|
||||
|
|
|
@ -9,11 +9,19 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { IEmbeddable, Embeddable, EmbeddableInput, EmbeddableOutput } from '../../..';
|
||||
import { core } from '../../../kibana_services';
|
||||
import {
|
||||
IEmbeddable,
|
||||
Embeddable,
|
||||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
EditPanelAction,
|
||||
} from '../../..';
|
||||
import { ViewMode, CommonlyUsedRange } from '../../../lib/types';
|
||||
import { tracksOverlays } from '../track_overlays';
|
||||
import { CustomizePanelEditor } from './customize_panel_editor';
|
||||
|
@ -52,6 +60,7 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
|
|||
constructor(
|
||||
protected readonly overlays: OverlayStart,
|
||||
protected readonly theme: ThemeServiceStart,
|
||||
protected readonly editPanel: EditPanelAction,
|
||||
protected readonly commonlyUsedRanges?: CommonlyUsedRange[],
|
||||
protected readonly dateFormat?: string
|
||||
) {}
|
||||
|
@ -99,19 +108,30 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
|
|||
const rootEmbeddable = embeddable.getRoot();
|
||||
const overlayTracker = tracksOverlays(rootEmbeddable) ? rootEmbeddable : undefined;
|
||||
|
||||
const { Provider: KibanaReactContextProvider } = createKibanaReactContext({
|
||||
uiSettings: core.uiSettings,
|
||||
});
|
||||
|
||||
const onEdit = () => {
|
||||
this.editPanel.execute({ embeddable });
|
||||
};
|
||||
|
||||
const handle = this.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={this.isTimeRangeCompatible({ embeddable })}
|
||||
dateFormat={this.dateFormat}
|
||||
commonlyUsedRanges={this.commonlyUsedRanges}
|
||||
onClose={() => {
|
||||
if (overlayTracker) overlayTracker.clearOverlays();
|
||||
handle.close();
|
||||
}}
|
||||
/>,
|
||||
{ theme$: this.theme.theme$ }
|
||||
<KibanaReactContextProvider>
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={this.isTimeRangeCompatible({ embeddable })}
|
||||
dateFormat={this.dateFormat}
|
||||
commonlyUsedRanges={this.commonlyUsedRanges}
|
||||
onClose={() => {
|
||||
if (overlayTracker) overlayTracker.clearOverlays();
|
||||
handle.close();
|
||||
}}
|
||||
onEdit={onEdit}
|
||||
/>
|
||||
</KibanaReactContextProvider>,
|
||||
{ theme: this.theme, i18n: core.i18n }
|
||||
),
|
||||
{
|
||||
size: 's',
|
||||
|
@ -120,6 +140,7 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
|
|||
if (overlayTracker) overlayTracker.clearOverlays();
|
||||
overlayRef.close();
|
||||
},
|
||||
maxWidth: true,
|
||||
}
|
||||
);
|
||||
overlayTracker?.openOverlay(handle);
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSuperDatePicker,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
|
@ -31,7 +32,14 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { TimeRangeInput } from './customize_panel_action';
|
||||
import { canInheritTimeRange } from './can_inherit_time_range';
|
||||
import { doesInheritTimeRange } from './does_inherit_time_range';
|
||||
import { IEmbeddable, Embeddable, CommonlyUsedRange, ViewMode } from '../../../lib';
|
||||
import {
|
||||
IEmbeddable,
|
||||
Embeddable,
|
||||
CommonlyUsedRange,
|
||||
ViewMode,
|
||||
isFilterableEmbeddable,
|
||||
} from '../../../lib';
|
||||
import { FiltersDetails } from './filters_details';
|
||||
|
||||
type PanelSettings = {
|
||||
title?: string;
|
||||
|
@ -46,10 +54,11 @@ interface CustomizePanelProps {
|
|||
dateFormat?: string;
|
||||
commonlyUsedRanges?: CommonlyUsedRange[];
|
||||
onClose: () => void;
|
||||
onEdit: () => void;
|
||||
}
|
||||
|
||||
export const CustomizePanelEditor = (props: CustomizePanelProps) => {
|
||||
const { onClose, embeddable, dateFormat, timeRangeCompatible } = props;
|
||||
const { onClose, embeddable, dateFormat, timeRangeCompatible, onEdit } = props;
|
||||
const editMode = embeddable.getInput().viewMode === ViewMode.EDIT;
|
||||
const [hideTitle, setHideTitle] = useState(embeddable.getInput().hidePanelTitles);
|
||||
const [panelDescription, setPanelDescription] = useState(
|
||||
|
@ -259,6 +268,17 @@ export const CustomizePanelEditor = (props: CustomizePanelProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const renderFilterDetails = () => {
|
||||
if (!isFilterableEmbeddable(embeddable)) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<FiltersDetails onEdit={onEdit} embeddable={embeddable} editMode={editMode} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
|
@ -275,6 +295,7 @@ export const CustomizePanelEditor = (props: CustomizePanelProps) => {
|
|||
<EuiForm>
|
||||
{renderCustomTitleComponent()}
|
||||
{renderCustomTimeRangeComponent()}
|
||||
{renderFilterDetails()}
|
||||
</EuiForm>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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, { useMemo, useState } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiCodeBlock,
|
||||
EuiFlexGroup,
|
||||
EuiFormRow,
|
||||
EuiSkeletonText,
|
||||
} from '@elastic/eui';
|
||||
import { FilterItems } from '@kbn/unified-search-plugin/public';
|
||||
import {
|
||||
type AggregateQuery,
|
||||
type Filter,
|
||||
getAggregateQueryMode,
|
||||
isOfQueryType,
|
||||
} from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { IEmbeddable } from '../../../lib/embeddables';
|
||||
import { isFilterableEmbeddable } from '../../../lib/filterable_embeddable';
|
||||
|
||||
export const filterDetailsActionStrings = {
|
||||
getQueryTitle: () =>
|
||||
i18n.translate('embeddableApi.panel.filters.queryTitle', {
|
||||
defaultMessage: 'Query',
|
||||
}),
|
||||
getFiltersTitle: () =>
|
||||
i18n.translate('embeddableApi.panel.filters.filtersTitle', {
|
||||
defaultMessage: 'Filters',
|
||||
}),
|
||||
};
|
||||
|
||||
interface FiltersDetailsProps {
|
||||
embeddable: IEmbeddable;
|
||||
editMode: boolean;
|
||||
onEdit: () => void;
|
||||
}
|
||||
|
||||
export function FiltersDetails({ embeddable, editMode, onEdit }: FiltersDetailsProps) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
const [queryString, setQueryString] = useState<string>('');
|
||||
const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>();
|
||||
const [disableEditbutton, setDisableEditButton] = useState(false);
|
||||
const dataViews = useMemo(
|
||||
() => (embeddable.getOutput() as { indexPatterns?: DataView[] }).indexPatterns || [],
|
||||
[embeddable]
|
||||
);
|
||||
|
||||
useMount(() => {
|
||||
if (!isFilterableEmbeddable(embeddable)) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all([embeddable.getFilters(), embeddable.getQuery()]).then(
|
||||
([embeddableFilters, embeddableQuery]) => {
|
||||
setFilters(embeddableFilters);
|
||||
if (embeddableQuery) {
|
||||
if (isOfQueryType(embeddableQuery)) {
|
||||
if (typeof embeddableQuery.query === 'string') {
|
||||
setQueryString(embeddableQuery.query);
|
||||
} else {
|
||||
setQueryString(JSON.stringify(embeddableQuery.query, null, 2));
|
||||
}
|
||||
} else {
|
||||
const language = getAggregateQueryMode(embeddableQuery);
|
||||
setQueryLanguage(language);
|
||||
setQueryString(embeddableQuery[language as keyof AggregateQuery]);
|
||||
setDisableEditButton(true);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiSkeletonText isLoading={isLoading} lines={3}>
|
||||
{queryString !== '' && (
|
||||
<EuiFormRow
|
||||
label={filterDetailsActionStrings.getQueryTitle()}
|
||||
display="rowCompressed"
|
||||
labelAppend={
|
||||
editMode && !disableEditbutton ? (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="customizePanelEditQueryButton"
|
||||
onClick={onEdit}
|
||||
aria-label={i18n.translate(
|
||||
'embeddableApi.customizePanel.flyout.optionsMenuForm.editQueryButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Edit query',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="embeddableApi.customizePanel.flyout.optionsMenuForm.editQueryButtonLabel"
|
||||
defaultMessage="Edit"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<EuiCodeBlock
|
||||
language={queryLanguage}
|
||||
paddingSize="s"
|
||||
fontSize="s"
|
||||
aria-labelledby={`${filterDetailsActionStrings.getQueryTitle()}: ${queryString}`}
|
||||
tabIndex={0} // focus so that keyboard controls will not skip over the code block
|
||||
>
|
||||
{queryString}
|
||||
</EuiCodeBlock>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{filters.length > 0 && (
|
||||
<EuiFormRow
|
||||
label={filterDetailsActionStrings.getFiltersTitle()}
|
||||
labelAppend={
|
||||
editMode && !disableEditbutton ? (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="customizePanelEditFiltersButton"
|
||||
onClick={onEdit}
|
||||
aria-label={i18n.translate(
|
||||
'embeddableApi.customizePanel.flyout.optionsMenuForm.editFiltersButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Edit filters',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="embeddableApi.customizePanel.flyout.optionsMenuForm.editFiltersButtonLabel"
|
||||
defaultMessage="Edit"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup wrap={true} gutterSize="xs">
|
||||
<FilterItems filters={filters} indexPatterns={dataViews} readOnly={true} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</EuiSkeletonText>
|
||||
);
|
||||
}
|
|
@ -12,8 +12,6 @@ import * as kibanaServices from '../kibana_services';
|
|||
import { ErrorEmbeddable, IEmbeddable } from '../lib';
|
||||
import { useEmbeddablePanel } from './use_embeddable_panel';
|
||||
|
||||
jest.mock('../kibana_services');
|
||||
|
||||
describe('useEmbeddablePanel', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
|
|
@ -39,6 +39,10 @@ export class TimeRangeContainer extends Container<
|
|||
super(initialInput, { embeddableLoaded: {} }, getFactory, parent);
|
||||
}
|
||||
|
||||
public getAllDataViews() {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getInheritedInput() {
|
||||
return { timeRange: this.input.timeRange };
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ import {
|
|||
import { getAllMigrations } from '../common/lib/get_all_migrations';
|
||||
import { setTheme } from './services';
|
||||
import { setKibanaServices } from './kibana_services';
|
||||
import { CustomTimeRangeBadge } from './embeddable_panel/panel_actions';
|
||||
import { CustomTimeRangeBadge, EditPanelAction } from './embeddable_panel/panel_actions';
|
||||
|
||||
export interface EmbeddableSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
|
@ -153,15 +153,6 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
const dateFormat = uiSettings.get(UI_SETTINGS.DATE_FORMAT);
|
||||
const commonlyUsedRanges = uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES);
|
||||
|
||||
const timeRangeBadge = new CustomTimeRangeBadge(
|
||||
overlays,
|
||||
theme,
|
||||
commonlyUsedRanges,
|
||||
dateFormat
|
||||
);
|
||||
|
||||
uiActions.addTriggerAction(PANEL_BADGE_TRIGGER, timeRangeBadge);
|
||||
|
||||
this.appListSubscription = core.application.applications$.subscribe((appList) => {
|
||||
this.appList = appList;
|
||||
});
|
||||
|
@ -173,6 +164,22 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
);
|
||||
this.isRegistryReady = true;
|
||||
|
||||
const editPanel = new EditPanelAction(
|
||||
this.getEmbeddableFactory,
|
||||
core.application,
|
||||
this.stateTransferService
|
||||
);
|
||||
|
||||
const timeRangeBadge = new CustomTimeRangeBadge(
|
||||
overlays,
|
||||
theme,
|
||||
editPanel,
|
||||
commonlyUsedRanges,
|
||||
dateFormat
|
||||
);
|
||||
|
||||
uiActions.addTriggerAction(PANEL_BADGE_TRIGGER, timeRangeBadge);
|
||||
|
||||
const commonContract: CommonEmbeddableStartContract = {
|
||||
getEmbeddableFactory: this
|
||||
.getEmbeddableFactory as unknown as CommonEmbeddableStartContract['getEmbeddableFactory'],
|
||||
|
|
|
@ -20,10 +20,15 @@ import {
|
|||
TIME_RANGE_EMBEDDABLE,
|
||||
} from '../lib/test_samples';
|
||||
import { CustomizePanelEditor } from '../embeddable_panel/panel_actions/customize_panel_action/customize_panel_editor';
|
||||
import { embeddablePluginMock } from '../mocks';
|
||||
import { AggregateQuery, Filter, Query } from '@kbn/es-query';
|
||||
|
||||
let container: TimeRangeContainer;
|
||||
let embeddable: TimeRangeEmbeddable;
|
||||
|
||||
const mockGetFilters = jest.fn(async () => [] as Filter[]);
|
||||
const mockGetQuery = jest.fn(async () => undefined as Query | AggregateQuery | undefined);
|
||||
|
||||
beforeEach(async () => {
|
||||
const { doStart, setup } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
|
||||
|
@ -46,16 +51,25 @@ beforeEach(async () => {
|
|||
description: 'This might be a neat line chart',
|
||||
viewMode: ViewMode.EDIT,
|
||||
});
|
||||
|
||||
if (isErrorEmbeddable(timeRangeEmbeddable)) {
|
||||
throw new Error('Error creating new hello world embeddable');
|
||||
} else {
|
||||
embeddable = timeRangeEmbeddable;
|
||||
embeddable = embeddablePluginMock.mockFilterableEmbeddable(timeRangeEmbeddable, {
|
||||
getFilters: mockGetFilters,
|
||||
getQuery: mockGetQuery,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('Value is initialized with the embeddables title', async () => {
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const titleField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -69,7 +83,12 @@ test('Value is initialized with the embeddables title', async () => {
|
|||
test('Calls updateInput with a new title', async () => {
|
||||
const updateInput = jest.spyOn(embeddable, 'updateInput');
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const inputField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -86,7 +105,12 @@ test('Calls updateInput with a new title', async () => {
|
|||
test('Input value shows custom title if one given', async () => {
|
||||
embeddable.updateInput({ title: 'new title' });
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const inputField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -98,7 +122,12 @@ test('Input value shows custom title if one given', async () => {
|
|||
test('Reset updates the input values with the default properties when the embeddable has overridden the properties', async () => {
|
||||
embeddable.updateInput({ title: 'my custom title', description: 'my custom description' });
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const titleField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -118,7 +147,12 @@ test('Reset updates the input values with the default properties when the embedd
|
|||
|
||||
test('Reset updates the input with the default properties when the embeddable has no property overrides', async () => {
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const titleField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -142,7 +176,12 @@ test('Reset updates the input with the default properties when the embeddable ha
|
|||
test('Reset title calls updateInput with undefined', async () => {
|
||||
const updateInput = jest.spyOn(embeddable, 'updateInput');
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const inputField = findTestSubject(component, 'customEmbeddablePanelTitleInput').find('input');
|
||||
|
@ -160,7 +199,12 @@ test('Reset title calls updateInput with undefined', async () => {
|
|||
test('Reset description calls updateInput with undefined', async () => {
|
||||
const updateInput = jest.spyOn(embeddable, 'updateInput');
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const inputField = findTestSubject(component, 'customEmbeddablePanelDescriptionInput').find(
|
||||
|
@ -180,7 +224,12 @@ test('Reset description calls updateInput with undefined', async () => {
|
|||
test('Can set title and description to an empty string', async () => {
|
||||
const updateInput = jest.spyOn(embeddable, 'updateInput');
|
||||
const component = mountWithIntl(
|
||||
<CustomizePanelEditor embeddable={embeddable} timeRangeCompatible={true} onClose={() => {}} />
|
||||
<CustomizePanelEditor
|
||||
embeddable={embeddable}
|
||||
timeRangeCompatible={true}
|
||||
onClose={() => {}}
|
||||
onEdit={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
for (const subject of [
|
||||
|
|
|
@ -30,7 +30,10 @@
|
|||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/core-mount-utils-browser",
|
||||
"@kbn/content-management-plugin"
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ThemeServiceSetup } from '@kbn/core/public';
|
||||
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { IncompatibleActionError, UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
|
||||
// for cleanup esFilters need to fix the issue https://github.com/elastic/kibana/issues/131292
|
||||
|
@ -22,7 +21,11 @@ export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER';
|
|||
export interface ApplyGlobalFilterActionContext {
|
||||
filters: Filter[];
|
||||
timeFieldName?: string;
|
||||
embeddable?: IEmbeddable;
|
||||
// Need to make this unknown to prevent circular dependencies.
|
||||
// Apps using this property will need to cast to `IEmbeddable`.
|
||||
// TODO: We should consider moving these commonly used types into a separate package to avoid circular dependencies
|
||||
// https://github.com/elastic/kibana/issues/163994
|
||||
embeddable?: unknown;
|
||||
// controlledBy is an optional key in filter.meta that identifies the owner of a filter
|
||||
// Pass controlledBy to cleanup an existing filter(s) owned by embeddable prior to adding new filters
|
||||
controlledBy?: string;
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface FilterBadgeProps {
|
|||
valueLabel: string;
|
||||
hideAlias?: boolean;
|
||||
filterLabelStatus: FilterLabelStatus;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function FilterBadge({
|
||||
|
@ -30,6 +31,7 @@ function FilterBadge({
|
|||
valueLabel,
|
||||
hideAlias,
|
||||
filterLabelStatus,
|
||||
readOnly,
|
||||
...rest
|
||||
}: FilterBadgeProps) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -53,7 +55,7 @@ function FilterBadge({
|
|||
<EuiBadge
|
||||
className={badgePaddingCss(euiTheme)}
|
||||
color="hollow"
|
||||
iconType="cross"
|
||||
iconType={readOnly ? 'cross' : undefined}
|
||||
iconSide="right"
|
||||
{...rest}
|
||||
>
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
"@kbn/data-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/data-view-editor-plugin",
|
||||
"@kbn/embeddable-plugin",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/kibana-react-plugin",
|
||||
|
@ -40,7 +39,7 @@
|
|||
"@kbn/saved-objects-management-plugin",
|
||||
"@kbn/text-based-languages",
|
||||
"@kbn/text-based-editor",
|
||||
"@kbn/core-doc-links-browser"
|
||||
"@kbn/core-doc-links-browser",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -41,7 +41,7 @@ interface UrlDrilldownDeps {
|
|||
application: () => ApplicationStart;
|
||||
}
|
||||
|
||||
export type ActionContext = ApplyGlobalFilterActionContext;
|
||||
export type ActionContext = ApplyGlobalFilterActionContext & { embeddable: IEmbeddable };
|
||||
|
||||
export interface Config extends SerializableRecord {
|
||||
openInNewTab: boolean;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue