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

Create dashboard drilldown - select dashboard via filtering combo box and navigate to dashboarrd #60087

Closed
Closed
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
813a896
partial progress on async loading / searching of dashboard titles
mattkime Mar 13, 2020
65ff148
feat: 🎸 make combobox full width
streamich Mar 13, 2020
d46fcf0
filtering combobox polish
mattkime Mar 16, 2020
6e11bc2
Merge branch 'drilldowns_load_dashboard_list' of github.com:mattkime/…
mattkime Mar 16, 2020
699d853
storybook fix
mattkime Mar 16, 2020
d3ac7ad
implement navigating to dashboard, seems like a type problem
mattkime Mar 17, 2020
a5ee274
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 17, 2020
4369399
try navToApp
mattkime Mar 17, 2020
94b08ae
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 17, 2020
86f9768
Merge remote-tracking branch 'upstream/drilldowns' into drilldowns_lo…
mattkime Mar 17, 2020
a276b96
filter out current dashboard
mattkime Mar 17, 2020
a112732
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 17, 2020
3b39dcd
rough draft linking to a dashboard
mattkime Mar 18, 2020
0bbf360
remove note
mattkime Mar 18, 2020
636d9ab
typefix
mattkime Mar 18, 2020
6ea19aa
fix navigation from dashboard to dashboard
Dosant Mar 18, 2020
0178ff5
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 19, 2020
a431e86
partial progress getting filters from action data
mattkime Mar 20, 2020
4217b18
fix issue with getIndexPatterns undefined
Dosant Mar 20, 2020
177822f
fix filter / time passing into url
Dosant Mar 20, 2020
8bcbc1b
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 21, 2020
f7b7add
typefix
mattkime Mar 21, 2020
346dc40
Merge branch 'drilldowns_load_dashboard_list' of github.com:mattkime/…
mattkime Mar 21, 2020
bd7a91c
dashboard to dashboard drilldown functional test and back button fix
Dosant Mar 21, 2020
5ea7818
documentation update
mattkime Mar 21, 2020
8297b55
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 22, 2020
feccdfc
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 22, 2020
29e885c
Merge branch 'drilldowns' of github.com:elastic/kibana into dev/drill…
Dosant Mar 23, 2020
100c4f0
chore clean-ups
Dosant Mar 23, 2020
340fd89
basic unit test for dashboard drilldown
Dosant Mar 23, 2020
406facd
remove test todos
Dosant Mar 23, 2020
d894574
Merge branch 'drilldowns' of github.com:elastic/kibana into dev/drill…
Dosant Mar 23, 2020
bf9e505
remove config
Dosant Mar 23, 2020
a24e29a
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 23, 2020
d7633b3
improve back button with filter comparison tweak
Dosant Mar 23, 2020
59101ef
dashboard filters/date option off by default
Dosant Mar 23, 2020
9fdb358
Merge branch 'drilldowns_load_dashboard_list' of github.com:mattkime/…
mattkime Mar 23, 2020
bb308d6
revert change to config/kibana.yml
mattkime Mar 23, 2020
8d10133
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
streamich Mar 23, 2020
31e28a3
remove unneeded comments
mattkime Mar 23, 2020
38ec7a0
use default time range as appropriate
mattkime Mar 24, 2020
c3708ca
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Mar 24, 2020
4a8d4a1
fix type, add filter icon, add text
mattkime Mar 24, 2020
0deb6b4
fix test
mattkime Mar 24, 2020
6a78486
change how time range is restored and improve back button for drilldowns
Dosant Mar 24, 2020
bc3fdd4
Merge branch 'drilldowns' into drilldowns_load_dashboard_list
mattkime Apr 3, 2020
8deeaa9
Merge branch 'drilldowns' of github.com:elastic/kibana into drilldown…
Dosant Apr 7, 2020
514561b
resolve conflicts
Dosant Apr 7, 2020
40e396c
fix async compile issue
mattkime Apr 8, 2020
a9b86a9
remove redundant test
Dosant Apr 8, 2020
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 @@ -9,5 +9,7 @@
```typescript
actions: {
createFiltersFromEvent: typeof createFiltersFromEvent;
valueClickActionGetFilters: typeof valueClickActionGetFilters;
selectRangeActionGetFilters: typeof selectRangeActionGetFilters;
};
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface DataPublicPluginStart

| Property | Type | Description |
| --- | --- | --- |
| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | <code>{</code><br/><code> createFiltersFromEvent: typeof createFiltersFromEvent;</code><br/><code> }</code> | |
| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | <code>{</code><br/><code> createFiltersFromEvent: typeof createFiltersFromEvent;</code><br/><code> valueClickActionGetFilters: typeof valueClickActionGetFilters;</code><br/><code> selectRangeActionGetFilters: typeof selectRangeActionGetFilters;</code><br/><code> }</code> | |
| [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | <code>AutocompleteStart</code> | |
| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | |
| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | <code>IndexPatternsContract</code> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ esFilters: {
generateFilters: typeof generateFilters;
onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean;
changeTimeFilter: typeof changeTimeFilter;
convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString;
mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[];
extractTimeFilter: typeof extractTimeFilter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
IndexPattern,
IndexPatternsContract,
Query,
QueryState,
SavedQuery,
syncQueryStateWithUrl,
} from '../../../../../../plugins/data/public';
Expand Down Expand Up @@ -132,13 +133,6 @@ export class DashboardAppController {
const queryFilter = filterManager;
const timefilter = queryService.timefilter.timefilter;

// starts syncing `_g` portion of url with query services
// note: dashboard_state_manager.ts syncs `_a` portion of url
const {
stop: stopSyncingQueryServiceStateWithUrl,
hasInheritedQueryFromUrl: hasInheritedGlobalStateFromUrl,
} = syncQueryStateWithUrl(queryService, kbnUrlStateStorage);

let lastReloadRequestTime = 0;
const dash = ($scope.dash = $route.current.locals.dash);
if (dash.id) {
Expand Down Expand Up @@ -170,9 +164,20 @@ export class DashboardAppController {

// The hash check is so we only update the time filter on dashboard open, not during
// normal cross app navigation.
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !hasInheritedGlobalStateFromUrl) {
const initialGlobalStateInUrl = kbnUrlStateStorage.get<QueryState>('_g');
const hasInheritedTimeRange = Boolean(initialGlobalStateInUrl?.time);
Dosant marked this conversation as resolved.
Show resolved Hide resolved
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !hasInheritedTimeRange) {
dashboardStateManager.syncTimefilterWithDashboard(timefilter);
}
// starts syncing `_g` portion of url with query services
// note: dashboard_state_manager.ts syncs `_a` portion of url
// it is important to start this syncing after `dashboardStateManager.syncTimefilterWithDashboard(timefilter);` above is run,
// otherwise it will case redundant browser history record
const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl(
Dosant marked this conversation as resolved.
Show resolved Hide resolved
queryService,
kbnUrlStateStorage
);

$scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean;

const getShouldShowEditHelp = () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
ReduxLikeStateContainer,
syncState,
} from '../../../../../../plugins/kibana_utils/public';
import { getDashboardIdFromUrl } from './url_helper';

/**
* Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
Expand Down Expand Up @@ -174,6 +175,12 @@ export class DashboardStateManager {
// sync state required state container to be able to handle null
// overriding set() so it could handle null coming from url
if (state) {
// Skip this update if current dashboardId in the url is different from what we have in current instance of state manager
// Because dashboard is driven by angular, the destroy cycle happens async,
// And we should not to interfere into state change longer as this instance will be destroyed soon
const currentDashboardIdInUrl = getDashboardIdFromUrl(history.location.pathname);
if (currentDashboardIdInUrl !== this.savedDashboard.id) return;

this.stateContainer.set({
...this.stateDefaults,
...state,
Expand Down Expand Up @@ -229,8 +236,8 @@ export class DashboardStateManager {

if (
!_.isEqual(
convertedPanelStateMap[panelState.explicitInput.id],
savedDashboardPanelMap[panelState.explicitInput.id]
_.omit(convertedPanelStateMap[panelState.explicitInput.id], 'version'),
_.omit(savedDashboardPanelMap[panelState.explicitInput.id], 'version')
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes an edge case with state update when panel with older version than current kibana version is opened before saved with newer version.
e.g. 7.7.0 panel is opened in master.

Without this, serialised state technically changes which causes redundant browser history record.
Change is required to have this test working: https://github.com/elastic/kibana/pull/60087/files#diff-401179577e069eaeb5f20de5ad1ba769R109

) {
// A panel was changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jest.mock('../legacy_imports', () => {

import {
addEmbeddableToDashboardUrl,
getDashboardIdFromUrl,
getLensUrlFromDashboardAbsoluteUrl,
getUrlVars,
} from './url_helper';
Expand Down Expand Up @@ -115,4 +116,30 @@ describe('Dashboard URL Helper', () => {
'http://localhost:5601/app/kibana#/lens/edit/1244'
);
});

it('getDashboardIdFromDashboardUrl', () => {
let url =
"http://localhost:5601/wev/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getDashboardIdFromUrl(url)).toEqual(undefined);

url =
"http://localhost:5601/wev/app/kibana#/dashboard/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
expect(getDashboardIdFromUrl(url)).toEqual('625357282');

url = 'http://myserver.mydomain.com:5601/wev/app/kibana#/dashboard/777182';
expect(getDashboardIdFromUrl(url)).toEqual('777182');

url =
"http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getDashboardIdFromUrl(url)).toEqual(undefined);

url = '/dashboard/test/?_g=(refreshInterval:';
expect(getDashboardIdFromUrl(url)).toEqual('test');

url = 'dashboard/test/?_g=(refreshInterval:';
expect(getDashboardIdFromUrl(url)).toEqual('test');

url = '/other-app/test/';
expect(getDashboardIdFromUrl(url)).toEqual(undefined);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,24 @@ export function getLensUrlFromDashboardAbsoluteUrl(
function getUrlWithoutQueryParams(url: string): string {
return url.split('?')[0];
}

/**
* Returns dashboard id from URL
* literally looks from id after `dashboard/` string and before `/`, `?` and end of string
* @param url to extract dashboardId from
* input: http://localhost:5601/lib/app/kibana#/dashboard?param1=x&param2=y&param3=z
* output: undefined
* input: http://localhost:5601/lib/app/kibana#/dashboard/39292992?param1=x&param2=y&param3=z
* output: 39292992
*/
export function getDashboardIdFromUrl(url: string): string | undefined {
const [, match1, match2, match3] = url.match(
/dashboard\/(.*)\/|dashboard\/(.*)\?|dashboard\/(.*)$/
) ?? [
undefined, // full match
undefined, // group1 - dashboardId is before `/`
undefined, // group2 - dashboardId is before `?`
undefined, // group3 - dashboardID is in the end
];
return match1 ?? match2 ?? match3 ?? undefined;
}
10 changes: 4 additions & 6 deletions src/legacy/ui/public/chrome/api/sub_url_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
* under the License.
*/

import url from 'url';

import { unhashUrl } from '../../../../../plugins/kibana_utils/public';
import { toastNotifications } from '../../notify/toasts';
import { npSetup } from '../../new_platform';
Expand Down Expand Up @@ -47,14 +45,14 @@ export function registerSubUrlHooks(angularModule, internals) {
}

$rootScope.$on('$locationChangeStart', (e, newUrl) => {
const getHash = url => url.split('#')[1] || '';

// This handler fixes issue #31238 where browser back navigation
// fails due to angular 1.6 parsing url encoded params wrong.
const parsedAbsUrl = url.parse($location.absUrl());
const absUrlHash = parsedAbsUrl.hash ? parsedAbsUrl.hash.slice(1) : '';
const absUrlHash = getHash($location.absUrl());
Copy link
Contributor

@Dosant Dosant Mar 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is required to make dashboard drilldown navigation with dashboard and back button work together.
Just an enhancement to current encoding differences workaround.
The change simplifies how the hash is extracted from the url, the problem is that url.parse encodes '`' when doing the parsing and for drilldown case this causes 2 browser history records.

