[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:
Julia Rechkunova 2022-07-04 14:56:58 +02:00 committed by GitHub
parent ffaa9e4a6b
commit 1dbdf128e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 35 deletions

View file

@ -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",
},
},
],
},
},

View file

@ -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,

View file

@ -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)}`

View file

@ -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,
},
};
};

View file

@ -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;
}
);
});
});
});
}