Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import {
SavedObjectEmbeddableInput,
ContainerOutput,
} from '../../../embeddable/public';
import { NavAction, SavedDashboardPanel } from '../types';
import { DashboardSavedFiltersHandling, NavAction, SavedDashboardPanel } from '../types';

import { showOptionsPopover } from './top_nav/show_options_popover';
import { DashboardSaveModal } from './top_nav/save_modal';
Expand Down Expand Up @@ -141,13 +141,20 @@ export class DashboardAppController {
chrome.docTitle.change(dash.title);
}

const savedFiltersHandling: DashboardSavedFiltersHandling =
$routeParams[DashboardConstants.SAVED_FILTERS_HANDLING_PARAM];
if (savedFiltersHandling) {
removeQueryParam(history, DashboardConstants.SAVED_FILTERS_HANDLING_PARAM);
}

const dashboardStateManager = new DashboardStateManager({
savedDashboard: dash,
hideWriteControls: dashboardConfig.getHideWriteControls(),
kibanaVersion: pluginInitializerContext.env.packageInfo.version,
kbnUrlStateStorage,
history,
usageCollection,
savedFiltersHandling,
});

// sync initial app filters from state to filterManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
DashboardAppStateDefaults,
DashboardAppStateInUrl,
DashboardAppStateTransitions,
DashboardSavedFiltersHandling,
SavedDashboardPanel,
} from '../types';
import {
Expand Down Expand Up @@ -101,13 +102,15 @@ export class DashboardStateManager {
kbnUrlStateStorage,
history,
usageCollection,
savedFiltersHandling,
}: {
savedDashboard: SavedObjectDashboard;
hideWriteControls: boolean;
kibanaVersion: string;
kbnUrlStateStorage: IKbnUrlStateStorage;
history: History;
usageCollection?: UsageCollectionSetup;
savedFiltersHandling?: DashboardSavedFiltersHandling;
}) {
this.history = history;
this.kibanaVersion = kibanaVersion;
Expand All @@ -123,13 +126,20 @@ export class DashboardStateManager {
);

this.kbnUrlStateStorage = kbnUrlStateStorage;
const initialStateFromUrl = this.kbnUrlStateStorage.get<DashboardAppState>(
this.STATE_STORAGE_KEY
);

// setup initial state by merging defaults with state from url
// also run migration, as state in url could be of older version
const initialState = migrateAppState(
{
...this.stateDefaults,
...this.kbnUrlStateStorage.get<DashboardAppState>(this.STATE_STORAGE_KEY),
...initialStateFromUrl,
filters:
savedFiltersHandling === 'merge'
? [...this.stateDefaults.filters, ...(initialStateFromUrl?.filters ?? [])] // merge filters from url with filters from saved object
: initialStateFromUrl?.filters ?? this.stateDefaults.filters, // filters from url take precedence over saved filters
},
kibanaVersion,
usageCollection
Expand Down
1 change: 1 addition & 0 deletions src/plugins/dashboard/public/dashboard_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const DashboardConstants = {
ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
DASHBOARDS_ID: 'dashboards',
DASHBOARD_ID: 'dashboard',
SAVED_FILTERS_HANDLING_PARAM: 'savedFiltersHandling',
};

export function createDashboardEditUrl(id: string) {
Expand Down
11 changes: 11 additions & 0 deletions src/plugins/dashboard/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,14 @@ export interface StagedFilter {
operator: string;
index: string;
}

/**
* When dashboard is loaded filters could come from 2 places:
* 1. From url
* 2. From dashboard's saved object
*
* Filters could be handled in 2 different ways:
* * override (default) - if there is state in the url, url is source of truth and filters from url take precedence over filters in saved object
* * merge - filters from url are merge together with filters from saved object
*/
export type DashboardSavedFiltersHandling = 'merge' | 'override';
29 changes: 26 additions & 3 deletions src/plugins/dashboard/public/url_generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { hashedItemStore } from '../../kibana_utils/public';
// eslint-disable-next-line
import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock';
import { esFilters } from '../../data/public';
import { DashboardConstants } from './dashboard_constants';

const APP_BASE_PATH: string = 'xyz/app/kibana';

Expand All @@ -36,7 +37,9 @@ describe('dashboard url generator', () => {
Promise.resolve({ appBasePath: APP_BASE_PATH, useHashedUrl: false })
);
const url = await generator.createUrl!({});
expect(url).toMatchInlineSnapshot(`"xyz/app/kibana#/dashboard?_a=()&_g=()"`);
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard?_a=()&_g=()&savedFiltersHandling=merge"`
);
});

test('creates a link with global time range set up', async () => {
Expand All @@ -47,7 +50,7 @@ describe('dashboard url generator', () => {
timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
});
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))"`
`"xyz/app/kibana#/dashboard?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))&savedFiltersHandling=merge"`
);
});

