Skip to content

Commit

Permalink
Use toast instead of tour
Browse files Browse the repository at this point in the history
  • Loading branch information
nickpeihl committed Feb 9, 2024
1 parent d19a28b commit 8173e16
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,7 @@ import {
SortableContext,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import {
EuiButtonEmpty,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiTourStep,
} from '@elastic/eui';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import classNames from 'classnames';
import React, { useEffect, useMemo, useState } from 'react';
import { TypedUseSelectorHook, useSelector } from 'react-redux';
Expand All @@ -54,19 +47,15 @@ export const ControlGroup = () => {
const viewMode = contextSelect((state) => state.explicitInput.viewMode);
const controlStyle = contextSelect((state) => state.explicitInput.controlStyle);
const showAddButton = contextSelect((state) => state.componentState.showAddButton);
const invalidSelectionsControlId = contextSelect(
(state) => state.componentState.invalidSelectionsControlId
const controlsHaveInvalidSelections = contextSelect(
(state) => state.componentState.controlsHaveInvalidSelections
);

const [renderTourStep, setRenderTourStep] = useState(false);
useEffect(() => {
/**
* This forces the tour step to get unmounted so that it can attach to the new invalid
* control - otherwise, the anchor will remain attached to the old invalid control
*/
setRenderTourStep(false);
setTimeout(() => setRenderTourStep(true), 100);
}, [invalidSelectionsControlId]);
if (controlsHaveInvalidSelections) {
controlGroup.showInvalidSelectionsToast();
}
}, [controlGroup, controlsHaveInvalidSelections]);

const isEditable = viewMode === ViewMode.EDIT;

Expand Down Expand Up @@ -137,39 +126,6 @@ export const ControlGroup = () => {
alignItems="center"
data-test-subj="controls-group"
>
{renderTourStep && invalidSelectionsControlId && (
<EuiTourStep
anchor={`#controlFrame--${invalidSelectionsControlId}`}
content={'Content'}
isStepOpen={true}
minWidth={300}
onFinish={() => {}}
step={1}
stepsTotal={1}
repositionOnScroll
maxWidth={300}
panelPaddingSize="m"
display="block"
title={ControlGroupStrings.invalidControlWarning.getTourStepTitle(
panels[invalidSelectionsControlId].type
)}
anchorPosition="downCenter"
footerAction={
<EuiButtonEmpty
size="xs"
flush="right"
color="text"
data-test-subj="invalidSelectionsPopoverDismissButton"
onClick={() => {
// set cookie to dismiss warning for **all** control types
}}
>
{/* TODO: i18n this */}
Do not show again
</EuiButtonEmpty>
}
/>
)}
<EuiFlexItem>
<DndContext
onDragStart={({ active }) => setDraggingId(active.id)}
Expand Down
31 changes: 12 additions & 19 deletions src/plugins/controls/public/control_group/control_group_strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,19 @@ import { RANGE_SLIDER_CONTROL } from '../range_slider';

export const ControlGroupStrings = {
invalidControlWarning: {
getTourStepTitle: (controlType: string) => {
switch (controlType) {
case RANGE_SLIDER_CONTROL: {
return i18n.translate(
'controls.controlGroup.invalidControlWarning.tourStepTitle.rangeSlider',
{
defaultMessage: 'You have selected an invalid range',
}
);
}
default: {
return i18n.translate(
'controls.controlGroup.invalidControlWarning.tourStepTitle.default',
{
defaultMessage: 'You have invalid selections',
}
);
}
title: i18n.translate('controls.controlGroup.invalidControlWarning.title', {
defaultMessage: 'Selections are returning no results',
}),
text: i18n.translate('controls.controlGroup.invalidControlWarning.text', {
defaultMessage:
'Some control selections are returning no results. Remove them for complete results.',
}),
dismissButtonLabel: i18n.translate(
'controls.controlGroup.invalidControlWarning.dismissButtonLabel',
{
defaultMessage: 'Do not show again',
}
},
),
},
manageControl: {
getFlyoutCreateTitle: () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { compareFilters, COMPARE_ALL_OPTIONS, Filter, uniqFilters } from '@kbn/es-query';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { isEqual, pick } from 'lodash';
import React, { createContext, useContext } from 'react';
import ReactDOM from 'react-dom';
import { Provider, TypedUseSelectorHook, useSelector } from 'react-redux';
import { BehaviorSubject, merge, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, skip } from 'rxjs/operators';

import { OverlayRef } from '@kbn/core/public';
import { OverlayRef, Toast } from '@kbn/core/public';
import { Container, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
Expand Down Expand Up @@ -55,6 +58,8 @@ import {
controlOrdersAreEqual,
} from './control_group_chaining_system';
import { getNextPanelOrder } from './control_group_helpers';
import { ControlGroupStrings } from '../control_group_strings';
import { ControlsStorageService } from '../../services/storage/types';

let flyoutRef: OverlayRef | undefined;
export const setFlyoutRef = (newRef: OverlayRef | undefined) => {
Expand Down Expand Up @@ -86,12 +91,15 @@ export class ControlGroupContainer extends Container<

private initialized$ = new BehaviorSubject(false);

private storageService: ControlsStorageService;

private subscriptions: Subscription = new Subscription();
private domNode?: HTMLElement;
private recalculateFilters$: Subject<null>;
private relevantDataViewId?: string;
private lastUsedDataViewId?: string;
private invalidSelectionsState: { [childId: string]: boolean };
private invalidSelectionsToast?: Toast;

public diffingSubscription: Subscription = new Subscription();

Expand Down Expand Up @@ -128,6 +136,8 @@ export class ControlGroupContainer extends Container<
ControlGroupChainingSystems[initialInput.chainingSystem]?.getContainerSettings(initialInput)
);

({ storage: this.storageService } = pluginServices.getServices());

this.recalculateFilters$ = new Subject();
this.onFiltersPublished$ = new Subject<Filter[]>();
this.onControlRemoved$ = new Subject<string>();
Expand Down Expand Up @@ -170,6 +180,51 @@ export class ControlGroupContainer extends Container<
this.fieldFilterPredicate = fieldFilterPredicate;
}

public canShowInvalidSelectionsWarning = () =>
this.storageService.getShowInvalidSelectionWarning() ?? true;

public supressInvalidSelectionsWarning = () => {
this.storageService.setShowInvalidSelectionWarning(false);
};

public showInvalidSelectionsToast = () => {
if (!this.canShowInvalidSelectionsWarning()) return;
const {
core: { notifications, theme, i18n },
} = pluginServices.getServices();

// remove any existing toasts to avoid a toast storm
if (this.invalidSelectionsToast) {
notifications.toasts.remove(this.invalidSelectionsToast);
}

this.invalidSelectionsToast = notifications.toasts.add({
title: ControlGroupStrings.invalidControlWarning.title,
text: toMountPoint(
<>
<p>{ControlGroupStrings.invalidControlWarning.text}</p>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="xs"
color="text"
onClick={() => {
this.supressInvalidSelectionsWarning();
if (this.invalidSelectionsToast) {
notifications.toasts.remove(this.invalidSelectionsToast);
}
}}
>
{ControlGroupStrings.invalidControlWarning.dismissButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</>,
{ theme, i18n }
),
});
};

public reportInvalidSelections = ({
id,
hasInvalidSelections,
Expand All @@ -184,9 +239,8 @@ export class ControlGroupContainer extends Container<
).idsInOrder.filter((childId) => {
return this.invalidSelectionsState[childId];
});
this.dispatch.setInvalidSelectionsControlId(
childrenWithInvalidSelections.length > 0 ? childrenWithInvalidSelections[0] : undefined
);

this.dispatch.setControlsHaveInvalidSelections(childrenWithInvalidSelections.length > 0);
};

private setupSubscriptions = () => {
Expand Down Expand Up @@ -242,6 +296,12 @@ export class ControlGroupContainer extends Container<
}
}

public reload() {
// reset invalid selections state on reload
this.dispatch.setControlsHaveInvalidSelections(undefined);
super.reload();
}

public getPersistableInput: () => PersistableControlGroupInput & { id: string } = () => {
const input = this.getInput();
return pick(input, [...persistableControlGroupInputKeys, 'id']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export const controlGroupReducers = {
) => {
state.componentState.lastSavedInput = action.payload;
},
setInvalidSelectionsControlId: (
setControlsHaveInvalidSelections: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupComponentState['invalidSelectionsControlId']>
action: PayloadAction<ControlGroupComponentState['controlsHaveInvalidSelections']>
) => {
state.componentState.invalidSelectionsControlId = action.payload;
state.componentState.controlsHaveInvalidSelections = action.payload;
},
setControlStyle: (
state: WritableDraft<ControlGroupReduxState>,
Expand Down
1 change: 1 addition & 0 deletions src/plugins/controls/public/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ControlGroupSettings {
export type ControlGroupComponentState = ControlGroupSettings & {
lastSavedInput: PersistableControlGroupInput;
invalidSelectionsControlId?: string;
controlsHaveInvalidSelections?: boolean;
};

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,6 @@ export const OptionsListControl = ({
};
}, [optionsList]);

const hasInvalidSelections = useMemo(
() => invalidSelections && invalidSelections?.length > 0,
[invalidSelections]
);

// show warning if there are invalid selections and the warning is not supressed
useEffect(() => {
if (hasInvalidSelections && optionsList.canShowInvalidSelectionsWarning()) {
optionsList.dispatch.setInvalidSelectionWarningOpen(true);
}
}, [hasInvalidSelections, optionsList]);

// debounce loading state so loading doesn't flash when user types
const [debouncedLoading, setDebouncedLoading] = useState(true);
const debounceSetLoading = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
import { pluginServices } from '../../services';
import { ControlsDataViewsService } from '../../services/data_views/types';
import { ControlsOptionsListService } from '../../services/options_list/types';
import { ControlsStorageService } from '../../services/storage/types';
import { IClearableControl } from '../../types';
import { OptionsListControl } from '../components/options_list_control';
import { getDefaultComponentState, optionsListReducers } from '../options_list_reducers';
Expand Down Expand Up @@ -93,7 +92,6 @@ export class OptionsListEmbeddable
// Controls services
private dataViewsService: ControlsDataViewsService;
private optionsListService: ControlsOptionsListService;
private storageService: ControlsStorageService;

// Internal data fetching state for this input control.
private typeaheadSubject: Subject<string> = new Subject<string>();
Expand All @@ -120,11 +118,8 @@ export class OptionsListEmbeddable
this.parent = parent as ControlGroupContainer;

// Destructure controls services
({
dataViews: this.dataViewsService,
optionsList: this.optionsListService,
storage: this.storageService,
} = pluginServices.getServices());
({ dataViews: this.dataViewsService, optionsList: this.optionsListService } =
pluginServices.getServices());

this.typeaheadSubject = new Subject<string>();
this.loadMoreSubject = new Subject<number>();
Expand Down Expand Up @@ -422,13 +417,6 @@ export class OptionsListEmbeddable
return [newFilter];
};

public canShowInvalidSelectionsWarning = () =>
this.storageService.getShowInvalidSelectionWarning() ?? true;

public supressInvalidSelectionsWarning = () => {
this.storageService.setShowInvalidSelectionWarning(false);
};

public clearSelections() {
this.dispatch.clearSelections({});
this.reportInvalidSelections(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@
import { debounce } from 'lodash';
import React, { FC, useState, useMemo, useEffect, useCallback, useRef } from 'react';

import {
EuiRangeTick,
EuiDualRange,
EuiDualRangeProps,
EuiTourStep,
EuiButtonEmpty,
} from '@elastic/eui';
import { EuiRangeTick, EuiDualRange, EuiDualRangeProps } from '@elastic/eui';

import { RangeValue } from '../../../common/range_slider/types';
import { useRangeSlider } from '../embeddable/range_slider_embeddable';
Expand All @@ -24,7 +18,6 @@ import { ControlError } from '../../control_group/component/control_error_compon
import './range_slider.scss';
import { MIN_POPOVER_WIDTH } from '../../constants';
import { useFieldFormatter } from '../../hooks/use_field_formatter';
import { RangeSliderStrings } from './range_slider_strings';

export const RangeSliderControl: FC = () => {
/** Controls Services Context */
Expand All @@ -35,15 +28,12 @@ export const RangeSliderControl: FC = () => {
const id = rangeSlider.select((state) => state.explicitInput.id);
const value = rangeSlider.select((state) => state.explicitInput.value);

// Embeddable cmponent state
// Embeddable component state
const min = rangeSlider.select((state) => state.componentState.min);
const max = rangeSlider.select((state) => state.componentState.max);
const error = rangeSlider.select((state) => state.componentState.error);
const fieldSpec = rangeSlider.select((state) => state.componentState.field);
const isInvalid = rangeSlider.select((state) => state.componentState.isInvalid);
const showInvalidSelectionWarning = rangeSlider.select(
(state) => state.componentState.showInvalidRangeWarning
);

// Embeddable output
const isLoading = rangeSlider.select((state) => state.output.loading);
Expand Down Expand Up @@ -75,13 +65,6 @@ export const RangeSliderControl: FC = () => {
return [Math.min(selectedMin, min), Math.max(selectedMax, max ?? Infinity)];
}, [min, max, value]);

// show warning if there are invalid selections and the warning is not supressed
useEffect(() => {
if (isInvalid && rangeSlider.canShowInvalidSelectionsWarning()) {
rangeSlider.dispatch.setInvalidRangeWarningOpen(true);
}
}, [isInvalid, rangeSlider]);

/**
* The following `useEffect` ensures that the changes to the value that come from the embeddable (for example,
* from the `reset` button on the dashboard or via chaining) are reflected in the displayed value
Expand Down
Loading

0 comments on commit 8173e16

Please sign in to comment.