diff --git a/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts b/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts index 6418023586084..9c6ea500c97c7 100644 --- a/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts +++ b/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts @@ -28,7 +28,7 @@ export type DateHistogramColumnParams = DateHistogramIndexPatternColumn['params' export type TopValuesColumnParams = Pick< TermsIndexPatternColumn['params'], - 'size' | 'orderDirection' | 'orderBy' | 'secondaryFields' | 'accuracyMode' + 'size' | 'orderDirection' | 'orderBy' | 'secondaryFields' | 'accuracyMode' | 'orderAgg' >; export const getHistogramColumn = ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index 9b37c661d923a..5548228b9b64c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -47,6 +47,7 @@ export const mockKibanaValues = { indexMappingComponent: null, isCloud: false, isSidebarEnabled: true, + kibanaVersion: null, lens: { EmbeddableComponent: jest.fn(), stateHelperApi: jest.fn().mockResolvedValue({ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx index 46e7998444729..ea5b59c4d3263 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx @@ -34,12 +34,14 @@ export interface DockerInstructionsStepProps { hasApiKey: boolean; isWaitingForConnector: boolean; serviceType: string; + connectorVersion: string; } export const DockerInstructionsStep: React.FC = ({ connectorId, isWaitingForConnector, serviceType, apiKeyData, + connectorVersion, }) => { const [isOpen, setIsOpen] = React.useState('open'); const { elasticsearchUrl } = useCloudDetails(); @@ -163,7 +165,7 @@ export const DockerInstructionsStep: React.FC = ({ showTopBar={false} languageType="bash" codeSnippet={getRunFromDockerSnippet({ - version: '8.15.0', + version: connectorVersion, })} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx index 2a617a87df8bc..7bf1f101cc71a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx @@ -30,6 +30,7 @@ import { ConnectorStatus } from '@kbn/search-connectors'; import { Status } from '../../../../../common/types/api'; +import { KibanaLogic } from '../../../shared/kibana'; import { GetApiKeyByIdLogic } from '../../api/api_key/get_api_key_by_id_api_logic'; import { GenerateConnectorApiKeyApiLogic } from '../../api/connector/generate_connector_api_key_api_logic'; @@ -48,6 +49,7 @@ export const ConnectorDeployment: React.FC = () => { const [selectedDeploymentMethod, setSelectedDeploymentMethod] = useState<'docker' | 'source'>( 'docker' ); + const { kibanaVersion } = useValues(KibanaLogic); const { generatedData, isGenerateLoading } = useValues(DeploymentLogic); const { index, isLoading, connector, connectorId } = useValues(ConnectorViewLogic); const { fetchConnector } = useActions(ConnectorViewLogic); @@ -199,6 +201,7 @@ export const ConnectorDeployment: React.FC = () => { serviceType={connector.service_type ?? ''} isWaitingForConnector={isWaitingForConnector} apiKeyData={apiKey} + connectorVersion={kibanaVersion ?? ''} /> )} diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 717379d433dd1..fe3e7a84147ba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -119,6 +119,7 @@ export const renderApp = ( history, indexMappingComponent, isSidebarEnabled, + kibanaVersion, lens, ml, navigateToUrl, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 592e20f25f382..b404d0e8fb65d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -60,6 +60,7 @@ export interface KibanaLogicProps { history: ScopedHistory; indexMappingComponent?: React.FC; isSidebarEnabled: boolean; + kibanaVersion?: string; lens?: LensPublicStart; ml?: MlPluginStart; navigateToUrl: RequiredFieldsOnly; @@ -94,6 +95,7 @@ export interface KibanaValues { indexMappingComponent: React.FC | null; isCloud: boolean; isSidebarEnabled: boolean; + kibanaVersion: string | null; lens: LensPublicStart | null; ml: MlPluginStart | null; navigateToUrl(path: string, options?: CreateHrefOptions): Promise; @@ -133,6 +135,7 @@ export const KibanaLogic = kea>({ history: [props.history, {}], indexMappingComponent: [props.indexMappingComponent || null, {}], isSidebarEnabled: [props.isSidebarEnabled, {}], + kibanaVersion: [props.kibanaVersion || null, {}], lens: [props.lens || null, {}], ml: [props.ml || null, {}], navigateToUrl: [ diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 1eb3384d4f9e3..4d2c66eee2e93 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -199,7 +199,6 @@ export class EnterpriseSearchPlugin implements Plugin { this.esConfig = { elasticsearch_host: ELASTICSEARCH_URL_PLACEHOLDER }; } - if (!this.config.host) return; // No API to call if (this.hasInitialized) return; // We've already made an initial call try { diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts index 044b57c64da28..a4825c11f85cd 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts @@ -17,7 +17,11 @@ const useCases = [ filter: '', name: '', }, - 'sum(system.cpu.user.pct)', + { + operation: 'sum', + operationWithField: 'sum(system.cpu.user.pct)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -26,7 +30,11 @@ const useCases = [ filter: '', name: '', }, - 'max(system.cpu.user.pct)', + { + operation: 'max', + operationWithField: 'max(system.cpu.user.pct)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -35,7 +43,11 @@ const useCases = [ filter: '', name: '', }, - 'min(system.cpu.user.pct)', + { + operation: 'min', + operationWithField: 'min(system.cpu.user.pct)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -44,16 +56,24 @@ const useCases = [ filter: '', name: '', }, - 'average(system.cpu.user.pct)', + { + operation: 'average', + operationWithField: 'average(system.cpu.user.pct)', + sourceField: 'system.cpu.user.pct', + }, ], [ { aggType: Aggregators.COUNT, - field: 'system.cpu.user.pct', - filter: '', + field: '', + filter: 'system.cpu.user.pct: *', name: '', }, - 'count(___records___)', + { + operation: 'count', + operationWithField: `count(kql='system.cpu.user.pct: *')`, + sourceField: '', + }, ], [ { @@ -62,7 +82,11 @@ const useCases = [ filter: '', name: '', }, - 'unique_count(system.cpu.user.pct)', + { + operation: 'unique_count', + operationWithField: 'unique_count(system.cpu.user.pct)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -71,7 +95,11 @@ const useCases = [ filter: '', name: '', }, - 'percentile(system.cpu.user.pct, percentile=95)', + { + operation: 'percentile', + operationWithField: 'percentile(system.cpu.user.pct, percentile=95)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -80,7 +108,11 @@ const useCases = [ filter: '', name: '', }, - 'percentile(system.cpu.user.pct, percentile=99)', + { + operation: 'percentile', + operationWithField: 'percentile(system.cpu.user.pct, percentile=99)', + sourceField: 'system.cpu.user.pct', + }, ], [ { @@ -89,7 +121,11 @@ const useCases = [ filter: '', name: '', }, - `counter_rate(max(system.network.in.bytes), kql='')`, + { + operation: 'counter_rate', + operationWithField: `counter_rate(max(system.network.in.bytes), kql='')`, + sourceField: 'system.network.in.bytes', + }, ], [ { @@ -98,7 +134,11 @@ const useCases = [ filter: 'host.name : "foo"', name: '', }, - `counter_rate(max(system.network.in.bytes), kql='host.name : foo')`, + { + operation: 'counter_rate', + operationWithField: `counter_rate(max(system.network.in.bytes), kql='host.name : foo')`, + sourceField: 'system.network.in.bytes', + }, ], ]; diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts index a487f4899ad06..9eb52af738ea9 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts @@ -8,14 +8,24 @@ import { Aggregators } from '../../../common/custom_threshold_rule/types'; import { GenericMetric } from './rule_condition_chart'; -export const getLensOperationFromRuleMetric = (metric: GenericMetric): string => { +export interface LensOperation { + operation: string; + operationWithField: string; + sourceField: string; +} + +export const getLensOperationFromRuleMetric = (metric: GenericMetric): LensOperation => { const { aggType, field, filter } = metric; let operation: string = aggType; const operationArgs: string[] = []; const aggFilter = JSON.stringify(filter || '').replace(/"|\\/g, ''); if (aggType === Aggregators.RATE) { - return `counter_rate(max(${field}), kql='${aggFilter}')`; + return { + operation: 'counter_rate', + operationWithField: `counter_rate(max(${field}), kql='${aggFilter}')`, + sourceField: field || '', + }; } if (aggType === Aggregators.AVERAGE) operation = 'average'; @@ -23,14 +33,10 @@ export const getLensOperationFromRuleMetric = (metric: GenericMetric): string => if (aggType === Aggregators.P95 || aggType === Aggregators.P99) operation = 'percentile'; if (aggType === Aggregators.COUNT) operation = 'count'; - let sourceField = field; - - if (aggType === Aggregators.COUNT) { - sourceField = '___records___'; + if (field) { + operationArgs.push(field); } - operationArgs.push(sourceField || ''); - if (aggType === Aggregators.P95) { operationArgs.push('percentile=95'); } @@ -41,7 +47,11 @@ export const getLensOperationFromRuleMetric = (metric: GenericMetric): string => if (aggFilter) operationArgs.push(`kql='${aggFilter}'`); - return operation + '(' + operationArgs.join(', ') + ')'; + return { + operation, + operationWithField: `${operation}(${operationArgs.join(', ')})`, + sourceField: field || '', + }; }; export const getBufferThreshold = (threshold?: number): string => diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts index a957c58167403..647791cab5b8d 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts @@ -17,7 +17,11 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -28,7 +32,11 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - 'ABC-abc': 'average(system.cpu.system.pct)', + 'ABC-abc': { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, }, }); expect(parser.parse()).toEqual('100*average(system.cpu.system.pct)'); @@ -38,8 +46,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -52,9 +68,21 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', - C: 'average(system.cpu.cores)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, + C: { + operationWithField: 'average(system.cpu.cores)', + operation: 'average', + sourceField: 'system.cpu.cores', + }, }, }); // ✅ checked with Lens Formula editor @@ -67,7 +95,11 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -79,8 +111,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -93,8 +133,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -107,8 +155,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -121,8 +177,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -135,8 +199,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -149,8 +221,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -163,8 +243,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -178,8 +266,16 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - aa: 'average(system.cpu.system.pct)', - baa: 'average(system.cpu.user.pct)', + aa: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + baa: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, }, }); expect(parser.parse()).toEqual( @@ -193,12 +289,36 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', - C: 'average(system.cpu.total.pct)', - D: 'average(system.cpu.cores)', - E: 'count()', - F: 'sum(system.cpu.total.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, + C: { + operationWithField: 'average(system.cpu.total.pct)', + operation: 'average', + sourceField: 'system.cpu.total.pct', + }, + D: { + operationWithField: 'average(system.cpu.cores)', + operation: 'average', + sourceField: 'system.cpu.cores', + }, + E: { + operationWithField: 'count()', + operation: 'count', + sourceField: '', + }, + F: { + operationWithField: 'sum(system.cpu.total.pct)', + operation: 'sum', + sourceField: 'system.cpu.total.pct', + }, }, }); // ✅ checked with Lens Formula editor @@ -213,12 +333,36 @@ describe('PainlessTinyMathParser', () => { const parser = new PainlessTinyMathParser({ equation, aggMap: { - A: 'average(system.cpu.system.pct)', - B: 'average(system.cpu.user.pct)', - C: 'average(system.cpu.total.pct)', - D: 'average(system.cpu.cores)', - E: 'count()', - F: 'sum(system.cpu.total.pct)', + A: { + operationWithField: 'average(system.cpu.system.pct)', + operation: 'average', + sourceField: 'system.cpu.system.pct', + }, + B: { + operationWithField: 'average(system.cpu.user.pct)', + operation: 'average', + sourceField: 'system.cpu.user.pct', + }, + C: { + operationWithField: 'average(system.cpu.total.pct)', + operation: 'average', + sourceField: 'system.cpu.total.pct', + }, + D: { + operationWithField: 'average(system.cpu.cores)', + operation: 'average', + sourceField: 'system.cpu.cores', + }, + E: { + operationWithField: 'count()', + operation: 'count', + sourceField: '', + }, + F: { + operationWithField: 'sum(system.cpu.total.pct)', + operation: 'sum', + sourceField: 'system.cpu.total.pct', + }, }, }); // ✅ checked with Lens Formula editor diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts index 25ec15d3a808b..8c09ba029c579 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { LensOperation } from './helpers'; + // This is a parser of a subset operations/expression/statement of Painless A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, = to be used in Lens formula editor that uses TinyMath // The goal is to parse painless expressions to a format that can be used in Lens formula editor // The parser will also replace the characters A-Z with the values from aggMap @@ -13,7 +15,7 @@ // This parser is using a simple recursive function to parse the expression and replace the characters with the values from aggMap export interface AggMap { - [key: string]: string; + [key: string]: LensOperation; } interface PainlessTinyMathParserProps { equation: string; @@ -81,7 +83,10 @@ export class PainlessTinyMathParser { .sort() .reverse() .forEach((metricName) => { - parsedInputString = parsedInputString.replaceAll(metricName, aggMap[metricName]); + parsedInputString = parsedInputString.replaceAll( + metricName, + aggMap[metricName].operationWithField + ); }); return parsedInputString; diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx index 4567d7c37b10b..2a9fa2c295274 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect } from 'react'; import { EuiEmptyPrompt, useEuiTheme } from '@elastic/eui'; import { Query, Filter } from '@kbn/es-query'; -import { FillStyle, SeriesType } from '@kbn/lens-plugin/public'; +import { FillStyle, SeriesType, TermsIndexPatternColumn } from '@kbn/lens-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import useAsync from 'react-use/lib/useAsync'; @@ -82,6 +82,10 @@ export interface RuleConditionChartProps { additionalFilters?: Filter[]; } +export type TopValuesOrderParams = + | Pick + | undefined; + const defaultQuery: Query = { language: 'kuery', query: '', @@ -299,10 +303,10 @@ export function RuleConditionChart({ return; } const aggMapFromMetrics = metrics.reduce((acc, metric) => { - const operationField = getLensOperationFromRuleMetric(metric); + const { operation, operationWithField, sourceField } = getLensOperationFromRuleMetric(metric); return { ...acc, - [metric.name]: operationField, + [metric.name]: { operation, operationWithField, sourceField }, }; }, {} as AggMap); @@ -354,6 +358,26 @@ export function RuleConditionChart({ seriesType: seriesType ? seriesType : 'bar', }; + const firstMetricAggMap = aggMap && metrics.length > 0 ? aggMap[metrics[0].name] : undefined; + const convertToMaxOperation = ['counter_rate', 'last_value', 'percentile']; + + const orderParams: TopValuesOrderParams = firstMetricAggMap + ? { + orderDirection: 'desc', + orderBy: { type: 'custom' }, + orderAgg: { + label: firstMetricAggMap.operationWithField, + dataType: 'number', + operationType: convertToMaxOperation.includes(firstMetricAggMap.operation) + ? 'max' + : firstMetricAggMap.operation, + sourceField: firstMetricAggMap.sourceField, + isBucketed: false, + scale: 'ratio', + }, + } + : undefined; + if (groupBy && groupBy?.length) { xYDataLayerOptions.breakdown = { type: 'top_values', @@ -362,6 +386,7 @@ export function RuleConditionChart({ size: 3, secondaryFields: (groupBy as string[]).slice(1), accuracyMode: false, + ...orderParams, }, }; } @@ -425,6 +450,7 @@ export function RuleConditionChart({ timeUnit, seriesType, warningThresholdReferenceLine, + aggMap, ]); if ( diff --git a/x-pack/test/api_integration/deployment_agnostic/README.md b/x-pack/test/api_integration/deployment_agnostic/README.md index b029d0d167002..3bc1c70dda1ab 100644 --- a/x-pack/test/api_integration/deployment_agnostic/README.md +++ b/x-pack/test/api_integration/deployment_agnostic/README.md @@ -108,7 +108,7 @@ Kibana provides both public and internal APIs, each requiring authentication wit Recommendations: - use `roleScopedSupertest` service to create supertest instance scoped to specific role and pre-defined request headers - `roleScopedSupertest.getSupertestWithRoleScope()` authenticate requests with API key by default -- pass `withCookieHeader: true` to use Cookie header for requests authentication +- pass `useCookieHeader: true` to use Cookie header for requests authentication - don't forget to invalidate API key using `destroy()` on supertest scoped instance in `after` hook Add test files to `x-pack/test//deployment_agnostic/apis/`: @@ -117,25 +117,36 @@ test example ```ts export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const roleScopedSupertest = getService('roleScopedSupertest'); - let supertestWithAdminScope: SupertestWithRoleScopeType; + let supertestViewerWithApiKey: SupertestWithRoleScopeType; + let supertestEditorWithCookieCredentials: SupertestWithRoleScopeType; - describe('compression', () => { + describe('test suite', () => { before(async () => { - supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { + supertestViewerWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('viewer', { withInternalHeaders: true, withCustomHeaders: { 'accept-encoding': 'gzip' }, }); + supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope('editor', { + withInternalHeaders: true, + useCookieHeader: true, + }); }); after(async () => { // always invalidate API key for the scoped role in the end - await supertestWithAdminScope.destroy(); + await supertestViewerWithApiKey.destroy(); + // supertestEditorWithCookieCredentials.destroy() has no effect because Cookie session is cached per SAML role + // and valid for the whole FTR config run, no need to call it }); - describe('against an application page', () => { - it(`uses compression when there isn't a referer`, async () => { - const response = await supertestWithAdminScope.get('/app/kibana'); - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestViewerWithApiKey.get('/app/kibana'); + expect(response.header).to.have.property('content-encoding', 'gzip'); }); + + it(`can run rule with Editor privileges`, async () => { + const response = await supertestEditorWithCookieCredentials + .post(`/internal/alerting/rule/${ruleId}/_run_soon`) + .expect(204); + }); }); } ``` diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts new file mode 100644 index 0000000000000..8d96b00867e80 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts @@ -0,0 +1,65 @@ +/* + * 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 expect from '@kbn/expect'; +import archives_metadata from '../../../../../../apm_api_integration/common/fixtures/es_archiver/archives_metadata'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ARCHIVER_ROUTES } from '../constants/archiver'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const esArchiver = getService('esArchiver'); + + const archiveName = '8.0.0'; + const { start, end } = archives_metadata[archiveName]; + + describe('Agent name', () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/agent', + params: { + path: { serviceName: 'opbeans-node' }, + query: { + start, + end, + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({}); + }); + }); + + describe('when data is loaded', () => { + before(async () => { + await esArchiver.load(ARCHIVER_ROUTES[archiveName]); + }); + after(async () => { + await esArchiver.unload(ARCHIVER_ROUTES[archiveName]); + }); + + it('returns the agent name', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/agent', + params: { + path: { serviceName: 'opbeans-node' }, + query: { + start, + end, + }, + }, + }); + + expect(response.status).to.be(200); + + expect(response.body).to.eql({ agentName: 'nodejs', runtimeName: 'node' }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/index.ts index 2beba223d9dc2..4993ec83c5eca 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/index.ts @@ -13,6 +13,7 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) loadTestFile(require.resolve('./error_groups/error_groups_main_statistics.spec.ts')); loadTestFile(require.resolve('./service_details/service_details.spec.ts')); loadTestFile(require.resolve('./service_icons/service_icons.spec.ts')); + loadTestFile(require.resolve('./agent.spec.ts')); loadTestFile(require.resolve('./archive_services_detailed_statistics.spec.ts')); loadTestFile(require.resolve('./derived_annotations.spec.ts')); loadTestFile(require.resolve('./get_service_node_metadata.spec.ts')); diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts index ee5d1b20f2e15..9988657f13897 100644 --- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts @@ -6,6 +6,10 @@ */ export default { + '8.0.0': { + start: '2020-08-26T11:00:43.849Z', + end: '2020-08-26T12:00:43.849Z', + }, 'apm_8.0.0': { start: '2021-08-03T06:50:15.910Z', end: '2021-08-03T07:20:15.910Z', diff --git a/x-pack/test_serverless/README.md b/x-pack/test_serverless/README.md index 44f871273aca2..17e5a9056f5d8 100644 --- a/x-pack/test_serverless/README.md +++ b/x-pack/test_serverless/README.md @@ -154,7 +154,7 @@ Kibana provides both public and internal APIs, each requiring authentication wit Recommendations: - use `roleScopedSupertest` service to create a supertest instance scoped to a specific role and predefined request headers - `roleScopedSupertest.getSupertestWithRoleScope()` authenticates requests with an API key by default -- pass `withCookieHeader: true` to use Cookie header for request authentication +- pass `useCookieHeader: true` to use Cookie header for request authentication - don't forget to invalidate API keys by using `destroy()` on the supertest scoped instance in the `after` hook ``` @@ -183,7 +183,7 @@ describe("my internal APIs test suite", async function() { before(async () => { supertestViewerWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withCookieHeader: true, // to avoid generating API key and use Cookie header instead + useCookieHeader: true, // to avoid generating API key and use Cookie header instead withInternalHeaders: true, }); });