mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Search][Discover] Restore searchSessionId from URL (#81633)
This commit is contained in:
parent
a50960e2fd
commit
7fdd0a1b81
7 changed files with 378 additions and 5 deletions
|
@ -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);
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
58
x-pack/test/functional/apps/discover/async_search.ts
Normal file
58
x-pack/test/functional/apps/discover/async_search.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
|
BIN
x-pack/test/functional/es_archives/discover/default/data.json.gz
Normal file
BIN
x-pack/test/functional/es_archives/discover/default/data.json.gz
Normal file
Binary file not shown.
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue