Skip to content

Commit

Permalink
Allow panel time range to be set in View mode
Browse files Browse the repository at this point in the history
  • Loading branch information
nickpeihl committed Jan 31, 2023
1 parent e736261 commit 49ba0a4
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,33 @@

import React from 'react';
import { Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, take, takeUntil } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { OverlayStart, ThemeServiceStart } from '@kbn/core/public';
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { TimeRange } from '@kbn/es-query';
import { ViewMode } from '../../../../types';
import { IEmbeddable, Embeddable, EmbeddableInput, CommonlyUsedRange } from '../../../..';
import {
IEmbeddable,
Embeddable,
EmbeddableInput,
CommonlyUsedRange,
EmbeddableOutput,
} from '../../../..';
import { CustomizePanelEditor } from './customize_panel_editor';

export const ACTION_CUSTOMIZE_PANEL = 'ACTION_CUSTOMIZE_PANEL';

const VISUALIZE_EMBEDDABLE_TYPE = 'visualization';

type VisualizeEmbeddable = IEmbeddable<{ id: string }, EmbeddableOutput & { visTypeName: string }>;

function isVisualizeEmbeddable(
embeddable: IEmbeddable | VisualizeEmbeddable
): embeddable is VisualizeEmbeddable {
return embeddable.type === VISUALIZE_EMBEDDABLE_TYPE;
}

export interface TimeRangeInput extends EmbeddableInput {
timeRange: TimeRange;
}
Expand All @@ -46,6 +61,22 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
protected readonly dateFormat?: string
) {}

