Skip to content

Commit

Permalink
Merge pull request #3 from Heenawter/try-to-clean-up-tour-stuff
Browse files Browse the repository at this point in the history
Suggestion for tour stuff
  • Loading branch information
nickpeihl authored Feb 12, 2024
2 parents 56f5d26 + 8173e16 commit 779acf5
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@

import '../control_group.scss';

import {
arrayMove,
SortableContext,
rectSortingStrategy,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
KeyboardSensor,
LayoutMeasuringStrategy,
PointerSensor,
useSensor,
useSensors,
LayoutMeasuringStrategy,
} from '@dnd-kit/core';
import {
arrayMove,
rectSortingStrategy,
SortableContext,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import classNames from 'classnames';
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';

import { ViewMode } from '@kbn/embeddable-plugin/public';

import { ControlGroupReduxState } from '../types';
import { ControlGroupStrings } from '../control_group_strings';
import { ControlClone, SortableControl } from './control_group_sortable_item';
import { useControlGroupContainer } from '../embeddable/control_group_container';
import { ControlGroupReduxState } from '../types';
import { ControlClone, SortableControl } from './control_group_sortable_item';

const contextSelect = useSelector as TypedUseSelectorHook<ControlGroupReduxState>;

Expand All @@ -47,6 +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 controlsHaveInvalidSelections = contextSelect(
(state) => state.componentState.controlsHaveInvalidSelections
);

useEffect(() => {
if (controlsHaveInvalidSelections) {
controlGroup.showInvalidSelectionsToast();
}
}, [controlGroup, controlsHaveInvalidSelections]);

const isEditable = viewMode === ViewMode.EDIT;

Expand Down
15 changes: 15 additions & 0 deletions src/plugins/controls/public/control_group/control_group_strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import { i18n } from '@kbn/i18n';
import { RANGE_SLIDER_CONTROL } from '../range_slider';

export const ControlGroupStrings = {
invalidControlWarning: {
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: () =>
i18n.translate('controls.controlGroup.manageControl.createFlyoutTitle', {
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,11 +91,16 @@ 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();

// state management
Expand Down Expand Up @@ -126,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 @@ -153,6 +165,10 @@ export class ControlGroupContainer extends Container<

this.store = reduxEmbeddableTools.store;

this.invalidSelectionsState = this.getChildIds().reduce((prev, id) => {
return { ...prev, [id]: false };
}, {});

// when all children are ready setup subscriptions
this.untilAllChildrenReady().then(() => {
this.recalculateDataViews();
Expand All @@ -164,6 +180,69 @@ 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,
}: {
id: string;
hasInvalidSelections: boolean;
}) => {
this.invalidSelectionsState = { ...this.invalidSelectionsState, [id]: hasInvalidSelections };

const childrenWithInvalidSelections = cachedChildEmbeddableOrder(
this.getInput().panels
).idsInOrder.filter((childId) => {
return this.invalidSelectionsState[childId];
});

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

private setupSubscriptions = () => {
/**
* refresh control order cache and make all panels refreshInputFromParent whenever panel orders change
Expand Down Expand Up @@ -201,7 +280,9 @@ export class ControlGroupContainer extends Container<
* debounce output recalculation
*/
this.subscriptions.add(
this.recalculateFilters$.pipe(debounceTime(10)).subscribe(() => this.recalculateFilters())
this.recalculateFilters$.pipe(debounceTime(10)).subscribe(() => {
this.recalculateFilters();
})
);
};

Expand All @@ -211,9 +292,16 @@ export class ControlGroupContainer extends Container<
} = this.getState();
if (!persistableControlGroupInputIsEqual(this.getPersistableInput(), lastSavedInput)) {
this.updateInput(lastSavedInput);
this.reload(); // this forces the children to update their inputs + perform validation as necessary
}
}

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 Expand Up @@ -284,13 +372,14 @@ export class ControlGroupContainer extends Container<
private recalculateFilters = () => {
const allFilters: Filter[] = [];
let timeslice;
Object.values(this.children).map((child) => {
Object.values(this.children).map((child: ControlEmbeddable) => {
const childOutput = child.getOutput() as ControlOutput;
allFilters.push(...(childOutput?.filters ?? []));
if (childOutput.timeslice) {
timeslice = childOutput.timeslice;
}
});

// if filters are different, publish them
if (
!compareFilters(this.output.filters ?? [], allFilters ?? [], COMPARE_ALL_OPTIONS) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export const controlGroupReducers = {
) => {
state.componentState.lastSavedInput = action.payload;
},
setControlsHaveInvalidSelections: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupComponentState['controlsHaveInvalidSelections']>
) => {
state.componentState.controlsHaveInvalidSelections = action.payload;
},
setControlStyle: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['controlStyle']>
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/controls/public/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface ControlGroupSettings {

export type ControlGroupComponentState = ControlGroupSettings & {
lastSavedInput: PersistableControlGroupInput;
invalidSelectionsControlId?: string;
controlsHaveInvalidSelections?: boolean;
};

export {
Expand Down
Loading

0 comments on commit 779acf5

Please sign in to comment.