Skip to content

Commit

Permalink
[8.6] [Dashboard] [Controls] Allow control changes to be discarded (#…
Browse files Browse the repository at this point in the history
…147482) (#147819)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Dashboard] [Controls] Allow control changes to be discarded
(#147482)](#147482)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Hannah
Mudge","email":"Heenawter@users.noreply.github.com"},"sourceCommit":{"committedDate":"2022-12-19T21:55:31Z","message":"[Dashboard]
[Controls] Allow control changes to be discarded (#147482)\n\nCloses
#147293
Summary\r\n\r\nBefore this change, the Redux state `explicitInput` was
getting out of\r\nsync with the embeddable `explicitInput` in scenarios
where the new\r\n`explicitInput` was missing a key that the old
`explicitInput` had -\r\ntherefore, because they were out of sync, the
changes that **should**\r\nhave been discarded kept getting injected
back into the embeddable\r\n`explicitInput`, which made it impossible to
actually discard anything\r\nunless the key existed in both the before
and after state.\r\n\r\nThis PR fixes this by replacing the entire Redux
state `explicitInput`\r\nwith the embeddable `explicitInput` rather than
spreading the new value.\r\nIt also fixes a bug with the time slider
control where changes to the\r\nembeddable's input were not reflected
properly in the control's state,\r\nso nothing could be discarded even
after the initial bug was fixed.\r\n\r\n#### Further Explanation
\r\n\r\nWhen a control is first created, all the optional properties of
the\r\nexplicit input do not yet exist - for example, when creating an
options\r\nlist control, the `selections` key does not exist in the
`explicitInput`\r\nuntil a selection is made. Therefore, imagine the
following scenario:\r\n\r\n1. You create an options list control (where
the `selections` key does\r\nnot exist) and save the dashboard\r\n2. You
make some selections, which causes `unsaved changes` because
the\r\n`selections` key now exists and is equal to an array\r\n3. You
switch to view mode and choose to discard your changes,
thus\r\n(supposedly) removing the `selections` key from the
`explicitInput`\r\nobject once again\r\n\r\nUnfortunately, the Redux
embeddable state for each control was **not**\r\naccurately removing the
`selections` key as expected - this was because,\r\nwhen trying to
update the `explicitInput` via the old\r\n`updateEmbeddableReduxInput`,
the new value was **spread** on top of the\r\nolder value rather than
replacing it. In a simplified scenario, this\r\nresulted in something
like this:\r\n\r\n```typescript\r\nconst oldExplicitInput = { id:
'test_id', selections: ['test selection'] };\r\nconst newExplicitInput =
{ id: 'test_id' }\r\nconst result = { ...oldExplicitInput,
...newExplicitInput };\r\n```\r\n\r\nIn this code, because
`newExplicitInput` does not have the `selections`\r\nkey, `result` will
equal `{ id: 'test_id', selections: ['test\r\nselection'] }` - this is
not the behaviour we want! Instead, we wanted\r\nto replace the entire
old `explicitInput` with the new `explicitInput`.\r\nEffectively, that
is what this PR does.\r\n\r\nThanks to @ThomThomson for helping out with
finding the root cause of\r\nthis after I got lost :)\r\n\r\n### How to
Test\r\nFor both options list and range slider controls, \r\n1. Create a
control of the desired type\r\n2. Save the dashboard \r\n3. Make some
sort of change that causes unsaved changes - for example,\r\nmake a
selection or, if an options list control, set `exclude` to `true`\r\n4.
Switch to view mode, discarding the changes\r\n5. Ensure that the
changes made in step 3 are no longer applied ✅ \r\n6. Switch back to
edit mode\r\n7. Ensure that there are no `unsaved changes` ✅
\r\n\r\n#### Flaky Test
Runner\r\n\r\n<a\r\nhref=\"https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/1649\"><img\r\nsrc=\"https://user-images.githubusercontent.com/8698078/207701101-69cdfada-77c6-4510-b254-1fd1fa13af5c.png\"/></a>\r\n\r\n###
Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"7a6eac8d1bfe492b8dce83c7a0f47dc26706e388","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Dashboard","release_note:fix","Feature:Input
Control","Team:Presentation","loe:days","impact:high","Project:Controls","backport:prev-minor","v8.7.0"],"number":147482,"url":"#147482
[Controls] Allow control changes to be discarded (#147482)\n\nCloses
#147293
Summary\r\n\r\nBefore this change, the Redux state `explicitInput` was
getting out of\r\nsync with the embeddable `explicitInput` in scenarios
where the new\r\n`explicitInput` was missing a key that the old
`explicitInput` had -\r\ntherefore, because they were out of sync, the
changes that **should**\r\nhave been discarded kept getting injected
back into the embeddable\r\n`explicitInput`, which made it impossible to
actually discard anything\r\nunless the key existed in both the before
and after state.\r\n\r\nThis PR fixes this by replacing the entire Redux
state `explicitInput`\r\nwith the embeddable `explicitInput` rather than
spreading the new value.\r\nIt also fixes a bug with the time slider
control where changes to the\r\nembeddable's input were not reflected
properly in the control's state,\r\nso nothing could be discarded even
after the initial bug was fixed.\r\n\r\n#### Further Explanation
\r\n\r\nWhen a control is first created, all the optional properties of
the\r\nexplicit input do not yet exist - for example, when creating an
options\r\nlist control, the `selections` key does not exist in the
`explicitInput`\r\nuntil a selection is made. Therefore, imagine the
following scenario:\r\n\r\n1. You create an options list control (where
the `selections` key does\r\nnot exist) and save the dashboard\r\n2. You
make some selections, which causes `unsaved changes` because
the\r\n`selections` key now exists and is equal to an array\r\n3. You
switch to view mode and choose to discard your changes,
thus\r\n(supposedly) removing the `selections` key from the
`explicitInput`\r\nobject once again\r\n\r\nUnfortunately, the Redux
embeddable state for each control was **not**\r\naccurately removing the
`selections` key as expected - this was because,\r\nwhen trying to
update the `explicitInput` via the old\r\n`updateEmbeddableReduxInput`,
the new value was **spread** on top of the\r\nolder value rather than
replacing it. In a simplified scenario, this\r\nresulted in something
like this:\r\n\r\n```typescript\r\nconst oldExplicitInput = { id:
'test_id', selections: ['test selection'] };\r\nconst newExplicitInput =
{ id: 'test_id' }\r\nconst result = { ...oldExplicitInput,
...newExplicitInput };\r\n```\r\n\r\nIn this code, because
`newExplicitInput` does not have the `selections`\r\nkey, `result` will
equal `{ id: 'test_id', selections: ['test\r\nselection'] }` - this is
not the behaviour we want! Instead, we wanted\r\nto replace the entire
old `explicitInput` with the new `explicitInput`.\r\nEffectively, that
is what this PR does.\r\n\r\nThanks to @ThomThomson for helping out with
finding the root cause of\r\nthis after I got lost :)\r\n\r\n### How to
Test\r\nFor both options list and range slider controls, \r\n1. Create a
control of the desired type\r\n2. Save the dashboard \r\n3. Make some
sort of change that causes unsaved changes - for example,\r\nmake a
selection or, if an options list control, set `exclude` to `true`\r\n4.
Switch to view mode, discarding the changes\r\n5. Ensure that the
changes made in step 3 are no longer applied ✅ \r\n6. Switch back to
edit mode\r\n7. Ensure that there are no `unsaved changes` ✅
\r\n\r\n#### Flaky Test
Runner\r\n\r\n<a\r\nhref=\"https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/1649\"><img\r\nsrc=\"https://user-images.githubusercontent.com/8698078/207701101-69cdfada-77c6-4510-b254-1fd1fa13af5c.png\"/></a>\r\n\r\n###
Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"7a6eac8d1bfe492b8dce83c7a0f47dc26706e388"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/147482","number":147482,"mergeCommit":{"message":"[Dashboard]
[Controls] Allow control changes to be discarded (#147482)\n\nCloses
#147293
Summary\r\n\r\nBefore this change, the Redux state `explicitInput` was
getting out of\r\nsync with the embeddable `explicitInput` in scenarios
where the new\r\n`explicitInput` was missing a key that the old
`explicitInput` had -\r\ntherefore, because they were out of sync, the
changes that **should**\r\nhave been discarded kept getting injected
back into the embeddable\r\n`explicitInput`, which made it impossible to
actually discard anything\r\nunless the key existed in both the before
and after state.\r\n\r\nThis PR fixes this by replacing the entire Redux
state `explicitInput`\r\nwith the embeddable `explicitInput` rather than
spreading the new value.\r\nIt also fixes a bug with the time slider
control where changes to the\r\nembeddable's input were not reflected
properly in the control's state,\r\nso nothing could be discarded even
after the initial bug was fixed.\r\n\r\n#### Further Explanation
\r\n\r\nWhen a control is first created, all the optional properties of
the\r\nexplicit input do not yet exist - for example, when creating an
options\r\nlist control, the `selections` key does not exist in the
`explicitInput`\r\nuntil a selection is made. Therefore, imagine the
following scenario:\r\n\r\n1. You create an options list control (where
the `selections` key does\r\nnot exist) and save the dashboard\r\n2. You
make some selections, which causes `unsaved changes` because
the\r\n`selections` key now exists and is equal to an array\r\n3. You
switch to view mode and choose to discard your changes,
thus\r\n(supposedly) removing the `selections` key from the
`explicitInput`\r\nobject once again\r\n\r\nUnfortunately, the Redux
embeddable state for each control was **not**\r\naccurately removing the
`selections` key as expected - this was because,\r\nwhen trying to
update the `explicitInput` via the old\r\n`updateEmbeddableReduxInput`,
the new value was **spread** on top of the\r\nolder value rather than
replacing it. In a simplified scenario, this\r\nresulted in something
like this:\r\n\r\n```typescript\r\nconst oldExplicitInput = { id:
'test_id', selections: ['test selection'] };\r\nconst newExplicitInput =
{ id: 'test_id' }\r\nconst result = { ...oldExplicitInput,
...newExplicitInput };\r\n```\r\n\r\nIn this code, because
`newExplicitInput` does not have the `selections`\r\nkey, `result` will
equal `{ id: 'test_id', selections: ['test\r\nselection'] }` - this is
not the behaviour we want! Instead, we wanted\r\nto replace the entire
old `explicitInput` with the new `explicitInput`.\r\nEffectively, that
is what this PR does.\r\n\r\nThanks to @ThomThomson for helping out with
finding the root cause of\r\nthis after I got lost :)\r\n\r\n### How to
Test\r\nFor both options list and range slider controls, \r\n1. Create a
control of the desired type\r\n2. Save the dashboard \r\n3. Make some
sort of change that causes unsaved changes - for example,\r\nmake a
selection or, if an options list control, set `exclude` to `true`\r\n4.
Switch to view mode, discarding the changes\r\n5. Ensure that the
changes made in step 3 are no longer applied ✅ \r\n6. Switch back to
edit mode\r\n7. Ensure that there are no `unsaved changes` ✅
\r\n\r\n#### Flaky Test
Runner\r\n\r\n<a\r\nhref=\"https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/1649\"><img\r\nsrc=\"https://user-images.githubusercontent.com/8698078/207701101-69cdfada-77c6-4510-b254-1fd1fa13af5c.png\"/></a>\r\n\r\n###
Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"7a6eac8d1bfe492b8dce83c7a0f47dc26706e388"}}]}]
BACKPORT-->
  • Loading branch information
Heenawter committed Dec 19, 2022
1 parent 1a94e76 commit d868e36
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class TimeSliderControlEmbeddable extends Embeddable<
private getDateFormat: ControlsSettingsService['getDateFormat'];
private getTimezone: ControlsSettingsService['getTimezone'];
private timefilter: ControlsDataService['timefilter'];
private prevTimeRange: TimeRange | undefined;
private prevTimesliceAsPercentage: {
timesliceStartAsPercentageOfTimeRange?: number;
timesliceEndAsPercentageOfTimeRange?: number;
};
private readonly waitForControlOutputConsumersToLoad$;

private reduxEmbeddableTools: ReduxEmbeddableTools<
Expand Down Expand Up @@ -98,6 +103,10 @@ export class TimeSliderControlEmbeddable extends Embeddable<
)
: undefined;

this.prevTimesliceAsPercentage = {
timesliceStartAsPercentageOfTimeRange: this.getInput().timesliceStartAsPercentageOfTimeRange,
timesliceEndAsPercentageOfTimeRange: this.getInput().timesliceEndAsPercentageOfTimeRange,
};
this.syncWithTimeRange();
}

Expand All @@ -111,17 +120,34 @@ export class TimeSliderControlEmbeddable extends Embeddable<

private onInputChange() {
const input = this.getInput();
const { timesliceStartAsPercentageOfTimeRange, timesliceEndAsPercentageOfTimeRange } =
this.prevTimesliceAsPercentage ?? {};

if (!input.timeRange) {
return;
}

const nextBounds = this.timeRangeToBounds(input.timeRange);
const { actions, dispatch, getState } = this.reduxEmbeddableTools;
if (!_.isEqual(nextBounds, getState().componentState.timeRangeBounds)) {
const { actions, dispatch } = this.reduxEmbeddableTools;
if (
timesliceStartAsPercentageOfTimeRange !== input.timesliceStartAsPercentageOfTimeRange ||
timesliceEndAsPercentageOfTimeRange !== input.timesliceEndAsPercentageOfTimeRange
) {
// Discarding edit mode changes results in replacing edited input with original input
// Re-sync with time range when edited input timeslice changes are discarded
if (
!input.timesliceStartAsPercentageOfTimeRange &&
!input.timesliceEndAsPercentageOfTimeRange
) {
// If no selections have been saved into the timeslider, then both `timesliceStartAsPercentageOfTimeRange`
// and `timesliceEndAsPercentageOfTimeRange` will be undefined - so, need to reset component state to match
dispatch(actions.publishValue({ value: undefined }));
dispatch(actions.setValue({ value: undefined }));
} else {
// Otherwise, need to call `syncWithTimeRange` so that the component state value can be calculated and set
this.syncWithTimeRange();
}
} else if (input.timeRange && !_.isEqual(input.timeRange, this.prevTimeRange)) {
const nextBounds = this.timeRangeToBounds(input.timeRange);
const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], this.getTimezone());
dispatch(
actions.setTimeRangeBounds({
ticks: getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], this.getTimezone()),
ticks,
timeRangeBounds: nextBounds,
})
);
Expand All @@ -135,6 +161,7 @@ export class TimeSliderControlEmbeddable extends Embeddable<
getState().explicitInput.timesliceStartAsPercentageOfTimeRange;
const timesliceEndAsPercentageOfTimeRange =
getState().explicitInput.timesliceEndAsPercentageOfTimeRange;