protected isTimeRangeCompatible({ embeddable }: CustomizePanelActionContext): boolean {
const isInputControl =
isVisualizeEmbeddable(embeddable) &&
(embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'input_control_vis';

const isMarkdown =
isVisualizeEmbeddable(embeddable) &&
(embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'markdown';

const isImage = embeddable.type === 'image';

return Boolean(
embeddable && hasTimeRange(embeddable) && !isInputControl && !isMarkdown && !isImage
);
}

public getDisplayName({ embeddable }: CustomizePanelActionContext): string {
return i18n.translate('embeddableApi.customizePanel.action.displayName', {
defaultMessage: 'Edit panel settings',
Expand All @@ -57,7 +88,10 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
}

public async isCompatible({ embeddable }: CustomizePanelActionContext) {
return embeddable.getInput().viewMode === ViewMode.EDIT ? true : false;
// It should be possible to customize just the time range in View mode
return (
embeddable.getInput().viewMode === ViewMode.EDIT || this.isTimeRangeCompatible({ embeddable })
);
}

public async execute({ embeddable }: CustomizePanelActionContext) {
Expand All @@ -76,6 +110,7 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
toMountPoint(
<CustomizePanelEditor
embeddable={embeddable}
timeRangeCompatible={this.isTimeRangeCompatible({ embeddable })}
dateFormat={this.dateFormat}
commonlyUsedRanges={this.commonlyUsedRanges}
onClose={close}
Expand All @@ -87,17 +122,5 @@ export class CustomizePanelAction implements Action<CustomizePanelActionContext>
'data-test-subj': 'customizePanel',
}
);

// Close flyout on dashboard switch to "view" mode or on embeddable destroy.
embeddable
.getInput$()
.pipe(
takeUntil(closed$),
map((input) => input.viewMode),
distinctUntilChanged(),
filter((mode) => mode !== ViewMode.EDIT),
take(1)
)
.subscribe({ next: close, complete: close });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { TimeRange } from '@kbn/es-query';
import { hasTimeRange, TimeRangeInput } from './customize_panel_action';
import { TimeRangeInput } from './customize_panel_action';
import { doesInheritTimeRange } from './does_inherit_time_range';
import { IEmbeddable, Embeddable, EmbeddableOutput, CommonlyUsedRange } from '../../../..';
import { IEmbeddable, Embeddable, CommonlyUsedRange, ViewMode } from '../../../..';
import { canInheritTimeRange } from './can_inherit_time_range';

type PanelSettings = {
Expand All @@ -41,40 +41,15 @@ type PanelSettings = {

interface CustomizePanelProps {
embeddable: IEmbeddable;
timeRangeCompatible: boolean;
dateFormat?: string;
commonlyUsedRanges?: CommonlyUsedRange[];
onClose: () => void;
}

const VISUALIZE_EMBEDDABLE_TYPE = 'visualization';

type VisualizeEmbeddable = IEmbeddable<{ id: string }, EmbeddableOutput & { visTypeName: string }>;

function isVisualizeEmbeddable(
embeddable: IEmbeddable | VisualizeEmbeddable
): embeddable is VisualizeEmbeddable {
return embeddable.type === VISUALIZE_EMBEDDABLE_TYPE;
}

function isTimeRangeCompatible(embeddable: IEmbeddable) {
const isInputControl =
isVisualizeEmbeddable(embeddable) &&
(embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'input_control_vis';

const isMarkdown =
isVisualizeEmbeddable(embeddable) &&
(embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'markdown';

const isImage = embeddable.type === 'image';

return Boolean(
embeddable && hasTimeRange(embeddable) && !isInputControl && !isMarkdown && !isImage
);
}

export const CustomizePanelEditor = (props: CustomizePanelProps) => {
const { onClose, embeddable, dateFormat } = props;
const timeRangeCompatible = isTimeRangeCompatible(embeddable);
const { onClose, embeddable, dateFormat, timeRangeCompatible } = props;
const editMode = embeddable.getInput().viewMode === ViewMode.EDIT;
const [hideTitle, setHideTitle] = useState(embeddable.getInput().hidePanelTitles);
const [panelDescription, setPanelDescription] = useState(
embeddable.getInput().description ?? embeddable.getOutput().defaultDescription
Expand Down Expand Up @@ -119,6 +94,119 @@ export const CustomizePanelEditor = (props: CustomizePanelProps) => {
onClose();
};

const renderCustomTitleComponent = () => {
if (!editMode) return null;

return (
<>
<EuiFormRow>
<EuiSwitch
checked={!hideTitle}
data-test-subj="customEmbeddablePanelHideTitleSwitch"
disabled={!editMode}
id="hideTitle"
label={
<FormattedMessage
defaultMessage="Show panel title"
id="embeddableApi.customizePanel.flyout.optionsMenuForm.showTitle"
/>
}
onChange={(e) => setHideTitle(!e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.panelTitleFormRowLabel"
defaultMessage="Panel title"
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelTitleButton"
onClick={() => setPanelTitle(embeddable.getOutput().defaultTitle)}
disabled={hideTitle || !editMode}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonAriaLabel',
{
defaultMessage: 'Reset panel title',
}
)}
>
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
}
>
<EuiFieldText
id="panelTitleInput"
className="panelTitleInputText"
data-test-subj="customEmbeddablePanelTitleInput"
name="title"
type="text"
disabled={hideTitle || !editMode}
value={panelTitle ?? ''}
onChange={(e) => setPanelTitle(e.target.value)}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.panelTitleInputAriaLabel',
{
defaultMessage: 'Enter a custom title for your panel',
}
)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.panelDescriptionFormRowLabel"
defaultMessage="Panel description"
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelDescriptionButton"
onClick={() => {
setPanelDescription(embeddable.getOutput().defaultDescription);
}}
disabled={hideTitle || !editMode}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomDescriptionButtonAriaLabel',
{
defaultMessage: 'Reset panel description',
}
)}
>
<FormattedMessage
id="embeddableApi.customizePanel.modal.optionsMenuForm.resetCustomDescriptionButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
}
>
<EuiTextArea
id="panelDescriptionInput"
className="panelDescriptionInputText"
data-test-subj="customEmbeddablePanelDescriptionInput"
disabled={hideTitle || !editMode}
name="description"
value={panelDescription ?? ''}
onChange={(e) => setPanelDescription(e.target.value)}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.panelDescriptionAriaLabel',
{
defaultMessage: 'Enter a custom description for your panel',
}
)}
/>
</EuiFormRow>
</>
);
};

const renderCustomTimeRangeComponent = () => {
if (!timeRangeCompatible) return null;

Expand Down Expand Up @@ -178,109 +266,7 @@ export const CustomizePanelEditor = (props: CustomizePanelProps) => {
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiForm>
<EuiFormRow>
<EuiSwitch
checked={!hideTitle}
data-test-subj="customEmbeddablePanelHideTitleSwitch"
id="hideTitle"
label={
<FormattedMessage
defaultMessage="Show panel title"
id="embeddableApi.customizePanel.flyout.optionsMenuForm.showTitle"
/>
}
onChange={(e) => setHideTitle(!e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.panelTitleFormRowLabel"
defaultMessage="Panel title"
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelTitleButton"
onClick={() => setPanelTitle(embeddable.getOutput().defaultTitle)}
disabled={hideTitle}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonAriaLabel',
{
defaultMessage: 'Reset panel title',
}
)}
>
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
}
>
<EuiFieldText
id="panelTitleInput"
className="panelTitleInputText"
data-test-subj="customEmbeddablePanelTitleInput"
name="title"
type="text"
disabled={hideTitle}
value={panelTitle ?? ''}
onChange={(e) => setPanelTitle(e.target.value)}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.panelTitleInputAriaLabel',
{
defaultMessage: 'Enter a custom title for your panel',
}
)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="embeddableApi.customizePanel.flyout.optionsMenuForm.panelDescriptionFormRowLabel"
defaultMessage="Panel description"
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelDescriptionButton"
onClick={() => {
setPanelDescription(embeddable.getOutput().defaultDescription);
}}
disabled={hideTitle}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.resetCustomDescriptionButtonAriaLabel',
{
defaultMessage: 'Reset panel description',
}
)}
>
<FormattedMessage
id="embeddableApi.customizePanel.modal.optionsMenuForm.resetCustomDescriptionButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
}
>
<EuiTextArea
id="panelDescriptionInput"
className="panelDescriptionInputText"
data-test-subj="customEmbeddablePanelDescriptionInput"
disabled={hideTitle}
name="description"
value={panelDescription ?? ''}
onChange={(e) => setPanelDescription(e.target.value)}
aria-label={i18n.translate(
'embeddableApi.customizePanel.flyout.optionsMenuForm.panelDescriptionAriaLabel',
{
defaultMessage: 'Enter a custom description for your panel',
}
)}
/>
</EuiFormRow>
{renderCustomTitleComponent()}
{renderCustomTimeRangeComponent()}
</EuiForm>
</EuiFlyoutBody>
Expand Down
Loading

0 comments on commit 49ba0a4

Please sign in to comment.