simple back button scenario for dashboard to dashboard drilldown is covered: https://github.com/elastic/kibana/pull/60087/files#diff-401179577e069eaeb5f20de5ad1ba769R109

We have existing e2e for original bug fix and that one stays green with the test

const decodedAbsUrlHash = decodeURIComponent(absUrlHash);

const parsedNewUrl = url.parse(newUrl);
const newHash = parsedNewUrl.hash ? parsedNewUrl.hash.slice(1) : '';
const newHash = getHash(newUrl);
const decodedHash = decodeURIComponent(newHash);

if (absUrlHash !== newHash && decodedHash === decodedAbsUrlHash) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dashboard/public/url_generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,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=(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:!(),time:(from:now-15m,mode:relative,to:now))"`
);
});

Expand Down
25 changes: 17 additions & 8 deletions src/plugins/dashboard/public/url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { TimeRange, Filter, Query } from '../../data/public';
import { TimeRange, Filter, Query, esFilters } from '../../data/public';
import { setStateToKbnUrl } from '../../kibana_utils/public';
import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public';

Expand All @@ -38,8 +38,7 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{
timeRange?: TimeRange;
/**
* Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the
* saved dashboard has filters saved with it, this will _replace_ those filters. This will set
* app filters, not global filters.
* saved dashboard has filters saved with it, this will _replace_ those filters.
*/
filters?: Filter[];
/**
Expand All @@ -64,21 +63,31 @@ export const createDirectAccessDashboardLinkGenerator = (
const appBasePath = startServices.appBasePath;
const hash = state.dashboardId ? `dashboard/${state.dashboardId}` : `dashboard`;

const cleanEmptyStateKeys = (stateObj: Record<string, any>) => {
Object.keys(stateObj).forEach(key => {
if (stateObj[key] === undefined) {
delete stateObj[key];
}
});
return stateObj;
};

const appStateUrl = setStateToKbnUrl(
STATE_STORAGE_KEY,
{
cleanEmptyStateKeys({
query: state.query,
filters: state.filters,
},
filters: state.filters?.filter(f => !esFilters.isFilterPinned(f)),
}),
{ useHash },
`${appBasePath}#/${hash}`
);

return setStateToKbnUrl(
GLOBAL_STATE_STORAGE_KEY,
{
cleanEmptyStateKeys({
time: state.timeRange,
},
filters: state.filters?.filter(f => esFilters.isFilterPinned(f)),
}),
{ useHash },
appStateUrl
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,28 @@ describe('filter manager utilities', () => {
expect(compareFilters(f1, f2)).toBeTruthy();
});

test('should compare duplicates, assuming $state: { store: FilterStateStore.APP_STATE } is default', () => {
const f1 = {
$state: { store: FilterStateStore.APP_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
};
const f2 = {
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
};
const f3 = {
$state: { store: FilterStateStore.GLOBAL_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
};

// don't take state into account at all:
expect(compareFilters(f1, f2)).toBeTruthy();
expect(compareFilters(f2, f3)).toBeTruthy();

// compare with state:
expect(compareFilters(f1, f2, { state: true })).toBeTruthy();
expect(compareFilters(f2, f3, { state: true })).toBeFalsy();
});

test('should compare filters array to non array', () => {
const f1 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import { defaults, isEqual, omit, map } from 'lodash';
import { FilterMeta, Filter } from '../../es_query';
import { FilterMeta, Filter, FilterStateStore } from '../../es_query';

export interface FilterCompareOptions {
disabled?: boolean;
Expand All @@ -42,11 +42,13 @@ const mapFilter = (
comparators: FilterCompareOptions,
excludedAttributes: string[]
) => {
const cleaned: FilterMeta = omit(filter, excludedAttributes);
const cleaned: Filter & FilterMeta = omit(filter, excludedAttributes);

if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
if (comparators.disabled) cleaned.alias = filter.meta?.alias;
if (comparators.state)
cleaned.$state = { store: filter.$state?.store ?? FilterStateStore.APP_STATE };
Dosant marked this conversation as resolved.
Show resolved Hide resolved

return cleaned;
};
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/public/actions/apply_filter_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function createFilterAction(
return createAction<typeof ACTION_GLOBAL_APPLY_FILTER>({
type: ACTION_GLOBAL_APPLY_FILTER,
id: ACTION_GLOBAL_APPLY_FILTER,
getIconType: () => 'filter',
getDisplayName: () => {
return i18n.translate('data.filter.applyFilterActionTitle', {
defaultMessage: 'Apply filter to current view',
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/data/public/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@

export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action';
export { createFiltersFromEvent } from './filters/create_filters_from_event';
export { selectRangeAction } from './select_range_action';
export { valueClickAction } from './value_click_action';
export { selectRangeAction, selectRangeActionGetFilters } from './select_range_action';
export { valueClickAction, valueClickActionGetFilters } from './value_click_action';
Loading