mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Discover] Fix loading of a single doc JSON (#135446)
* [Discover] Fix fetching doc JSON * [Discover] Make the query strict * [Discover] Revert some changes * [Discover] Allow to open Single Doc and Surrounding docs in a new tab * [Discover] Switch to `filter` to skip score calculations * [Discover] Update tests * [Discover] Update tests * [Discover] Add functional tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ffaa9e4a6b
commit
1dbdf128e7
5 changed files with 209 additions and 35 deletions
|
@ -17,6 +17,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import React from 'react';
|
||||
import { buildDataTableRecord } from '../utils/build_data_record';
|
||||
|
||||
const index = 'test-index';
|
||||
const mockSearchResult = new Subject();
|
||||
const services = {
|
||||
data: {
|
||||
|
@ -40,16 +41,27 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
const indexPattern = {
|
||||
getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }),
|
||||
} as unknown as DataView;
|
||||
const actual = buildSearchBody('1', indexPattern, false);
|
||||
const actual = buildSearchBody('1', index, indexPattern, false);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
"_source": true,
|
||||
"fields": Array [],
|
||||
"query": Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"_index": "test-index",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -65,7 +77,7 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
const indexPattern = {
|
||||
getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }),
|
||||
} as unknown as DataView;
|
||||
const actual = buildSearchBody('1', indexPattern, true);
|
||||
const actual = buildSearchBody('1', index, indexPattern, true);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
|
@ -76,9 +88,20 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
},
|
||||
],
|
||||
"query": Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"_index": "test-index",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -95,7 +118,7 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
const indexPattern = {
|
||||
getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }),
|
||||
} as unknown as DataView;
|
||||
const actual = buildSearchBody('1', indexPattern, true, true);
|
||||
const actual = buildSearchBody('1', index, indexPattern, true, true);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
|
@ -107,9 +130,20 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
},
|
||||
],
|
||||
"query": Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"_index": "test-index",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -138,7 +172,7 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
},
|
||||
}),
|
||||
} as unknown as DataView;
|
||||
const actual = buildSearchBody('1', indexPattern, true);
|
||||
const actual = buildSearchBody('1', index, indexPattern, true);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
|
@ -149,9 +183,20 @@ describe('Test of <Doc /> helper / hook', () => {
|
|||
},
|
||||
],
|
||||
"query": Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"_index": "test-index",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -38,8 +38,8 @@ export function useEsDocSearch({
|
|||
const result = await lastValueFrom(
|
||||
data.search.search({
|
||||
params: {
|
||||
index,
|
||||
body: buildSearchBody(id, indexPattern, useNewFieldsApi, requestSource)?.body,
|
||||
index: indexPattern.title,
|
||||
body: buildSearchBody(id, index, indexPattern, useNewFieldsApi, requestSource)?.body,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -77,6 +77,7 @@ export function useEsDocSearch({
|
|||
*/
|
||||
export function buildSearchBody(
|
||||
id: string,
|
||||
index: string,
|
||||
indexPattern: DataView,
|
||||
useNewFieldsApi: boolean,
|
||||
requestAllFields?: boolean
|
||||
|
@ -86,8 +87,8 @@ export function buildSearchBody(
|
|||
const request: RequestBody = {
|
||||
body: {
|
||||
query: {
|
||||
ids: {
|
||||
values: [id],
|
||||
bool: {
|
||||
filter: [{ ids: { values: [id] } }, { term: { _index: index } }],
|
||||
},
|
||||
},
|
||||
stored_fields: computedFields.storedFields,
|
||||
|
|
|
@ -63,7 +63,8 @@ describe('useNavigationProps', () => {
|
|||
test('should provide valid breadcrumb for single doc page from main view', () => {
|
||||
const { result, history } = render();
|
||||
|
||||
result.current.singleDocProps.onClick?.();
|
||||
// @ts-expect-error
|
||||
result.current.singleDocProps.onClick();
|
||||
expect(history.location.pathname).toEqual(getSingeDocRoute());
|
||||
expect(history.location.search).toEqual(`?id=${defaultProps.rowId}`);
|
||||
expect(history.location.state?.breadcrumb).toEqual(`#/${getSearch()}`);
|
||||
|
@ -72,7 +73,8 @@ describe('useNavigationProps', () => {
|
|||
test('should provide valid breadcrumb for context page from main view', () => {
|
||||
const { result, history } = render();
|
||||
|
||||
result.current.surrDocsProps.onClick?.();
|
||||
// @ts-expect-error
|
||||
result.current.surrDocsProps.onClick();
|
||||
expect(history.location.pathname).toEqual(getContextRoute());
|
||||
expect(history.location.search).toEqual(
|
||||
`?${getContextHash(defaultProps.columns, filterManager)}`
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { MouseEventHandler, useMemo, useRef } from 'react';
|
||||
import { useHistory, matchPath } from 'react-router-dom';
|
||||
import type { Location } from 'history';
|
||||
import { stringify } from 'query-string';
|
||||
|
@ -86,6 +86,15 @@ export const useNavigationProps = ({
|
|||
[columns, filterManager]
|
||||
);
|
||||
|
||||
const singleDocHref = addBasePath(
|
||||
`/app/discover#/doc/${indexPatternId}/${rowIndex}?id=${encodeURIComponent(rowId)}`
|
||||
);
|
||||
const surDocsHref = addBasePath(
|
||||
`/app/discover#/context/${encodeURIComponent(indexPatternId)}/${encodeURIComponent(
|
||||
rowId
|
||||
)}?${contextSearchHash}`
|
||||
);
|
||||
|
||||
/**
|
||||
* When history can be accessed via hooks,
|
||||
* it is discover main or context route.
|
||||
|
@ -96,7 +105,9 @@ export const useNavigationProps = ({
|
|||
exact: true,
|
||||
});
|
||||
|
||||
const onOpenSingleDoc = () => {
|
||||
const onOpenSingleDoc: MouseEventHandler<HTMLAnchorElement> = (event) => {
|
||||
event?.preventDefault?.();
|
||||
|
||||
history.push({
|
||||
pathname: `/doc/${indexPatternId}/${rowIndex}`,
|
||||
search: `?id=${encodeURIComponent(rowId)}`,
|
||||
|
@ -106,7 +117,9 @@ export const useNavigationProps = ({
|
|||
});
|
||||
};
|
||||
|
||||
const onOpenSurrDocs = () =>
|
||||
const onOpenSurrDocs: MouseEventHandler<HTMLAnchorElement> = (event) => {
|
||||
event?.preventDefault?.();
|
||||
|
||||
history.push({
|
||||
pathname: `/context/${encodeURIComponent(indexPatternId)}/${encodeURIComponent(
|
||||
String(rowId)
|
||||
|
@ -116,26 +129,21 @@ export const useNavigationProps = ({
|
|||
breadcrumb: getCurrentBreadcrumbs(!!isContextRoute, currentLocation, prevBreadcrumb),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
singleDocProps: { onClick: onOpenSingleDoc },
|
||||
surrDocsProps: { onClick: onOpenSurrDocs },
|
||||
singleDocProps: { onClick: onOpenSingleDoc, href: singleDocHref },
|
||||
surrDocsProps: { onClick: onOpenSurrDocs, href: surDocsHref },
|
||||
};
|
||||
}
|
||||
|
||||
// for embeddable absolute href should be kept
|
||||
return {
|
||||
singleDocProps: {
|
||||
href: addBasePath(
|
||||
`/app/discover#/doc/${indexPatternId}/${rowIndex}?id=${encodeURIComponent(rowId)}`
|
||||
),
|
||||
href: singleDocHref,
|
||||
},
|
||||
surrDocsProps: {
|
||||
href: addBasePath(
|
||||
`/app/discover#/context/${encodeURIComponent(indexPatternId)}/${encodeURIComponent(
|
||||
rowId
|
||||
)}?${contextSearchHash}`
|
||||
),
|
||||
href: surDocsHref,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,6 +10,12 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const esSupertest = getService('esSupertest');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const find = getService('find');
|
||||
const indexPatterns = getService('indexPatterns');
|
||||
const retry = getService('retry');
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
const security = getService('security');
|
||||
const globalNav = getService('globalNav');
|
||||
const PageObjects = getPageObjects([
|
||||
|
@ -27,6 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const queryBar = getService('queryBar');
|
||||
const savedQueryManagementComponent = getService('savedQueryManagementComponent');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const logstashIndexName = 'logstash-2015.09.22';
|
||||
|
||||
async function setDiscoverTimeRange() {
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||
|
@ -443,5 +450,116 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await PageObjects.error.expectForbidden();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when has privileges to read data views but no privileges to read index', () => {
|
||||
before(async () => {
|
||||
await esSupertest
|
||||
.post('/_aliases')
|
||||
.send({
|
||||
actions: [
|
||||
{
|
||||
add: { index: logstashIndexName, alias: 'alias-logstash-discover' },
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await indexPatterns.create(
|
||||
{ title: 'alias-logstash-discover', timeFieldName: '@timestamp' },
|
||||
{ override: true }
|
||||
);
|
||||
|
||||
await security.role.create('discover_only_data_views_role', {
|
||||
elasticsearch: {
|
||||
indices: [
|
||||
{ names: ['alias-logstash-discover'], privileges: ['read', 'view_index_metadata'] },
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
discover: ['read'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create('discover_only_data_views_user', {
|
||||
password: 'discover_only_data_views_user-password',
|
||||
roles: ['discover_only_data_views_role'],
|
||||
full_name: 'test user',
|
||||
});
|
||||
|
||||
await PageObjects.security.login(
|
||||
'discover_only_data_views_user',
|
||||
'discover_only_data_views_user-password',
|
||||
{
|
||||
expectSpaceSelector: false,
|
||||
}
|
||||
);
|
||||
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esSupertest
|
||||
.post('/_aliases')
|
||||
.send({
|
||||
actions: [
|
||||
{
|
||||
remove: { index: logstashIndexName, alias: 'alias-logstash-discover' },
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await security.role.delete('discover_only_data_views_role');
|
||||
await security.user.delete('discover_only_data_views_user');
|
||||
});
|
||||
|
||||
it('allows to access only via a permitted index alias', async () => {
|
||||
await globalNav.badgeExistsOrFail('Read only');
|
||||
|
||||
// can't access logstash index directly
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.existOrFail('discoverNoResultsCheckIndices');
|
||||
|
||||
// but can access via a permitted alias for the logstash index
|
||||
await PageObjects.discover.selectIndexPattern('alias-logstash-discover');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await setDiscoverTimeRange();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.missingOrFail('discoverNoResultsCheckIndices');
|
||||
await PageObjects.discover.waitForDocTableLoadingComplete();
|
||||
|
||||
// expand a row
|
||||
await dataGrid.clickRowToggle();
|
||||
|
||||
// check the fields tab
|
||||
await retry.waitForWithTimeout(
|
||||
'index in flyout fields tab is matching the logstash index',
|
||||
5000,
|
||||
async () => {
|
||||
return (
|
||||
(await testSubjects.getVisibleText('tableDocViewRow-_index-value')) ===
|
||||
logstashIndexName
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// check the JSON tab
|
||||
await find.clickByCssSelectorWhenNotDisabled('#kbn_doc_viewer_tab_1');
|
||||
await retry.waitForWithTimeout(
|
||||
'index in flyout JSON tab is matching the logstash index',
|
||||
5000,
|
||||
async () => {
|
||||
const text = await monacoEditor.getCodeEditorValue();
|
||||
return JSON.parse(text)._index === logstashIndexName;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue