Improved Visualize button in field popover (#103099)
* Improve field popover * Slightly improve type safteyness * Add unit tests for visualize trigger utils * Remove unused div Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5abac25ba3
commit
bf6c53bb45
|
@ -1,705 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`discover sidebar field details footer renders properly 1`] = `
|
||||
<DiscoverFieldDetailsFooter
|
||||
details={
|
||||
Object {
|
||||
"buckets": Array [],
|
||||
"columns": Array [],
|
||||
"error": "",
|
||||
"exists": 1,
|
||||
"total": 2,
|
||||
}
|
||||
}
|
||||
field={
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 10,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"long",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "bytes",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "number",
|
||||
}
|
||||
}
|
||||
indexPattern={
|
||||
StubIndexPattern {
|
||||
"_reindexFields": [Function],
|
||||
"fieldFormatMap": Object {},
|
||||
"fields": FldList [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 10,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"long",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "bytes",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 20,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"boolean",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "ssl",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 30,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"date",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "@timestamp",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 30,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"date",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "time",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "@tags",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"date",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "utc_time",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"integer",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "phpmemory",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"ip",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "ip",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "ip",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"attachment",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "request_body",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "attachment",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"geo_point",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "point",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "geo_point",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"geo_shape",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "area",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "geo_shape",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": false,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"murmur3",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "hashed",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "murmur3",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"geo_point",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "geo.coordinates",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "geo_point",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"text",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "extension",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "extension.keyword",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": Object {
|
||||
"multi": Object {
|
||||
"parent": "extension",
|
||||
},
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"text",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "machine.os",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "machine.os.raw",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": Object {
|
||||
"multi": Object {
|
||||
"parent": "machine.os",
|
||||
},
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "geo.src",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"_id",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "_id",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"_type",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "_type",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"_source",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "_source",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "_source",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"text",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "non-filterable",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": false,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": false,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"text",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "non-sortable",
|
||||
"readFromDocValues": false,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": false,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"conflict",
|
||||
],
|
||||
"lang": undefined,
|
||||
"name": "custom_user_field",
|
||||
"readFromDocValues": true,
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "conflict",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"text",
|
||||
],
|
||||
"lang": "expression",
|
||||
"name": "script string",
|
||||
"readFromDocValues": false,
|
||||
"script": "'i am a string'",
|
||||
"scripted": true,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"long",
|
||||
],
|
||||
"lang": "expression",
|
||||
"name": "script number",
|
||||
"readFromDocValues": false,
|
||||
"script": "1234",
|
||||
"scripted": true,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"date",
|
||||
],
|
||||
"lang": "painless",
|
||||
"name": "script date",
|
||||
"readFromDocValues": false,
|
||||
"script": "1234",
|
||||
"scripted": true,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"conflictDescriptions": undefined,
|
||||
"count": 0,
|
||||
"customLabel": undefined,
|
||||
"esTypes": Array [
|
||||
"murmur3",
|
||||
],
|
||||
"lang": "expression",
|
||||
"name": "script murmur3",
|
||||
"readFromDocValues": false,
|
||||
"script": "1234",
|
||||
"scripted": true,
|
||||
"searchable": true,
|
||||
"subType": undefined,
|
||||
"type": "murmur3",
|
||||
},
|
||||
],
|
||||
"fieldsFetcher": Object {
|
||||
"apiClient": Object {
|
||||
"baseUrl": "",
|
||||
},
|
||||
},
|
||||
"flattenHit": [Function],
|
||||
"formatField": [Function],
|
||||
"formatHit": [Function],
|
||||
"getComputedFields": [Function],
|
||||
"getConfig": [Function],
|
||||
"getFieldByName": [Function],
|
||||
"getFormatterForField": [Function],
|
||||
"getNonScriptedFields": [Function],
|
||||
"getScriptedFields": [Function],
|
||||
"getSourceFiltering": [Function],
|
||||
"id": "logstash-*",
|
||||
"isTimeBased": [Function],
|
||||
"metaFields": Array [
|
||||
"_id",
|
||||
"_type",
|
||||
"_source",
|
||||
],
|
||||
"popularizeField": [Function],
|
||||
"setFieldFormat": [Function],
|
||||
"stubSetFieldFormat": [Function],
|
||||
"timeFieldName": "time",
|
||||
"title": "logstash-*",
|
||||
}
|
||||
}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
onAddFilter={[MockFunction]}
|
||||
>
|
||||
<EuiPopoverFooter>
|
||||
<div
|
||||
className="euiPopoverFooter"
|
||||
>
|
||||
<EuiText
|
||||
size="xs"
|
||||
textAlign="center"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--extraSmall"
|
||||
>
|
||||
<EuiTextAlign
|
||||
textAlign="center"
|
||||
>
|
||||
<div
|
||||
className="euiTextAlign euiTextAlign--center"
|
||||
>
|
||||
<EuiLink
|
||||
data-test-subj="onAddFilterButton"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="euiLink euiLink--primary"
|
||||
data-test-subj="onAddFilterButton"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Exists in {value} / {totalValue} records"
|
||||
id="discover.fieldChooser.detailViews.existsInRecordsText"
|
||||
values={
|
||||
Object {
|
||||
"totalValue": 2,
|
||||
"value": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
Exists in 1 / 2 records
|
||||
</FormattedMessage>
|
||||
</button>
|
||||
</EuiLink>
|
||||
</div>
|
||||
</EuiTextAlign>
|
||||
</div>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiPopoverFooter>
|
||||
</DiscoverFieldDetailsFooter>
|
||||
`;
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import './discover_field.scss';
|
||||
|
||||
import React, { useState, useCallback, memo } from 'react';
|
||||
import React, { useState, useCallback, memo, useMemo } from 'react';
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
|
@ -18,6 +18,7 @@ import {
|
|||
EuiIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
|
@ -27,7 +28,7 @@ import { FieldIcon, FieldButton } from '../../../../../../../kibana_react/public
|
|||
import { FieldDetails } from './types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../../../data/public';
|
||||
import { getFieldTypeName } from './lib/get_field_type_name';
|
||||
import { DiscoverFieldDetailsFooter } from './discover_field_details_footer';
|
||||
import { DiscoverFieldVisualize } from './discover_field_visualize';
|
||||
|
||||
function wrapOnDot(str?: string) {
|
||||
// u200B is a non-width white-space character, which allows
|
||||
|
@ -172,6 +173,7 @@ const MultiFields: React.FC<MultiFieldsProps> = memo(
|
|||
})}
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
{multiFields.map((entry) => (
|
||||
<FieldButton
|
||||
size="s"
|
||||
|
@ -282,6 +284,8 @@ function DiscoverFieldComponent({
|
|||
setOpen(!infoIsOpen);
|
||||
}, [infoIsOpen]);
|
||||
|
||||
const rawMultiFields = useMemo(() => multiFields?.map((f) => f.field), [multiFields]);
|
||||
|
||||
if (field.type === '_source') {
|
||||
return (
|
||||
<FieldButton
|
||||
|
@ -399,23 +403,24 @@ function DiscoverFieldComponent({
|
|||
field={field}
|
||||
details={details}
|
||||
onAddFilter={onAddFilter}
|
||||
trackUiMetric={trackUiMetric}
|
||||
/>
|
||||
{multiFields && (
|
||||
<MultiFields
|
||||
multiFields={multiFields}
|
||||
alwaysShowActionButton={alwaysShowActionButton}
|
||||
toggleDisplay={toggleDisplay}
|
||||
/>
|
||||
)}
|
||||
{!details.error && (
|
||||
<DiscoverFieldDetailsFooter
|
||||
indexPattern={indexPattern}
|
||||
field={field}
|
||||
details={details}
|
||||
onAddFilter={onAddFilter}
|
||||
/>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<MultiFields
|
||||
multiFields={multiFields}
|
||||
alwaysShowActionButton={alwaysShowActionButton}
|
||||
toggleDisplay={toggleDisplay}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<DiscoverFieldVisualize
|
||||
field={field}
|
||||
indexPattern={indexPattern}
|
||||
multiFields={rawMultiFields}
|
||||
trackUiMetric={trackUiMetric}
|
||||
details={details}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</EuiPopover>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.dscFieldDetails {
|
||||
color: $euiTextColor;
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
|
||||
.dscFieldDetails__visualizeBtn {
|
||||
@include euiFontSizeXS;
|
||||
height: $euiSizeL !important;
|
||||
min-width: $euiSize * 4;
|
||||
}
|
|
@ -25,10 +25,11 @@ const indexPattern = getStubIndexPattern(
|
|||
);
|
||||
|
||||
describe('discover sidebar field details', function () {
|
||||
const onAddFilter = jest.fn();
|
||||
const defaultProps = {
|
||||
indexPattern,
|
||||
details: { buckets: [], error: '', exists: 1, total: 2, columns: [] },
|
||||
onAddFilter: jest.fn(),
|
||||
onAddFilter,
|
||||
};
|
||||
|
||||
function mountComponent(field: IndexPatternField) {
|
||||
|
@ -36,7 +37,7 @@ describe('discover sidebar field details', function () {
|
|||
return mountWithIntl(<DiscoverFieldDetails {...compProps} />);
|
||||
}
|
||||
|
||||
it('should enable the visualize link for a number field', function () {
|
||||
it('click on addFilter calls the function', function () {
|
||||
const visualizableField = new IndexPatternField({
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
|
@ -47,37 +48,9 @@ describe('discover sidebar field details', function () {
|
|||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
});
|
||||
const comp = mountComponent(visualizableField);
|
||||
expect(findTestSubject(comp, 'fieldVisualize-bytes')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should disable the visualize link for an _id field', function () {
|
||||
const conflictField = new IndexPatternField({
|
||||
name: '_id',
|
||||
type: 'string',
|
||||
esTypes: ['_id'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
});
|
||||
const comp = mountComponent(conflictField);
|
||||
expect(findTestSubject(comp, 'fieldVisualize-_id')).toEqual({});
|
||||
});
|
||||
|
||||
it('should disable the visualize link for an unknown field', function () {
|
||||
const unknownField = new IndexPatternField({
|
||||
name: 'test',
|
||||
type: 'unknown',
|
||||
esTypes: ['double'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
});
|
||||
const comp = mountComponent(unknownField);
|
||||
expect(findTestSubject(comp, 'fieldVisualize-test')).toEqual({});
|
||||
const component = mountComponent(visualizableField);
|
||||
const onAddButton = findTestSubject(component, 'onAddFilterButton');
|
||||
onAddButton.simulate('click');
|
||||
expect(onAddFilter).toHaveBeenCalledWith('_exists_', visualizableField.name, '+');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,27 +6,18 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { EuiIconTip, EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { EuiText, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
|
||||
import { DiscoverFieldBucket } from './discover_field_bucket';
|
||||
import { getWarnings } from './lib/get_warnings';
|
||||
import {
|
||||
triggerVisualizeActions,
|
||||
isFieldVisualizable,
|
||||
getVisualizeHref,
|
||||
} from './lib/visualize_trigger_utils';
|
||||
import { Bucket, FieldDetails } from './types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../../../data/public';
|
||||
import './discover_field_details.scss';
|
||||
|
||||
interface DiscoverFieldDetailsProps {
|
||||
field: IndexPatternField;
|
||||
indexPattern: IndexPattern;
|
||||
details: FieldDetails;
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
|
||||
}
|
||||
|
||||
export function DiscoverFieldDetails({
|
||||
|
@ -34,46 +25,12 @@ export function DiscoverFieldDetails({
|
|||
indexPattern,
|
||||
details,
|
||||
onAddFilter,
|
||||
trackUiMetric,
|
||||
}: DiscoverFieldDetailsProps) {
|
||||
const warnings = getWarnings(field);
|
||||
const [showVisualizeLink, setShowVisualizeLink] = useState<boolean>(false);
|
||||
const [visualizeLink, setVisualizeLink] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
isFieldVisualizable(field, indexPattern.id, details.columns).then(
|
||||
(flag) => {
|
||||
setShowVisualizeLink(flag);
|
||||
// get href only if Visualize button is enabled
|
||||
getVisualizeHref(field, indexPattern.id, details.columns).then(
|
||||
(uri) => {
|
||||
if (uri) setVisualizeLink(uri);
|
||||
},
|
||||
() => {
|
||||
setVisualizeLink('');
|
||||
}
|
||||
);
|
||||
},
|
||||
() => {
|
||||
setShowVisualizeLink(false);
|
||||
}
|
||||
);
|
||||
}, [field, indexPattern.id, details.columns]);
|
||||
|
||||
const handleVisualizeLinkClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
// regular link click. let the uiActions code handle the navigation and show popup if needed
|
||||
event.preventDefault();
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'visualize_link_click');
|
||||
}
|
||||
triggerVisualizeActions(field, indexPattern.id, details.columns);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dscFieldDetails">
|
||||
{details.error && <EuiText size="xs">{details.error}</EuiText>}
|
||||
{!details.error && (
|
||||
{details.error && <EuiText size="xs">{details.error}</EuiText>}
|
||||
{!details.error && (
|
||||
<>
|
||||
<div style={{ marginTop: '4px' }}>
|
||||
{details.buckets.map((bucket: Bucket, idx: number) => (
|
||||
<DiscoverFieldBucket
|
||||
|
@ -84,30 +41,35 @@ export function DiscoverFieldDetails({
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showVisualizeLink && (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButton
|
||||
onClick={(e) => handleVisualizeLinkClick(e)}
|
||||
href={visualizeLink}
|
||||
size="s"
|
||||
className="dscFieldDetails__visualizeBtn"
|
||||
data-test-subj={`fieldVisualize-${field.name}`}
|
||||
>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs">
|
||||
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
|
||||
<EuiLink
|
||||
onClick={() => onAddFilter('_exists_', field.name, '+')}
|
||||
data-test-subj="onAddFilterButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="discover.fieldChooser.detailViews.existsInRecordsText"
|
||||
defaultMessage="Exists in {value} / {totalValue} records"
|
||||
values={{
|
||||
value: details.exists,
|
||||
totalValue: details.total,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="discover.fieldChooser.detailViews.visualizeLinkText"
|
||||
defaultMessage="Visualize"
|
||||
id="discover.fieldChooser.detailViews.valueOfRecordsText"
|
||||
defaultMessage="{value} / {totalValue} records"
|
||||
values={{
|
||||
value: details.exists,
|
||||
totalValue: details.total,
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
{warnings.length > 0 && (
|
||||
<EuiIconTip type="alert" color="warning" content={warnings.join(' ')} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
import { DiscoverFieldDetailsFooter } from './discover_field_details_footer';
|
||||
|
||||
const indexPattern = getStubIndexPattern(
|
||||
'logstash-*',
|
||||
(cfg: unknown) => cfg,
|
||||
'time',
|
||||
stubbedLogstashFields(),
|
||||
coreMock.createSetup()
|
||||
);
|
||||
|
||||
describe('discover sidebar field details footer', function () {
|
||||
const onAddFilter = jest.fn();
|
||||
const defaultProps = {
|
||||
indexPattern,
|
||||
details: { buckets: [], error: '', exists: 1, total: 2, columns: [] },
|
||||
onAddFilter,
|
||||
};
|
||||
|
||||
function mountComponent(field: IndexPatternField) {
|
||||
const compProps = { ...defaultProps, field };
|
||||
return mountWithIntl(<DiscoverFieldDetailsFooter {...compProps} />);
|
||||
}
|
||||
|
||||
it('renders properly', function () {
|
||||
const visualizableField = new IndexPatternField({
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
esTypes: ['long'],
|
||||
count: 10,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
});
|
||||
const component = mountComponent(visualizableField);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('click on addFilter calls the function', function () {
|
||||
const visualizableField = new IndexPatternField({
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
esTypes: ['long'],
|
||||
count: 10,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
});
|
||||
const component = mountComponent(visualizableField);
|
||||
const onAddButton = findTestSubject(component, 'onAddFilterButton');
|
||||
onAddButton.simulate('click');
|
||||
expect(onAddFilter).toHaveBeenCalledWith('_exists_', visualizableField.name, '+');
|
||||
});
|
||||
});
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiLink, EuiPopoverFooter, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { IndexPatternField } from '../../../../../../../data/common/index_patterns/fields';
|
||||
import { IndexPattern } from '../../../../../../../data/common/index_patterns/index_patterns';
|
||||
import { FieldDetails } from './types';
|
||||
|
||||
interface DiscoverFieldDetailsFooterProps {
|
||||
field: IndexPatternField;
|
||||
indexPattern: IndexPattern;
|
||||
details: FieldDetails;
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
}
|
||||
|
||||
export function DiscoverFieldDetailsFooter({
|
||||
field,
|
||||
indexPattern,
|
||||
details,
|
||||
onAddFilter,
|
||||
}: DiscoverFieldDetailsFooterProps) {
|
||||
return (
|
||||
<EuiPopoverFooter>
|
||||
<EuiText size="xs" textAlign="center">
|
||||
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
|
||||
<EuiLink
|
||||
onClick={() => onAddFilter('_exists_', field.name, '+')}
|
||||
data-test-subj="onAddFilterButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="discover.fieldChooser.detailViews.existsInRecordsText"
|
||||
defaultMessage="Exists in {value} / {totalValue} records"
|
||||
values={{
|
||||
value: details.exists,
|
||||
totalValue: details.total,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="discover.fieldChooser.detailViews.valueOfRecordsText"
|
||||
defaultMessage="{value} / {totalValue} records"
|
||||
values={{
|
||||
value: details.exists,
|
||||
totalValue: details.total,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiPopoverFooter>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiButton, EuiPopoverFooter } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
|
||||
import type { IndexPattern, IndexPatternField } from 'src/plugins/data/common';
|
||||
|
||||
import { triggerVisualizeActions, VisualizeInformation } from './lib/visualize_trigger_utils';
|
||||
import type { FieldDetails } from './types';
|
||||
import { getVisualizeInformation } from './lib/visualize_trigger_utils';
|
||||
|
||||
interface Props {
|
||||
field: IndexPatternField;
|
||||
indexPattern: IndexPattern;
|
||||
details: FieldDetails;
|
||||
multiFields?: IndexPatternField[];
|
||||
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
|
||||
}
|
||||
|
||||
export const DiscoverFieldVisualize: React.FC<Props> = React.memo(
|
||||
({ field, indexPattern, details, trackUiMetric, multiFields }) => {
|
||||
const [visualizeInfo, setVisualizeInfo] = useState<VisualizeInformation>();
|
||||
|
||||
useEffect(() => {
|
||||
getVisualizeInformation(field, indexPattern.id, details.columns, multiFields).then(
|
||||
setVisualizeInfo
|
||||
);
|
||||
}, [details.columns, field, indexPattern, multiFields]);
|
||||
|
||||
if (!visualizeInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleVisualizeLinkClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
// regular link click. let the uiActions code handle the navigation and show popup if needed
|
||||
event.preventDefault();
|
||||
trackUiMetric?.(METRIC_TYPE.CLICK, 'visualize_link_click');
|
||||
triggerVisualizeActions(visualizeInfo.field, indexPattern.id, details.columns);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPopoverFooter>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButton
|
||||
fullWidth
|
||||
size="s"
|
||||
href={visualizeInfo.href}
|
||||
onClick={handleVisualizeLinkClick}
|
||||
data-test-subj={`fieldVisualize-${field.name}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="discover.fieldChooser.visualizeButton.label"
|
||||
defaultMessage="Visualize"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiPopoverFooter>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IndexPatternField } from '../../../../../../../../data/public';
|
||||
|
||||
export function getWarnings(field: IndexPatternField) {
|
||||
let warnings = [];
|
||||
|
||||
if (field.scripted) {
|
||||
warnings.push(
|
||||
i18n.translate(
|
||||
'discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription',
|
||||
{
|
||||
defaultMessage: 'Scripted fields can take a long time to execute.',
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (warnings.length > 1) {
|
||||
warnings = warnings.map(function (warning, i) {
|
||||
return (i > 0 ? '\n' : '') + (i + 1) + ' - ' + warning;
|
||||
});
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { IndexPatternField } from 'src/plugins/data/common';
|
||||
import type { Action } from 'src/plugins/ui_actions/public';
|
||||
import { getVisualizeInformation } from './visualize_trigger_utils';
|
||||
|
||||
const field = {
|
||||
name: 'fieldName',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 1,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
visualizable: true,
|
||||
} as IndexPatternField;
|
||||
|
||||
const mockGetActions = jest.fn<Promise<Array<Action<object>>>, [string, { fieldName: string }]>(
|
||||
() => Promise.resolve([])
|
||||
);
|
||||
|
||||
jest.mock('../../../../../../kibana_services', () => ({
|
||||
getUiActions: () => ({
|
||||
getTriggerCompatibleActions: mockGetActions,
|
||||
}),
|
||||
}));
|
||||
|
||||
const action: Action = {
|
||||
id: 'action',
|
||||
type: 'VISUALIZE_FIELD',
|
||||
getIconType: () => undefined,
|
||||
getDisplayName: () => 'Action',
|
||||
isCompatible: () => Promise.resolve(true),
|
||||
execute: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
describe('visualize_trigger_utils', () => {
|
||||
afterEach(() => {
|
||||
mockGetActions.mockReset();
|
||||
});
|
||||
|
||||
describe('getVisualizeInformation', () => {
|
||||
it('should return for a visualizeable field with an action', async () => {
|
||||
mockGetActions.mockResolvedValue([action]);
|
||||
const information = await getVisualizeInformation(field, '1', [], undefined);
|
||||
expect(information).not.toBeUndefined();
|
||||
expect(information?.field).toHaveProperty('name', 'fieldName');
|
||||
expect(information?.href).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return field and href from the action', async () => {
|
||||
mockGetActions.mockResolvedValue([{ ...action, getHref: () => Promise.resolve('hreflink') }]);
|
||||
const information = await getVisualizeInformation(field, '1', [], undefined);
|
||||
expect(information).not.toBeUndefined();
|
||||
expect(information?.field).toHaveProperty('name', 'fieldName');
|
||||
expect(information).toHaveProperty('href', 'hreflink');
|
||||
});
|
||||
|
||||
it('should return undefined if no field has a compatible action', async () => {
|
||||
mockGetActions.mockResolvedValue([]);
|
||||
const information = await getVisualizeInformation(
|
||||
{ ...field, name: 'rootField' } as IndexPatternField,
|
||||
'1',
|
||||
[],
|
||||
[
|
||||
{ ...field, name: 'multi1' },
|
||||
{ ...field, name: 'multi2' },
|
||||
] as IndexPatternField[]
|
||||
);
|
||||
expect(information).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return information for the root field, when multi fields and root are having actions', async () => {
|
||||
mockGetActions.mockResolvedValue([action]);
|
||||
const information = await getVisualizeInformation(
|
||||
{ ...field, name: 'rootField' } as IndexPatternField,
|
||||
'1',
|
||||
[],
|
||||
[
|
||||
{ ...field, name: 'multi1' },
|
||||
{ ...field, name: 'multi2' },
|
||||
] as IndexPatternField[]
|
||||
);
|
||||
expect(information).not.toBeUndefined();
|
||||
expect(information?.field).toHaveProperty('name', 'rootField');
|
||||
});
|
||||
|
||||
it('should return information for first multi field that has a compatible action', async () => {
|
||||
mockGetActions.mockImplementation(async (_, { fieldName }) => {
|
||||
if (fieldName === 'multi2' || fieldName === 'multi3') {
|
||||
return [action];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const information = await getVisualizeInformation(
|
||||
{ ...field, name: 'rootField' } as IndexPatternField,
|
||||
'1',
|
||||
[],
|
||||
[
|
||||
{ ...field, name: 'multi1' },
|
||||
{ ...field, name: 'multi2' },
|
||||
{ ...field, name: 'multi3' },
|
||||
] as IndexPatternField[]
|
||||
);
|
||||
expect(information).not.toBeUndefined();
|
||||
expect(information?.field).toHaveProperty('name', 'multi2');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -41,30 +41,6 @@ async function getCompatibleActions(
|
|||
return compatibleActions;
|
||||
}
|
||||
|
||||
export async function getVisualizeHref(
|
||||
field: IndexPatternField,
|
||||
indexPatternId: string | undefined,
|
||||
contextualFields: string[]
|
||||
) {
|
||||
if (!indexPatternId) return undefined;
|
||||
const triggerOptions = {
|
||||
indexPatternId,
|
||||
fieldName: field.name,
|
||||
contextualFields,
|
||||
trigger: getTrigger(field.type),
|
||||
};
|
||||
const compatibleActions = await getCompatibleActions(
|
||||
field.name,
|
||||
indexPatternId,
|
||||
contextualFields,
|
||||
getTriggerConstant(field.type)
|
||||
);
|
||||
// enable the link only if only one action is registered
|
||||
return compatibleActions.length === 1
|
||||
? compatibleActions[0].getHref?.(triggerOptions)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function triggerVisualizeActions(
|
||||
field: IndexPatternField,
|
||||
indexPatternId: string | undefined,
|
||||
|
@ -80,21 +56,55 @@ export function triggerVisualizeActions(
|
|||
getUiActions().getTrigger(trigger).exec(triggerOptions);
|
||||
}
|
||||
|
||||
export async function isFieldVisualizable(
|
||||
export interface VisualizeInformation {
|
||||
field: IndexPatternField;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field name and potentially href of the field or the first multi-field
|
||||
* that has a compatible visualize uiAction.
|
||||
*/
|
||||
export async function getVisualizeInformation(
|
||||
field: IndexPatternField,
|
||||
indexPatternId: string | undefined,
|
||||
contextualFields: string[]
|
||||
) {
|
||||
contextualFields: string[],
|
||||
multiFields: IndexPatternField[] = []
|
||||
): Promise<VisualizeInformation | undefined> {
|
||||
if (field.name === '_id' || !indexPatternId) {
|
||||
// for first condition you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
|
||||
return false;
|
||||
// _id fields are not visualizeable in ES
|
||||
return undefined;
|
||||
}
|
||||
const trigger = getTriggerConstant(field.type);
|
||||
const compatibleActions = await getCompatibleActions(
|
||||
field.name,
|
||||
indexPatternId,
|
||||
contextualFields,
|
||||
trigger
|
||||
);
|
||||
return compatibleActions.length > 0 && field.visualizable;
|
||||
|
||||
for (const f of [field, ...multiFields]) {
|
||||
if (!f.visualizable) {
|
||||
continue;
|
||||
}
|
||||
// Retrieve compatible actions for the specific field
|
||||
const actions = await getCompatibleActions(
|
||||
f.name,
|
||||
indexPatternId,
|
||||
contextualFields,
|
||||
getTriggerConstant(f.type)
|
||||
);
|
||||
|
||||
// if the field has compatible actions use this field for visualizing
|
||||
if (actions.length > 0) {
|
||||
const triggerOptions = {
|
||||
indexPatternId,
|
||||
fieldName: f.name,
|
||||
contextualFields,
|
||||
trigger: getTrigger(f.type),
|
||||
};
|
||||
|
||||
return {
|
||||
field: f,
|
||||
// We use the href of the first action always. Multiple actions will only work
|
||||
// via the modal shown by triggerVisualizeActions that should be called via onClick.
|
||||
href: await actions[0].getHref?.(triggerOptions),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -1625,7 +1625,6 @@
|
|||
"discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "{field}を除外:\"{value}\"",
|
||||
"discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "{field}を除外:\"{value}\"",
|
||||
"discover.fieldChooser.detailViews.valueOfRecordsText": "{value} / {totalValue}件のレコード",
|
||||
"discover.fieldChooser.detailViews.visualizeLinkText": "可視化",
|
||||
"discover.fieldChooser.discoverField.addButtonAriaLabel": "{field}を表に追加",
|
||||
"discover.fieldChooser.discoverField.addFieldTooltip": "フィールドを列として追加",
|
||||
"discover.fieldChooser.discoverField.deleteFieldLabel": "インデックスパターンフィールドを削除",
|
||||
|
@ -1634,7 +1633,6 @@
|
|||
"discover.fieldChooser.discoverField.multiFields": "マルチフィールド",
|
||||
"discover.fieldChooser.discoverField.removeButtonAriaLabel": "{field}を表から削除",
|
||||
"discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除",
|
||||
"discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "スクリプトフィールドは実行に時間がかかる場合があります。",
|
||||
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。",
|
||||
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。",
|
||||
"discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "このフィールドはElasticsearchマッピングに表示されますが、ドキュメントテーブルの{hitsLength}件のドキュメントには含まれません。可視化や検索は可能な場合があります。",
|
||||
|
|
|
@ -1634,7 +1634,6 @@
|
|||
"discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "筛除 {field}:“{value}”",
|
||||
"discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "筛留 {field}:“{value}”",
|
||||
"discover.fieldChooser.detailViews.valueOfRecordsText": "{value} / {totalValue} 条记录",
|
||||
"discover.fieldChooser.detailViews.visualizeLinkText": "Visualize",
|
||||
"discover.fieldChooser.discoverField.addButtonAriaLabel": "将 {field} 添加到表中",
|
||||
"discover.fieldChooser.discoverField.addFieldTooltip": "将字段添加为列",
|
||||
"discover.fieldChooser.discoverField.deleteFieldLabel": "删除索引模式字段",
|
||||
|
@ -1643,7 +1642,6 @@
|
|||
"discover.fieldChooser.discoverField.multiFields": "多字段",
|
||||
"discover.fieldChooser.discoverField.removeButtonAriaLabel": "从表中移除 {field}",
|
||||
"discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段",
|
||||
"discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "脚本字段执行时间会很长。",
|
||||
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "分析不适用于地理字段。",
|
||||
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "分析不适用于对象字段。",
|
||||
"discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "此字段在您的 Elasticsearch 映射中,但不在文档表中显示的 {hitsLength} 个文档中。您可能仍能够基于它可视化或搜索。",
|
||||
|
|
Loading…
Reference in a new issue