[Search][Discover] Restore searchSessionId from URL (#81633)

This commit is contained in:
Anton Dosov 2020-11-11 14:03:10 +01:00 committed by GitHub
parent a50960e2fd
commit 7fdd0a1b81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 378 additions and 5 deletions

View file

@ -83,6 +83,8 @@ import {
MODIFY_COLUMNS_ON_SWITCH,
} from '../../../common';
import { METRIC_TYPE } from '@kbn/analytics';
import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator';
import { removeQueryParam, getQueryParams } from '../../../../kibana_utils/public';
const fetchStatuses = {
UNINITIALIZED: 'uninitialized',
@ -91,6 +93,9 @@ const fetchStatuses = {
ERROR: 'error',
};
const getSearchSessionIdFromURL = (history) =>
getQueryParams(history.location)[SEARCH_SESSION_ID_QUERY_PARAM];
const app = getAngularModule();
app.config(($routeProvider) => {
@ -208,6 +213,8 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
};
const history = getHistory();
// used for restoring background session
let isInitialSearch = true;
const {
appStateContainer,
@ -798,17 +805,30 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
if (abortController) abortController.abort();
abortController = new AbortController();
const sessionId = data.search.session.start();
const searchSessionId = (() => {
const searchSessionIdFromURL = getSearchSessionIdFromURL(history);
if (searchSessionIdFromURL) {
if (isInitialSearch) {
data.search.session.restore(searchSessionIdFromURL);
isInitialSearch = false;
return searchSessionIdFromURL;
} else {
// navigating away from background search
removeQueryParam(history, SEARCH_SESSION_ID_QUERY_PARAM);
}
}
return data.search.session.start();
})();
$scope
.updateDataSource()
.then(setupVisualization)
.then(function () {
$scope.fetchStatus = fetchStatuses.LOADING;
logInspectorRequest();
logInspectorRequest({ searchSessionId });
return $scope.searchSource.fetch({
abortSignal: abortController.signal,
sessionId,
sessionId: searchSessionId,
});
})
.then(onResults)
@ -900,7 +920,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
$scope.fetchStatus = fetchStatuses.COMPLETE;
}
function logInspectorRequest() {
function logInspectorRequest({ searchSessionId = null } = { searchSessionId: null }) {
inspectorAdapters.requests.reset();
const title = i18n.translate('discover.inspectorRequestDataTitle', {
defaultMessage: 'data',
@ -908,7 +928,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
const description = i18n.translate('discover.inspectorRequestDescription', {
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
});
inspectorRequest = inspectorAdapters.requests.start(title, { description });
inspectorRequest = inspectorAdapters.requests.start(title, { description, searchSessionId });
inspectorRequest.stats(getRequestInspectorStats($scope.searchSource));
$scope.searchSource.getSearchRequestBody().then((body) => {
inspectorRequest.json(body);

View file

@ -212,6 +212,15 @@ describe('Discover url generator', () => {
});
});
test('can specify a search session id', async () => {
const { generator } = await setup();
const url = await generator.createUrl({
searchSessionId: '__test__',
});
expect(url).toMatchInlineSnapshot(`"xyz/app/discover#/?_g=()&_a=()&searchSessionId=__test__"`);
expect(url).toContain('__test__');
});
describe('useHash property', () => {
describe('when default useHash is set to false', () => {
test('when using default, sets index pattern ID in the generated URL', async () => {

View file

@ -67,6 +67,11 @@ export interface DiscoverUrlGeneratorState {
* whether to hash the data in the url to avoid url length issues.
*/
useHash?: boolean;
/**
* Background search session id
*/
searchSessionId?: string;
}
interface Params {
@ -74,6 +79,8 @@ interface Params {
useHash: boolean;
}
export const SEARCH_SESSION_ID_QUERY_PARAM = 'searchSessionId';
export class DiscoverUrlGenerator
implements UrlGeneratorsDefinition<typeof DISCOVER_APP_URL_GENERATOR> {
constructor(private readonly params: Params) {}
@ -88,6 +95,7 @@ export class DiscoverUrlGenerator
savedSearchId,
timeRange,
useHash = this.params.useHash,
searchSessionId,
}: DiscoverUrlGeneratorState): Promise<string> => {
const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : '';
const appState: {
@ -111,6 +119,10 @@ export class DiscoverUrlGenerator
url = setStateToKbnUrl<QueryState>('_g', queryState, { useHash }, url);
url = setStateToKbnUrl('_a', appState, { useHash }, url);
if (searchSessionId) {
url = `${url}&${SEARCH_SESSION_ID_QUERY_PARAM}=${searchSessionId}`;
}
return url;
};
}

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const queryBar = getService('queryBar');
const testSubjects = getService('testSubjects');
const browser = getService('browser');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['discover', 'common', 'timePicker', 'header']);
describe('discover async search', () => {
before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('discover/default');
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.header.waitUntilLoadingHasFinished();
});
it('search session id should change between searches', async () => {
const searchSessionId1 = await getSearchSessionId();
expect(searchSessionId1).not.to.be.empty();
await queryBar.clickQuerySubmitButton();
const searchSessionId2 = await getSearchSessionId();
expect(searchSessionId2).not.to.be(searchSessionId1);
});
// NOTE: this test will be revised when
// `searchSessionId` functionality actually works
it('search session id should be picked up from the URL', async () => {
const url = await browser.getCurrentUrl();
const fakeSearchSessionId = '__test__';
const savedSessionURL = url + `&searchSessionId=${fakeSearchSessionId}`;
await browser.navigateTo(savedSessionURL);
await PageObjects.header.waitUntilLoadingHasFinished();
const searchSessionId1 = await getSearchSessionId();
expect(searchSessionId1).to.be(fakeSearchSessionId);
await queryBar.clickQuerySubmitButton();
const searchSessionId2 = await getSearchSessionId();
expect(searchSessionId2).not.to.be(searchSessionId1);
});
});
async function getSearchSessionId(): Promise<string> {
await inspector.open();
const searchSessionId = await (
await testSubjects.find('inspectorRequestSearchSessionId')
).getAttribute('data-search-session-id');
await inspector.close();
return searchSessionId;
}
}

View file

@ -16,5 +16,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./error_handling'));
loadTestFile(require.resolve('./visualize_field'));
loadTestFile(require.resolve('./value_suggestions'));
loadTestFile(require.resolve('./async_search'));
});
}

View file

@ -0,0 +1,273 @@
{
"type": "index",
"value": {
"index": ".kibana",
"mappings": {
"properties": {
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"type": {
"type": "keyword"
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"query": {
"properties": {
"title": {
"type": "text"
},
"description": {
"type": "text"
},
"query": {
"properties": {
"language": {
"type": "keyword"
},
"query": {
"type": "keyword",
"index": false
}
}
},
"filters": {
"type": "object",
"enabled": false
},
"timefilter": {
"type": "object",
"enabled": false
}
}
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}