Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Controls] Select Relevant Data View ID #128440

Merged
merged 10 commits into from Mar 28, 2022
Expand Up @@ -56,6 +56,8 @@ interface EditControlProps {
removeControl?: () => void;
updateTitle: (title?: string) => void;
updateWidth: (newWidth: ControlWidth) => void;
getRelevantDataViewId?: () => string | undefined;
setLastUsedDataViewId?: (newDataViewId: string) => void;
onTypeEditorChange: (partial: Partial<ControlInput>) => void;
}

Expand All @@ -70,6 +72,8 @@ export const ControlEditor = ({
updateTitle,
updateWidth,
onTypeEditorChange,
getRelevantDataViewId,
setLastUsedDataViewId,
}: EditControlProps) => {
const { controls } = pluginServices.getServices();
const { getControlTypes, getControlFactory } = controls;
Expand All @@ -87,6 +91,8 @@ export const ControlEditor = ({
const ControlTypeEditor = (factory as IEditableControlFactory).controlEditorComponent;
return ControlTypeEditor ? (
<ControlTypeEditor
getRelevantDataViewId={getRelevantDataViewId}
setLastUsedDataViewId={setLastUsedDataViewId}
onChange={onTypeEditorChange}
setValidState={setControlEditorValid}
initialInput={embeddable?.getInput()}
Expand Down
Expand Up @@ -22,6 +22,8 @@ export interface CreateControlButtonProps {
defaultControlWidth?: ControlWidth;
updateDefaultWidth: (defaultControlWidth: ControlWidth) => void;
addNewEmbeddable: (type: string, input: Omit<ControlInput, 'id'>) => void;
setLastUsedDataViewId?: (newDataViewId: string) => void;
getRelevantDataViewId?: () => string | undefined;
buttonType: CreateControlButtonTypes;
closePopover?: () => void;
}
Expand All @@ -37,6 +39,8 @@ export const CreateControlButton = ({
addNewEmbeddable,
buttonType,
closePopover,
setLastUsedDataViewId,
getRelevantDataViewId,
}: CreateControlButtonProps) => {
// Controls Services Context
const { overlays, controls } = pluginServices.getServices();
Expand Down Expand Up @@ -72,6 +76,8 @@ export const CreateControlButton = ({
toMountPoint(
<PresentationUtilProvider>
<ControlEditor
setLastUsedDataViewId={setLastUsedDataViewId}
getRelevantDataViewId={getRelevantDataViewId}
isCreate={true}
width={defaultControlWidth ?? DEFAULT_CONTROL_WIDTH}
updateTitle={(newTitle) => (inputToReturn.title = newTitle)}
Expand Down
Expand Up @@ -20,6 +20,7 @@ import { IEditableControlFactory, ControlInput } from '../../types';
import { controlGroupReducers } from '../state/control_group_reducers';
import { EmbeddableFactoryNotFoundError } from '../../../../embeddable/public';
import { useReduxContainerContext } from '../../../../presentation_util/public';
import { ControlGroupContainer } from '../embeddable/control_group_container';

export const EditControlButton = ({ embeddableId }: { embeddableId: string }) => {
// Controls Services Context
Expand Down Expand Up @@ -53,6 +54,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
const panel = panels[embeddableId];
const factory = getControlFactory(panel.type);
const embeddable = await untilEmbeddableLoaded(embeddableId);
const controlGroup = embeddable.getRoot() as ControlGroupContainer;

let inputToReturn: Partial<ControlInput> = {};

Expand Down Expand Up @@ -93,6 +95,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
title={embeddable.getTitle()}
onCancel={() => onCancel(flyoutInstance)}
updateTitle={(newTitle) => (inputToReturn.title = newTitle)}
setLastUsedDataViewId={(lastUsed) => controlGroup.setLastUsedDataViewId(lastUsed)}
updateWidth={(newWidth) => dispatch(setControlWidth({ width: newWidth, embeddableId }))}
onTypeEditorChange={(partialInput) =>
(inputToReturn = { ...inputToReturn, ...partialInput })
Expand Down
Expand Up @@ -81,6 +81,21 @@ export class ControlGroupContainer extends Container<
private childOrderCache: ChildEmbeddableOrderCache;
private recalculateFilters$: Subject<null>;

private relevantDataViewId?: string;
private lastUsedDataViewId?: string;

public setLastUsedDataViewId = (lastUsedDataViewId: string) => {
this.lastUsedDataViewId = lastUsedDataViewId;
};

public setRelevantDataViewId = (newRelevantDataViewId: string) => {
this.relevantDataViewId = newRelevantDataViewId;
};

public getMostRelevantDataViewId = () => {
return this.lastUsedDataViewId ?? this.relevantDataViewId;
};

/**
* Returns a button that allows controls to be created externally using the embeddable
* @param buttonType Controls the button styling
Expand All @@ -99,6 +114,8 @@ export class ControlGroupContainer extends Container<
updateDefaultWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })}
addNewEmbeddable={(type, input) => this.addNewEmbeddable(type, input)}
closePopover={closePopover}
getRelevantDataViewId={() => this.getMostRelevantDataViewId()}
setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)}
/>
);
};
Expand Down
Expand Up @@ -38,6 +38,8 @@ export const OptionsListEditor = ({
initialInput,
setValidState,
setDefaultTitle,
getRelevantDataViewId,
setLastUsedDataViewId,
}: ControlEditorProps<OptionsListEmbeddableInput>) => {
// Controls Services Context
const { dataViews } = pluginServices.getHooks();
Expand All @@ -54,7 +56,8 @@ export const OptionsListEditor = ({
if (state.fieldName) setDefaultTitle(state.fieldName);
(async () => {
const dataViewListItems = await getIdsWithTitle();
const initialId = initialInput?.dataViewId ?? (await getDefaultId());
const initialId =
initialInput?.dataViewId ?? getRelevantDataViewId?.() ?? (await getDefaultId());
let dataView: DataView | undefined;
if (initialId) {
onChange({ dataViewId: initialId });
Expand All @@ -81,6 +84,7 @@ export const OptionsListEditor = ({
dataViews={state.dataViewListItems}
selectedDataViewId={dataView?.id}
onChangeDataViewId={(dataViewId) => {
setLastUsedDataViewId?.(dataViewId);
onChange({ dataViewId });
get(dataViewId).then((newDataView) =>
setState((s) => ({ ...s, dataView: newDataView }))
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/controls/public/types.ts
Expand Up @@ -46,6 +46,8 @@ export interface IEditableControlFactory<T extends ControlInput = ControlInput>
}
export interface ControlEditorProps<T extends ControlInput = ControlInput> {
initialInput?: Partial<T>;
getRelevantDataViewId?: () => string | undefined;
setLastUsedDataViewId?: (newId: string) => void;
onChange: (partial: Partial<T>) => void;
setValidState: (valid: boolean) => void;
setDefaultTitle: (defaultTitle: string) => void;
Expand Down
Expand Up @@ -235,8 +235,12 @@ export const useDashboardAppState = ({
const dataViewsSubscription = syncDashboardDataViews({
dashboardContainer,
dataViews: dashboardBuildContext.dataViews,
onUpdateDataViews: (newDataViews: DataView[]) =>
setDashboardAppState((s) => ({ ...s, dataViews: newDataViews })),
onUpdateDataViews: (newDataViews: DataView[]) => {
if (newDataViews.length > 0 && newDataViews[0].id) {
dashboardContainer.controlGroup?.setRelevantDataViewId(newDataViews[0].id);
}
setDashboardAppState((s) => ({ ...s, dataViews: newDataViews }));
},
});

/**
Expand Down
Expand Up @@ -17,6 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const filterBar = getService('filterBar');
const testSubjects = getService('testSubjects');
const dashboardAddPanel = getService('dashboardAddPanel');
const dashboardPanelActions = getService('dashboardPanelActions');
const { dashboardControls, timePicker, common, dashboard, header } = getPageObjects([
'dashboardControls',
'timePicker',
Expand All @@ -33,9 +34,44 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await timePicker.setDefaultDataRange();
});

describe('Options List Control Editor selects relevant data views', async () => {
it('selects the default data view when the dashboard is blank', async () => {
expect(await dashboardControls.optionsListEditorGetCurrentDataView(true)).to.eql(
'logstash-*'
);
});

it('selects a relevant data view based on the panels on the dashboard', async () => {
await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie');
await dashboard.waitForRenderComplete();
expect(await dashboardControls.optionsListEditorGetCurrentDataView(true)).to.eql(
'animals-*'
);
await dashboardPanelActions.removePanelByTitle('Rendering Test: animal sounds pie');
await dashboard.waitForRenderComplete();
expect(await dashboardControls.optionsListEditorGetCurrentDataView(true)).to.eql(
'logstash-*'
);
});

it('selects the last used data view by default', async () => {
await dashboardControls.createOptionsListControl({
dataViewTitle: 'animals-*',
fieldName: 'sound.keyword',
});
expect(await dashboardControls.optionsListEditorGetCurrentDataView(true)).to.eql(
'animals-*'
);
await dashboardControls.deleteAllControls();
});
});

describe('Options List Control creation and editing experience', async () => {
it('can add a new options list control from a blank state', async () => {
await dashboardControls.createOptionsListControl({ fieldName: 'machine.os.raw' });
await dashboardControls.createOptionsListControl({
dataViewTitle: 'logstash-*',
fieldName: 'machine.os.raw',
});
expect(await dashboardControls.getControlsCount()).to.be(1);
});

Expand Down
16 changes: 15 additions & 1 deletion test/functional/page_objects/dashboard_page_controls.ts
Expand Up @@ -336,9 +336,12 @@ export class DashboardPageControls extends FtrService {
await this.testSubjects.click(`control-editor-save`);
}

public async controlEditorCancel() {
public async controlEditorCancel(confirm?: boolean) {
this.log.debug(`Canceling changes in control editor`);
await this.testSubjects.click(`control-editor-cancel`);
if (confirm) {
await this.common.clickConfirmOnModal();
}
}

// Options List editor functions
Expand All @@ -364,6 +367,17 @@ export class DashboardPageControls extends FtrService {
await this.controlEditorSave();
}

public async optionsListEditorGetCurrentDataView(openAndCloseFlyout?: boolean) {
if (openAndCloseFlyout) {
await this.openCreateControlFlyout(OPTIONS_LIST_CONTROL);
}
const dataViewName = (await this.testSubjects.find('open-data-view-picker')).getVisibleText();
if (openAndCloseFlyout) {
await this.controlEditorCancel(true);
}
return dataViewName;
}

public async optionsListEditorSetDataView(dataViewTitle: string) {
this.log.debug(`Setting options list data view to ${dataViewTitle}`);
await this.testSubjects.click('open-data-view-picker');
Expand Down