forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Search][Notebooks] allow pre-selecting a specific notebook (elastic#…
…189825) ## Summary Updated the search notebooks to allow loading the selected notebooks from a query parameter. exposes a function on the search notebooks start contract to select notebook. Tested to ensure this allows the user to change the selected notebooks once the notebook view is open. caveats: - The way this currently works is you have to set the selected notebook before opening the notebooks view. - If you set the query parameter when the notebook view is open the selection DOES NOT update, I tried to implement this but ran into issues with not being able to change the selection or unrelated re-renders reverting the selection back to the query parameter value :/ ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Elastic Machine <[email protected]>
- Loading branch information
1 parent
507f1d2
commit 40d1a91
Showing
7 changed files
with
295 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,3 +65,5 @@ export const INTRODUCTION_NOTEBOOK: Notebook = { | |
], | ||
}, | ||
}; | ||
|
||
export const DEFAULT_NOTEBOOK_ID = 'introduction'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
x-pack/plugins/search_notebooks/public/utils/notebook_query_param.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { setNotebookParameter, removeNotebookParameter } from './notebook_query_param'; | ||
|
||
const baseMockWindow = () => { | ||
return { | ||
history: { | ||
pushState: jest.fn(), | ||
}, | ||
location: { | ||
host: 'my-kibana.elastic.co', | ||
pathname: '', | ||
protocol: 'https:', | ||
search: '', | ||
hash: '', | ||
}, | ||
}; | ||
}; | ||
let windowSpy: jest.SpyInstance; | ||
let mockWindow = baseMockWindow(); | ||
|
||
describe('notebook query parameter utility', () => { | ||
beforeEach(() => { | ||
mockWindow = baseMockWindow(); | ||
windowSpy = jest.spyOn(globalThis, 'window', 'get'); | ||
windowSpy.mockImplementation(() => mockWindow); | ||
}); | ||
|
||
afterEach(() => { | ||
windowSpy.mockRestore(); | ||
}); | ||
|
||
describe('setNotebookParameter', () => { | ||
it('adds notebookId query param', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
}; | ||
const notebook = '00_quick_start'; | ||
const expectedUrl = | ||
'https://my-kibana.elastic.co/foo/app/elasticsearch?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; | ||
|
||
setNotebookParameter(notebook); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('can replace an existing value', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
search: '?notebookId=AwRg+g1gpgng7gewE4BMwEcCuUkwJYB2A5mAGZ4A2ALjoUUA', | ||
}; | ||
const notebook = '00_quick_start'; | ||
const expectedUrl = | ||
'https://my-kibana.elastic.co/foo/app/elasticsearch?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; | ||
|
||
setNotebookParameter(notebook); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('leaves other query parameters in place', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
search: '?foo=bar', | ||
}; | ||
const notebook = '00_quick_start'; | ||
const expectedUrl = | ||
'https://my-kibana.elastic.co/foo/app/elasticsearch?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; | ||
|
||
setNotebookParameter(notebook); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('works with hash routes', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
hash: '#/home', | ||
}; | ||
const notebook = '00_quick_start'; | ||
const expectedUrl = | ||
'https://my-kibana.elastic.co/foo/app/elasticsearch#/home?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; | ||
|
||
setNotebookParameter(notebook); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
}); | ||
describe('removeNotebookParameter', () => { | ||
it('leaves other params in place', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
search: `?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, | ||
}; | ||
|
||
const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch?foo=bar'; | ||
|
||
removeNotebookParameter(); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('leaves other params with a hashroute', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
hash: `#/home?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, | ||
}; | ||
|
||
const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch#/home?foo=bar'; | ||
|
||
removeNotebookParameter(); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('removes ? if load_from was the only param', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
search: `?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, | ||
}; | ||
|
||
const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch'; | ||
|
||
removeNotebookParameter(); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('removes ? if load_from was the only param in a hashroute', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
hash: '#/home?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA', | ||
}; | ||
|
||
const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch#/home'; | ||
|
||
removeNotebookParameter(); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.history.pushState).toHaveBeenCalledWith( | ||
{ | ||
path: expectedUrl, | ||
}, | ||
'', | ||
expectedUrl | ||
); | ||
}); | ||
it('noop if load_from not currently defined on QS', () => { | ||
mockWindow.location = { | ||
...mockWindow.location, | ||
pathname: '/foo/app/elasticsearch', | ||
hash: `#/home?foo=bar`, | ||
}; | ||
|
||
removeNotebookParameter(); | ||
expect(mockWindow.history.pushState).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
60 changes: 60 additions & 0 deletions
60
x-pack/plugins/search_notebooks/public/utils/notebook_query_param.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import qs from 'query-string'; | ||
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string'; | ||
|
||
function getBaseUrl() { | ||
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`; | ||
} | ||
|
||
function parseQueryString() { | ||
const [hashRoute, queryString] = (window.location.hash || window.location.search || '').split( | ||
'?' | ||
); | ||
|
||
const parsedQueryString = qs.parse(queryString || '', { sort: false }); | ||
return { | ||
hasHash: !!window.location.hash, | ||
hashRoute, | ||
queryString: parsedQueryString, | ||
}; | ||
} | ||
|
||
export const setNotebookParameter = (value: string) => { | ||
const baseUrl = getBaseUrl(); | ||
const { hasHash, hashRoute, queryString } = parseQueryString(); | ||
const notebookId = compressToEncodedURIComponent(value); | ||
queryString.notebookId = notebookId; | ||
const params = `?${qs.stringify(queryString)}`; | ||
const newUrl = hasHash ? `${baseUrl}${hashRoute}${params}` : `${baseUrl}${params}`; | ||
|
||
window.history.pushState({ path: newUrl }, '', newUrl); | ||
}; | ||
export const removeNotebookParameter = () => { | ||
const baseUrl = getBaseUrl(); | ||
const { hasHash, hashRoute, queryString } = parseQueryString(); | ||
if (queryString.notebookId) { | ||
delete queryString.notebookId; | ||
|
||
const params = Object.keys(queryString).length ? `?${qs.stringify(queryString)}` : ''; | ||
const newUrl = hasHash ? `${baseUrl}${hashRoute}${params}` : `${baseUrl}${params}`; | ||
window.history.pushState({ path: newUrl }, '', newUrl); | ||
} | ||
}; | ||
export const readNotebookParameter = (): string | undefined => { | ||
const { queryString } = parseQueryString(); | ||
if (queryString.notebookId && typeof queryString.notebookId === 'string') { | ||
try { | ||
const notebookId = decompressFromEncodedURIComponent(queryString.notebookId); | ||
if (notebookId.length > 0) return notebookId; | ||
} catch { | ||
return undefined; | ||
} | ||
} | ||
return undefined; | ||
}; |