if (
timesliceStartAsPercentageOfTimeRange !== undefined &&
timesliceEndAsPercentageOfTimeRange !== undefined
Expand Down Expand Up @@ -167,8 +194,8 @@ export class TimeSliderControlEmbeddable extends Embeddable<
dispatch(actions.publishValue({ value }));
}, 500);

private onTimesliceChange = (value?: [number, number]) => {
const { actions, dispatch, getState } = this.reduxEmbeddableTools;
private getTimeSliceAsPercentageOfTimeRange(value?: [number, number]) {
const { getState } = this.reduxEmbeddableTools;
let timesliceStartAsPercentageOfTimeRange: number | undefined;
let timesliceEndAsPercentageOfTimeRange: number | undefined;
if (value) {
Expand All @@ -179,6 +206,18 @@ export class TimeSliderControlEmbeddable extends Embeddable<
timesliceEndAsPercentageOfTimeRange =
(value[TO_INDEX] - timeRangeBounds[FROM_INDEX]) / timeRange;
}
this.prevTimesliceAsPercentage = {
timesliceStartAsPercentageOfTimeRange,
timesliceEndAsPercentageOfTimeRange,
};
return { timesliceStartAsPercentageOfTimeRange, timesliceEndAsPercentageOfTimeRange };
}

private onTimesliceChange = (value?: [number, number]) => {
const { actions, dispatch } = this.reduxEmbeddableTools;

const { timesliceStartAsPercentageOfTimeRange, timesliceEndAsPercentageOfTimeRange } =
this.getTimeSliceAsPercentageOfTimeRange(value);
dispatch(
actions.setValueAsPercentageOfTimeRange({
timesliceStartAsPercentageOfTimeRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ export const createReduxEmbeddableTools = <
}): ReduxEmbeddableTools<ReduxEmbeddableStateType, ReducerType> => {
// Additional generic reducers to aid in embeddable syncing
const genericReducers = {
updateEmbeddableReduxInput: (
replaceEmbeddableReduxInput: (
state: Draft<ReduxEmbeddableStateType>,
action: PayloadAction<Partial<ReduxEmbeddableStateType['explicitInput']>>
action: PayloadAction<ReduxEmbeddableStateType['explicitInput']>
) => {
state.explicitInput = { ...state.explicitInput, ...action.payload };
state.explicitInput = action.payload;
},
updateEmbeddableReduxOutput: (
replaceEmbeddableReduxOutput: (
state: Draft<ReduxEmbeddableStateType>,
action: PayloadAction<Partial<ReduxEmbeddableStateType['output']>>
action: PayloadAction<ReduxEmbeddableStateType['output']>
) => {
state.output = { ...state.output, ...action.payload };
state.output = action.payload;
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const syncReduxEmbeddable = <

if (!inputEqual(reduxExplicitInput, embeddableExplictInput)) {
store.dispatch(
actions.updateEmbeddableReduxInput(cleanInputForRedux(embeddableExplictInput))
actions.replaceEmbeddableReduxInput(cleanInputForRedux(embeddableExplictInput))
);
}
embeddableToReduxInProgress = false;
Expand All @@ -93,7 +93,7 @@ export const syncReduxEmbeddable = <
embeddableToReduxInProgress = true;
const reduxState = store.getState();
if (!outputEqual(reduxState.output, embeddableOutput)) {
store.dispatch(actions.updateEmbeddableReduxOutput(embeddableOutput));
store.dispatch(actions.replaceEmbeddableReduxOutput(embeddableOutput));
}
embeddableToReduxInProgress = false;
});
Expand Down
17 changes: 17 additions & 0 deletions test/functional/apps/dashboard_elements/controls/options_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await pieChart.getPieSliceCount()).to.be(2);
await dashboard.clearUnsavedChanges();
});

it('changes to selections can be discarded', async () => {
await dashboardControls.optionsListOpenPopover(controlId);
await dashboardControls.optionsListPopoverSelectOption('bark');
await dashboardControls.optionsListEnsurePopoverIsClosed(controlId);
let selections = await dashboardControls.optionsListGetSelectionsString(controlId);
expect(selections).to.equal('hiss, grr, bark');

await dashboard.clickCancelOutOfEditMode();
selections = await dashboardControls.optionsListGetSelectionsString(controlId);
expect(selections).to.equal('hiss, grr');
});

it('dashboard does not load with unsaved changes when changes are discarded', async () => {
await dashboard.switchToEditMode();
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});
});

describe('test data view runtime field', async () => {
Expand Down
21 changes: 21 additions & 0 deletions test/functional/apps/dashboard_elements/controls/range_slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const firstId = (await dashboardControls.getAllControlIds())[0];
await dashboardControls.rangeSliderClearSelection(firstId);
await dashboardControls.validateRange('value', firstId, '', '');
await dashboard.clearUnsavedChanges();
});

it('making changes to range causes unsaved changes', async () => {
const firstId = (await dashboardControls.getAllControlIds())[0];
await dashboardControls.rangeSliderSetLowerBound(firstId, '0');
await dashboardControls.rangeSliderSetUpperBound(firstId, '3');
await dashboardControls.rangeSliderWaitForLoading();
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
});

it('changes to range can be discarded', async () => {
const firstId = (await dashboardControls.getAllControlIds())[0];
await dashboardControls.validateRange('value', firstId, '0', '3');
await dashboard.clickCancelOutOfEditMode();
await dashboardControls.validateRange('value', firstId, '', '');
});

it('dashboard does not load with unsaved changes when changes are discarded', async () => {
await dashboard.switchToEditMode();
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});

it('deletes an existing control', async () => {
Expand Down
28 changes: 26 additions & 2 deletions test/functional/apps/dashboard_elements/controls/time_slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const security = getService('security');
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');

const { dashboardControls, discover, timePicker, common, dashboard } = getPageObjects([
'dashboardControls',
'discover',
Expand Down Expand Up @@ -52,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await security.testUser.restoreDefaults();
});

describe('create and delete', async () => {
describe('create, edit, and delete', async () => {
before(async () => {
await common.navigateToApp('dashboard');
await dashboard.preserveCrossAppState();
Expand All @@ -62,11 +63,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'Oct 22, 2018 @ 00:00:00.000',
'Dec 3, 2018 @ 00:00:00.000'
);
await dashboard.saveDashboard('test time slider control', { exitFromEditMode: false });
});

it('can create a new time slider control from a blank state', async () => {
await dashboardControls.createTimeSliderControl();
expect(await dashboardControls.getControlsCount()).to.be(1);
await dashboard.clearUnsavedChanges();
});

it('can not add a second time slider control', async () => {
Expand All @@ -87,13 +90,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboardControls.validateRange('placeholder', secondId, '100', '1200');
});

it('applies filter from the first control on the second control', async () => {
it('making changes to time slice causes unsaved changes', async () => {
await dashboardControls.gotoNextTimeSlice();
await dashboard.clearUnsavedChanges();
});

it('applies filter from the first control on the second control', async () => {
await dashboardControls.rangeSliderWaitForLoading();
const secondId = (await dashboardControls.getAllControlIds())[1];
await dashboardControls.validateRange('placeholder', secondId, '101', '1000');
});

it('changes to time slice can be discarded', async () => {
const valueBefore = await dashboardControls.getTimeSliceFromTimeSlider();
await dashboardControls.gotoNextTimeSlice();
const valueAfter = await dashboardControls.getTimeSliceFromTimeSlider();
expect(valueBefore).to.not.equal(valueAfter);

await dashboardControls.closeTimeSliderPopover();
await dashboard.clickCancelOutOfEditMode();
const valueNow = await dashboardControls.getTimeSliceFromTimeSlider();
expect(valueNow).to.equal(valueBefore);
});

it('dashboard does not load with unsaved changes when changes are discarded', async () => {
await dashboard.switchToEditMode();
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});

it('deletes an existing control', async () => {
const firstId = (await dashboardControls.getAllControlIds())[0];
await dashboardControls.removeExistingControl(firstId);
Expand Down
17 changes: 17 additions & 0 deletions test/functional/page_objects/dashboard_page_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,4 +564,21 @@ export class DashboardPageControls extends FtrService {
await this.testSubjects.click('timeSlider-popoverToggleButton');
}
}

public async getTimeSliceFromTimeSlider() {
const isOpen = await this.testSubjects.exists('timeSlider-popoverContents');
if (!isOpen) {
await this.testSubjects.click('timeSlider-popoverToggleButton');
await this.retry.try(async () => {
await this.testSubjects.existOrFail('timeSlider-popoverContents');
});
}
const popover = await this.testSubjects.find('timeSlider-popoverContents');
const dualRangeSlider = await this.find.descendantDisplayedByCssSelector(
'.euiRangeDraggable',
popover
);
const value = await dualRangeSlider.getAttribute('aria-valuetext');
return value;
}
}

0 comments on commit d868e36

Please sign in to comment.