Expand Down Expand Up @@ -83,7 +86,7 @@ describe('dashboard url generator', () => {
query: { query: 'bye', language: 'kuery' },
});
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))"`
`"xyz/app/kibana#/dashboard/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&savedFiltersHandling=merge"`
);
});

Expand Down Expand Up @@ -118,4 +121,24 @@ describe('dashboard url generator', () => {
});
expect(url.indexOf('relative')).toBeGreaterThan(1);
});

test('by default saved filters are merged', async () => {
const generator = createDirectAccessDashboardLinkGenerator(() =>
Promise.resolve({ appBasePath: APP_BASE_PATH, useHashedUrl: false })
);
const url = await generator.createUrl!({});
expect(url).toEqual(
expect.stringContaining(`${DashboardConstants.SAVED_FILTERS_HANDLING_PARAM}=merge`)
);
});

test('can disable merging filters', async () => {
const generator = createDirectAccessDashboardLinkGenerator(() =>
Promise.resolve({ appBasePath: APP_BASE_PATH, useHashedUrl: false })
);
const url = await generator.createUrl!({ savedFiltersHandling: 'override' });
expect(url).not.toEqual(
expect.stringContaining(DashboardConstants.SAVED_FILTERS_HANDLING_PARAM)
);
});
});
27 changes: 24 additions & 3 deletions src/plugins/dashboard/public/url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
} from '../../data/public';
import { setStateToKbnUrl } from '../../kibana_utils/public';
import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public';
import { DashboardSavedFiltersHandling } from './types';
import { DashboardConstants } from './dashboard_constants';

export const STATE_STORAGE_KEY = '_a';
export const GLOBAL_STATE_STORAGE_KEY = '_g';
Expand Down Expand Up @@ -64,6 +66,19 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{
* whether to hash the data in the url to avoid url length issues.
*/
useHash?: boolean;

/**
* When dashboard is loaded filters could come from 2 places:
* 1. From url
* 2. From dashboard's saved object
*
* Filters could be handled in 2 different ways:
* * override - if there is state in the url, url is source of truth and filters from url take precedence over filters in saved object
* * merge - filters from url are merged together with filters from saved object
*
* Url generator uses `merge` handling as default
*/
savedFiltersHandling?: DashboardSavedFiltersHandling;
}>;

export const createDirectAccessDashboardLinkGenerator = (
Expand All @@ -85,7 +100,7 @@ export const createDirectAccessDashboardLinkGenerator = (
return stateObj;
};

const appStateUrl = setStateToKbnUrl(
let resultUrl = setStateToKbnUrl(
STATE_STORAGE_KEY,
cleanEmptyKeys({
query: state.query,
Expand All @@ -95,15 +110,21 @@ export const createDirectAccessDashboardLinkGenerator = (
`${appBasePath}#/${hash}`
);

return setStateToKbnUrl<QueryState>(
resultUrl = setStateToKbnUrl<QueryState>(
GLOBAL_STATE_STORAGE_KEY,
cleanEmptyKeys({
time: state.timeRange,
filters: state.filters?.filter(f => esFilters.isFilterPinned(f)),
refreshInterval: state.refreshInterval,
}),
{ useHash },
appStateUrl
resultUrl
);

if (!state.savedFiltersHandling || state.savedFiltersHandling === 'merge') {
resultUrl = `${resultUrl}&${DashboardConstants.SAVED_FILTERS_HANDLING_PARAM}=merge`;
}

return resultUrl;
},
});
15 changes: 15 additions & 0 deletions test/functional/apps/dashboard/dashboard_filter_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ export default function({ getService, getPageObjects }) {
expect(await filterBar.getFilterCount()).to.be(0);
await pieChart.expectPieSliceCount(5);
});

it('&savedFiltersHandling=merge merges incoming filters from url with saved filters', async () => {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard('with filters');
await PageObjects.header.waitUntilLoadingHasFinished();
await filterBar.removeFilter('bytes');
await filterBar.addFilter('extension', 'is', 'jpg');
const currentUrl = await browser.getCurrentUrl();
const newUrl = `${currentUrl}&savedFiltersHandling=merge`;
await browser.get(newUrl);
await PageObjects.header.waitUntilLoadingHasFinished();
const filterCount = await filterBar.getFilterCount();
expect(filterCount).to.equal(2);
await pieChart.expectPieSliceCount(1);
});
});

describe('saved search filtering', function() {
Expand Down