= ({ basePath }) => (
),
diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
index a0a81f77b7b08..f645a0753f8c2 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -127,7 +127,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
to: globalState.time.to,
});
}
- }, [globalState?.time?.from, globalState?.time?.to]);
+ }, [lastRefresh, globalState?.time?.from, globalState?.time?.to]);
const getJobsWithStoppedPartitions = useCallback(async (selectedJobIds: string[]) => {
try {
diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
index ef669a7703c1f..6eb4386276753 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
@@ -142,10 +142,10 @@ export const TimeSeriesExplorerUrlStateManager: FC selectedEarliestMs || chartRange.max < selectedLatestBucketStart) &&
- chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestMs
+ (chartRange.min > selectedEarliestBucketCeil || chartRange.max < selectedLatestBucketStart) &&
+ chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestBucketCeil
) {
tooManyBuckets = true;
}
diff --git a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
index e11eb4048c374..6f2b5417eff5f 100644
--- a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
+++ b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
@@ -25,6 +25,8 @@ import {
} from '../explorer/explorer_utils';
import { OVERALL_LABEL, VIEW_BY_JOB_LABEL } from '../explorer/explorer_constants';
import { MlResultsService } from './results_service';
+import { EntityField } from '../../../common/util/anomaly_utils';
+import { InfluencersFilterQuery } from '../../../common/types/es_client';
/**
* Service for retrieving anomaly swim lanes data.
@@ -241,7 +243,9 @@ export class AnomalyTimelineService {
swimlaneLimit: number,
perPage: number,
fromPage: number,
- swimlaneContainerWidth: number
+ swimlaneContainerWidth: number,
+ selectionInfluencers: EntityField[],
+ influencersFilterQuery: InfluencersFilterQuery
) {
const selectedJobIds = selectedJobs.map((d) => d.id);
@@ -254,7 +258,9 @@ export class AnomalyTimelineService {
latestMs,
swimlaneLimit,
perPage,
- fromPage
+ fromPage,
+ selectionInfluencers,
+ influencersFilterQuery
);
if (resp.influencers[viewBySwimlaneFieldName] === undefined) {
return [];
@@ -276,6 +282,8 @@ export class AnomalyTimelineService {
earliestMs,
latestMs,
this.getSwimlaneBucketInterval(selectedJobs, swimlaneContainerWidth).asMilliseconds(),
+ perPage,
+ fromPage,
swimlaneLimit
);
return Object.keys(resp.results);
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
index 25ef36782207f..a9f6dbb45f6e3 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
@@ -16,6 +16,11 @@ import { JobId } from '../../../../common/types/anomaly_detection_jobs';
import { JOB_ID, PARTITION_FIELD_VALUE } from '../../../../common/constants/anomalies';
import { PartitionFieldsDefinition } from '../results_service/result_service_rx';
import { PartitionFieldsConfig } from '../../../../common/types/storage';
+import {
+ ESSearchRequest,
+ ESSearchResponse,
+} from '../../../../../../../src/core/types/elasticsearch';
+import { MLAnomalyDoc } from '../../../../common/types/anomalies';
export const resultsApiProvider = (httpService: HttpService) => ({
getAnomaliesTableData(
@@ -112,18 +117,18 @@ export const resultsApiProvider = (httpService: HttpService) => ({
});
},
- anomalySearch(query: any, jobIds: string[]) {
+ anomalySearch(query: ESSearchRequest, jobIds: string[]) {
const body = JSON.stringify({ query, jobIds });
- return httpService.http({
+ return httpService.http>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
});
},
- anomalySearch$(query: any, jobIds: string[]) {
+ anomalySearch$(query: ESSearchRequest, jobIds: string[]) {
const body = JSON.stringify({ query, jobIds });
- return httpService.http$({
+ return httpService.http$>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
index ea07d32bfff1d..1848b13cb5a1f 100644
--- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
+++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
@@ -23,7 +23,8 @@ export function resultsServiceProvider(
intervalMs: number,
perPage?: number,
fromPage?: number,
- swimLaneSeverity?: number
+ swimLaneSeverity?: number,
+ influencersFilterQuery?: InfluencersFilterQuery
): Promise;
getTopInfluencers(
selectedJobIds: string[],
@@ -32,7 +33,7 @@ export function resultsServiceProvider(
maxFieldValues: number,
perPage?: number,
fromPage?: number,
- influencers?: any[],
+ influencers?: EntityField[],
influencersFilterQuery?: InfluencersFilterQuery
): Promise;
getTopInfluencerValues(): Promise;
diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
index 6cb5f67149fb6..56221f9a72c89 100644
--- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
+++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
@@ -85,7 +85,6 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml
),
events: calendarEvents.map((s) => ({
calendar_id: calendarId,
- event_id: '',
description: s.description,
start_time: `${s.start}`,
end_time: `${s.end}`,
diff --git a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx
index 914446c42aaa7..fadf4c5872507 100644
--- a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useEffect, useState, useContext } from 'react';
+import React, { useEffect, useState } from 'react';
import {
EuiButton,
@@ -22,14 +22,16 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { AlertsContext } from './context';
import { Legacy } from '../legacy_shims';
-export const EnableAlertsModal: React.FC<{}> = () => {
+interface Props {
+ alerts: {};
+}
+
+export const EnableAlertsModal: React.FC = ({ alerts }: Props) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const $injector = Legacy.shims.getAngularInjector();
const alertsEnableModalProvider: any = $injector.get('enableAlertsModal');
- const alertsContext = useContext(AlertsContext);
const closeModal = () => {
setIsModalVisible(false);
@@ -58,10 +60,10 @@ export const EnableAlertsModal: React.FC<{}> = () => {
};
useEffect(() => {
- if (alertsEnableModalProvider.shouldShowAlertsModal(alertsContext)) {
+ if (alertsEnableModalProvider.shouldShowAlertsModal(alerts)) {
setIsModalVisible(true);
}
- }, [alertsEnableModalProvider, alertsContext]);
+ }, [alertsEnableModalProvider, alerts]);
const confirmButtonClick = () => {
if (radioIdSelected === 'create-alerts') {
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index a5b7d4906b586..9f84165a27ba9 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -93,7 +93,10 @@ export class MonitoringPlugin
category: DEFAULT_APP_CATEGORIES.management,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
- const { AngularApp } = await import('./angular');
+ const [, { AngularApp }] = await Promise.all([
+ pluginsStart.kibanaLegacy.loadAngularBootstrap(),
+ import('./angular'),
+ ]);
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
kibanaLegacy: pluginsStart.kibanaLegacy,
diff --git a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
index 0232e302517af..438c5ab83f5e3 100644
--- a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
+++ b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
@@ -12,12 +12,10 @@ export function enableAlertsModalProvider($http, $window, $injector) {
const modalHasBeenShown = $window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN');
const decisionMade = $window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE');
- if (Object.keys(alerts.allAlerts).length > 0) {
+ if (Object.keys(alerts).length > 0) {
$window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
return false;
- }
-
- if (!modalHasBeenShown && !decisionMade) {
+ } else if (!modalHasBeenShown && !decisionMade) {
return true;
}
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
index 9f9eec3848604..8b365292aeb13 100644
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
+++ b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
@@ -13,6 +13,7 @@ import { MonitoringViewBaseEuiTableController } from '../../';
import template from './index.html';
import { Listing } from '../../../components/cluster/listing';
import { CODE_PATH_ALL } from '../../../../common/constants';
+import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx';
const CODE_PATHS = [CODE_PATH_ALL];
@@ -21,6 +22,10 @@ const getPageData = ($injector) => {
return monitoringClusters(undefined, undefined, CODE_PATHS);
};
+const getAlerts = (clusters) => {
+ return clusters.reduce((alerts, cluster) => ({ ...alerts, ...cluster.alerts.list }), {});
+};
+
uiRoutes
.when('/home', {
template,
@@ -71,18 +76,21 @@ uiRoutes
() => this.data,
(data) => {
this.renderReact(
-
+ <>
+
+
+ >
);
}
);
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
index bf34650bdb700..20e694ad8548f 100644
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
+++ b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
@@ -83,7 +83,7 @@ uiRoutes.when('/overview', {
setupMode={setupMode}
showLicenseExpiration={showLicenseExpiration}
/>
-
+
{bottomBarComponent}
)}
diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts
index c4a0687bef497..b920f2bfacf80 100644
--- a/x-pack/plugins/monitoring/server/types.ts
+++ b/x-pack/plugins/monitoring/server/types.ts
@@ -20,6 +20,7 @@ import type {
ActionsApiRequestHandlerContext,
} from '../../actions/server';
import type { AlertingApiRequestHandlerContext } from '../../alerting/server';
+import type { RacApiRequestHandlerContext } from '../../rule_registry/server';
import {
PluginStartContract as AlertingPluginStartContract,
PluginSetupContract as AlertingPluginSetupContract,
@@ -57,6 +58,7 @@ export interface RequestHandlerContextMonitoringPlugin extends RequestHandlerCon
actions?: ActionsApiRequestHandlerContext;
alerting?: AlertingApiRequestHandlerContext;
infra: InfraRequestHandlerContext;
+ ruleRegistry?: RacApiRequestHandlerContext;
}
export interface PluginsStart {
diff --git a/x-pack/plugins/observability/.storybook/jest_setup.js b/x-pack/plugins/observability/.storybook/jest_setup.js
new file mode 100644
index 0000000000000..32071b8aa3f62
--- /dev/null
+++ b/x-pack/plugins/observability/.storybook/jest_setup.js
@@ -0,0 +1,11 @@
+/*
+ * 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 { setGlobalConfig } from '@storybook/testing-react';
+import * as globalStorybookConfig from './preview';
+
+setGlobalConfig(globalStorybookConfig);
diff --git a/x-pack/plugins/observability/.storybook/preview.js b/x-pack/plugins/observability/.storybook/preview.js
new file mode 100644
index 0000000000000..18343c15a6465
--- /dev/null
+++ b/x-pack/plugins/observability/.storybook/preview.js
@@ -0,0 +1,10 @@
+/*
+ * 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 { EuiThemeProviderDecorator } from '../../../../src/plugins/kibana_react/common';
+
+export const decorators = [EuiThemeProviderDecorator];
diff --git a/x-pack/plugins/observability/jest.config.js b/x-pack/plugins/observability/jest.config.js
index 66d42122382f3..6fdeab06df053 100644
--- a/x-pack/plugins/observability/jest.config.js
+++ b/x-pack/plugins/observability/jest.config.js
@@ -9,4 +9,5 @@ module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['/x-pack/plugins/observability'],
+ setupFiles: ['/x-pack/plugins/observability/.storybook/jest_setup.js'],
};
diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx
index 5f5cf2cb4da21..5c07b4626cf19 100644
--- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx
+++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx
@@ -9,7 +9,6 @@ import React, { ComponentType } from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n/react';
import { Observable } from 'rxjs';
import { CoreStart } from 'src/core/public';
-import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
import { CoreVitalItem } from '../core_vital_item';
import { LCP_HELP_LABEL, LCP_LABEL } from '../translations';
@@ -25,9 +24,7 @@ export default {
(Story: ComponentType) => (
-
-
-
+
),
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
index dd48cf3f7eeb8..ba1f2214223e3 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
@@ -44,6 +44,7 @@ import {
TAGS_LABEL,
TBT_LABEL,
URL_LABEL,
+ BACKEND_TIME_LABEL,
} from './labels';
export const DEFAULT_TIME = { from: 'now-1h', to: 'now' };
@@ -66,7 +67,7 @@ export const FieldLabels: Record = {
[TBT_FIELD]: TBT_LABEL,
[FID_FIELD]: FID_LABEL,
[CLS_FIELD]: CLS_LABEL,
- [TRANSACTION_TIME_TO_FIRST_BYTE]: 'Page load time',
+ [TRANSACTION_TIME_TO_FIRST_BYTE]: BACKEND_TIME_LABEL,
'monitor.id': MONITOR_ID_LABEL,
'monitor.status': MONITOR_STATUS_LABEL,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
index 0be64677586c1..ae70bbdcfa3b8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
@@ -411,6 +411,7 @@ describe('Lens Attribute', () => {
sourceField: USER_AGENT_NAME,
layerId: 'layer0',
indexPattern: mockIndexPattern,
+ labels: layerConfig.seriesConfig.labels,
});
expect(lnsAttr.visualization.layers).toEqual([
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
index 5734cd1592692..dfb17ee470d35 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
@@ -30,7 +30,6 @@ import {
import { urlFiltersToKueryString } from '../utils/stringify_kueries';
import { ExistsFilter, IndexPattern } from '../../../../../../../../src/plugins/data/common';
import {
- FieldLabels,
FILTER_RECORDS,
USE_BREAK_DOWN_COLUMN,
TERMS_COLUMN,
@@ -125,17 +124,19 @@ export class LensAttributes {
getBreakdownColumn({
sourceField,
layerId,
+ labels,
indexPattern,
}: {
sourceField: string;
layerId: string;
+ labels: Record;
indexPattern: IndexPattern;
}): TermsIndexPatternColumn {
const fieldMeta = indexPattern.getFieldByName(sourceField);
return {
sourceField,
- label: `Top values of ${FieldLabels[sourceField]}`,
+ label: `Top values of ${labels[sourceField]}`,
dataType: fieldMeta?.type as DataType,
operationType: 'terms',
scale: 'ordinal',
@@ -304,6 +305,7 @@ export class LensAttributes {
layerId,
indexPattern: layerConfig.indexPattern,
sourceField: layerConfig.breakdown || layerConfig.seriesConfig.breakdownFields[0],
+ labels: layerConfig.seriesConfig.labels,
});
}
@@ -344,7 +346,7 @@ export class LensAttributes {
if (fieldName === RECORDS_FIELD || columnType === FILTER_RECORDS) {
return this.getRecordsColumn(
- columnLabel || label,
+ label || columnLabel,
colIndex !== undefined ? columnFilters?.[colIndex] : undefined,
timeScale
);
@@ -433,6 +435,8 @@ export class LensAttributes {
if (yAxisColumns.length === 1) {
return lensColumns;
}
+
+ // starting from 1 index since 0 column is used as main column
for (let i = 1; i < yAxisColumns.length; i++) {
const { sourceField, operationType, label } = yAxisColumns[i];
@@ -555,16 +559,21 @@ export class LensAttributes {
const layerConfigs = this.layerConfigs;
layerConfigs.forEach((layerConfig, index) => {
- const { breakdown } = layerConfig;
+ const { breakdown, seriesConfig } = layerConfig;
const layerId = `layer${index}`;
const columnFilter = this.getLayerFilters(layerConfig, layerConfigs.length);
const timeShift = this.getTimeShift(this.layerConfigs[0], layerConfig, index);
const mainYAxis = this.getMainYAxis(layerConfig, layerId, columnFilter);
+
+ const { sourceField } = seriesConfig.xAxisColumn;
+
layers[layerId] = {
columnOrder: [
`x-axis-column-${layerId}`,
- ...(breakdown ? [`breakdown-column-${layerId}`] : []),
+ ...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN
+ ? [`breakdown-column-${layerId}`]
+ : []),
`y-axis-column-${layerId}`,
...Object.keys(this.getChildYAxises(layerConfig, layerId, columnFilter)),
],
@@ -576,13 +585,14 @@ export class LensAttributes {
filter: { query: columnFilter, language: 'kuery' },
...(timeShift ? { timeShift } : {}),
},
- ...(breakdown && breakdown !== USE_BREAK_DOWN_COLUMN
+ ...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN
? // do nothing since this will be used a x axis source
{
[`breakdown-column-${layerId}`]: this.getBreakdownColumn({
layerId,
sourceField: breakdown,
indexPattern: layerConfig.indexPattern,
+ labels: layerConfig.seriesConfig.labels,
}),
}
: {}),
@@ -617,7 +627,10 @@ export class LensAttributes {
{ forAccessor: `y-axis-column-layer${index}` },
],
xAccessor: `x-axis-column-layer${index}`,
- ...(layerConfig.breakdown ? { splitAccessor: `breakdown-column-layer${index}` } : {}),
+ ...(layerConfig.breakdown &&
+ layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN
+ ? { splitAccessor: `breakdown-column-layer${index}` }
+ : {}),
})),
...(this.layerConfigs[0].seriesConfig.yTitle
? { yTitle: this.layerConfigs[0].seriesConfig.yTitle }
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
index 98979b9922a86..d1612a08f5551 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import { FieldLabels, USE_BREAK_DOWN_COLUMN } from '../constants';
+import { FieldLabels, REPORT_METRIC_FIELD, USE_BREAK_DOWN_COLUMN } from '../constants';
import { buildPhraseFilter } from '../utils';
import { SERVICE_NAME } from '../constants/elasticsearch_fieldnames';
import { MOBILE_APP, NUMBER_OF_DEVICES } from '../constants/labels';
@@ -22,9 +22,8 @@ export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps)
},
yAxisColumns: [
{
- sourceField: 'labels.device_id',
+ sourceField: REPORT_METRIC_FIELD,
operationType: 'unique_count',
- label: NUMBER_OF_DEVICES,
},
],
hasOperationType: false,
@@ -39,6 +38,13 @@ export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps)
...MobileFields,
[SERVICE_NAME]: MOBILE_APP,
},
+ metricOptions: [
+ {
+ id: 'labels.device_id',
+ field: 'labels.device_id',
+ label: NUMBER_OF_DEVICES,
+ },
+ ],
definitionFields: [SERVICE_NAME],
};
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
index b9894347d96c0..9b1c4c8da3e9b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants';
+import { FieldLabels, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants';
import { buildPhrasesFilter } from '../utils';
import {
METRIC_SYSTEM_CPU_USAGE,
@@ -49,19 +49,16 @@ export function getMobileKPIDistributionConfig({ indexPattern }: ConfigProps): S
label: RESPONSE_LATENCY,
field: TRANSACTION_DURATION,
id: TRANSACTION_DURATION,
- columnType: OPERATION_COLUMN,
},
{
label: MEMORY_USAGE,
field: METRIC_SYSTEM_MEMORY_USAGE,
id: METRIC_SYSTEM_MEMORY_USAGE,
- columnType: OPERATION_COLUMN,
},
{
label: CPU_USAGE,
field: METRIC_SYSTEM_CPU_USAGE,
id: METRIC_SYSTEM_CPU_USAGE,
- columnType: OPERATION_COLUMN,
},
],
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts
new file mode 100644
index 0000000000000..07bb13f957e45
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { mockAppIndexPattern, mockIndexPattern } from '../../rtl_helpers';
+import { getDefaultConfigs } from '../default_configs';
+import { LayerConfig, LensAttributes } from '../lens_attributes';
+import { sampleAttributeCoreWebVital } from '../test_data/sample_attribute_cwv';
+import { SERVICE_NAME, USER_AGENT_OS } from '../constants/elasticsearch_fieldnames';
+
+describe('Core web vital config test', function () {
+ mockAppIndexPattern();
+
+ const seriesConfig = getDefaultConfigs({
+ reportType: 'core-web-vitals',
+ dataType: 'ux',
+ indexPattern: mockIndexPattern,
+ });
+
+ let lnsAttr: LensAttributes;
+
+ const layerConfig: LayerConfig = {
+ seriesConfig,
+ indexPattern: mockIndexPattern,
+ reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] },
+ time: { from: 'now-15m', to: 'now' },
+ breakdown: USER_AGENT_OS,
+ };
+
+ beforeEach(() => {
+ lnsAttr = new LensAttributes([layerConfig]);
+ });
+ it('should return expected json', function () {
+ expect(lnsAttr.getJSON()).toEqual(sampleAttributeCoreWebVital);
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
index 1d04a9b389503..62455df248085 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
@@ -31,6 +31,7 @@ import {
URL_FULL,
SERVICE_ENVIRONMENT,
} from '../constants/elasticsearch_fieldnames';
+import { CLS_LABEL, FID_LABEL, LCP_LABEL } from '../constants/labels';
export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesConfig {
const statusPallete = euiPaletteForStatus(3);
@@ -91,7 +92,7 @@ export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesCon
metricOptions: [
{
id: LCP_FIELD,
- label: 'Largest contentful paint',
+ label: LCP_LABEL,
columnType: FILTER_RECORDS,
columnFilters: [
{
@@ -109,7 +110,7 @@ export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesCon
],
},
{
- label: 'First input delay',
+ label: FID_LABEL,
id: FID_FIELD,
columnType: FILTER_RECORDS,
columnFilters: [
@@ -128,7 +129,7 @@ export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesCon
],
},
{
- label: 'Cumulative layout shift',
+ label: CLS_LABEL,
id: CLS_FIELD,
columnType: FILTER_RECORDS,
columnFilters: [
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
new file mode 100644
index 0000000000000..2087b85b81886
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+export const sampleAttributeCoreWebVital = {
+ description: '',
+ references: [
+ {
+ id: 'apm-*',
+ name: 'indexpattern-datasource-current-indexpattern',
+ type: 'index-pattern',
+ },
+ {
+ id: 'apm-*',
+ name: 'indexpattern-datasource-layer-layer0',
+ type: 'index-pattern',
+ },
+ ],
+ state: {
+ datasourceStates: {
+ indexpattern: {
+ layers: {
+ layer0: {
+ columnOrder: [
+ 'x-axis-column-layer0',
+ 'y-axis-column-layer0',
+ 'y-axis-column-1',
+ 'y-axis-column-2',
+ ],
+ columns: {
+ 'x-axis-column-layer0': {
+ dataType: 'string',
+ isBucketed: true,
+ label: 'Top values of Operating system',
+ operationType: 'terms',
+ params: {
+ missingBucket: false,
+ orderBy: {
+ columnId: 'y-axis-column-layer0',
+ type: 'column',
+ },
+ orderDirection: 'desc',
+ otherBucket: true,
+ size: 10,
+ },
+ scale: 'ordinal',
+ sourceField: 'user_agent.os.name',
+ },
+ 'y-axis-column-1': {
+ dataType: 'number',
+ filter: {
+ language: 'kuery',
+ query:
+ 'transaction.marks.agent.largestContentfulPaint > 2500 and transaction.marks.agent.largestContentfulPaint < 4000',
+ },
+ isBucketed: false,
+ label: 'Average',
+ operationType: 'count',
+ scale: 'ratio',
+ sourceField: 'Records',
+ },
+ 'y-axis-column-2': {
+ dataType: 'number',
+ filter: {
+ language: 'kuery',
+ query: 'transaction.marks.agent.largestContentfulPaint > 4000',
+ },
+ isBucketed: false,
+ label: 'Poor',
+ operationType: 'count',
+ scale: 'ratio',
+ sourceField: 'Records',
+ },
+ 'y-axis-column-layer0': {
+ dataType: 'number',
+ filter: {
+ language: 'kuery',
+ query: 'transaction.type: page-load and processor.event: transaction',
+ },
+ isBucketed: false,
+ label: 'Good',
+ operationType: 'count',
+ scale: 'ratio',
+ sourceField: 'Records',
+ },
+ },
+ incompleteColumns: {},
+ },
+ },
+ },
+ },
+ filters: [],
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ visualization: {
+ axisTitlesVisibilitySettings: {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
+ curveType: 'CURVE_MONOTONE_X',
+ fittingFunction: 'Linear',
+ gridlinesVisibilitySettings: {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
+ layers: [
+ {
+ accessors: ['y-axis-column-layer0', 'y-axis-column-1', 'y-axis-column-2'],
+ layerId: 'layer0',
+ seriesType: 'bar_horizontal_percentage_stacked',
+ xAccessor: 'x-axis-column-layer0',
+ yConfig: [
+ {
+ color: '#209280',
+ forAccessor: 'y-axis-column',
+ },
+ {
+ color: '#d6bf57',
+ forAccessor: 'y-axis-column-1',
+ },
+ {
+ color: '#cc5642',
+ forAccessor: 'y-axis-column-2',
+ },
+ ],
+ },
+ ],
+ legend: {
+ isVisible: true,
+ position: 'right',
+ },
+ preferredSeriesType: 'line',
+ tickLabelsVisibilitySettings: {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
+ valueLabels: 'hide',
+ },
+ },
+ title: 'Prefilled from exploratory view app',
+ visualizationType: 'lnsXY',
+};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
index 07048d47b2bc3..12ae8560453c9 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
@@ -36,7 +36,6 @@ describe('ReportTypesCol', function () {
fireEvent.click(screen.getByText(/KPI over time/i));
expect(setSeries).toHaveBeenCalledWith(seriesId, {
- breakdown: 'user_agent.name',
dataType: 'ux',
selectedMetricField: undefined,
reportType: 'kpi-over-time',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
index 396f8c4f1deb3..c4eebbfaca3eb 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
@@ -78,6 +78,7 @@ export function ReportTypesCol({ seriesId, reportTypes }: Props) {
...restSeries,
reportType,
selectedMetricField: undefined,
+ breakdown: undefined,
time: restSeries?.time ?? DEFAULT_TIME,
});
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
index 634408dd614da..964de86ddf377 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
@@ -100,11 +100,14 @@ export class ObservabilityIndexPatterns {
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
let isParamsDifferent = false;
defaultFieldFormats.forEach(({ field, format }) => {
- const fieldFormat = indexPattern.getFormatterForField(indexPattern.getFieldByName(field)!);
- const params = fieldFormat.params();
- if (!isParamsSame(params, format.params)) {
- indexPattern.setFieldFormat(field, format);
- isParamsDifferent = true;
+ const fieldByName = indexPattern.getFieldByName(field);
+ if (fieldByName) {
+ const fieldFormat = indexPattern.getFormatterForField(fieldByName);
+ const params = fieldFormat.params();
+ if (!isParamsSame(params, format.params)) {
+ indexPattern.setFieldFormat(field, format);
+ isParamsDifferent = true;
+ }
}
});
if (isParamsDifferent) {
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx
index 80a25b82eb8cb..1152ba32960ed 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx
@@ -10,7 +10,6 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n/react';
import { Observable } from 'rxjs';
import { CoreStart } from 'src/core/public';
import { text } from '@storybook/addon-knobs';
-import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
import { FieldValueSelectionProps } from '../types';
import { FieldValueSelection } from '../field_value_selection';
@@ -31,16 +30,14 @@ export default {
(Story: ComponentType) => (
-
- {}}
- selectedValue={[]}
- loading={false}
- setQuery={() => {}}
- />
-
+ {}}
+ selectedValue={[]}
+ loading={false}
+ setQuery={() => {}}
+ />
),
diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts
index 0561eab08fb45..6bafe465fd024 100644
--- a/x-pack/plugins/observability/public/index.ts
+++ b/x-pack/plugins/observability/public/index.ts
@@ -68,5 +68,9 @@ export { createExploratoryViewUrl } from './components/shared/exploratory_view/c
export { FilterValueLabel } from './components/shared/filter_value_label/filter_value_label';
export type { SeriesUrl } from './components/shared/exploratory_view/types';
-export type { ObservabilityRuleTypeRegistry } from './rules/create_observability_rule_type_registry';
+export type {
+ ObservabilityRuleTypeFormatter,
+ ObservabilityRuleTypeModel,
+ ObservabilityRuleTypeRegistry,
+} from './rules/create_observability_rule_type_registry';
export { createObservabilityRuleTypeRegistryMock } from './rules/observability_rule_type_registry_mock';
diff --git a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx
index 86922b045c742..ef3ded61492c7 100644
--- a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx
+++ b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx
@@ -6,7 +6,6 @@
*/
import React, { ComponentType } from 'react';
-import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
import { PluginContext, PluginContextValue } from '../../context/plugin_context';
import { LandingPage } from './';
@@ -27,9 +26,7 @@ export default {
} as unknown) as PluginContextValue;
return (
-
-
-
+
);
},
diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
index dd424cf221d15..2982333235331 100644
--- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
@@ -10,7 +10,6 @@ import { storiesOf } from '@storybook/react';
import { AppMountParameters, CoreStart } from 'kibana/public';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
-import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
import { HasDataContextProvider } from '../../context/has_data_context';
import { PluginContext } from '../../context/plugin_context';
@@ -65,9 +64,7 @@ const withCore = makeDecorator({
ObservabilityPageTemplate: KibanaPageTemplate,
}}
>
-
- {storyFn(context)}
-
+ {storyFn(context)}
);
diff --git a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
index 35f2dc18c2f22..d6f8c08359888 100644
--- a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
+++ b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
@@ -5,19 +5,29 @@
* 2.0.
*/
-import { AlertTypeModel, AlertTypeRegistryContract } from '../../../triggers_actions_ui/public';
+import {
+ AlertTypeModel,
+ AlertTypeParams,
+ AlertTypeRegistryContract,
+} from '../../../triggers_actions_ui/public';
import { ParsedTechnicalFields } from '../../../rule_registry/common/parse_technical_fields';
import { AsDuration, AsPercent } from '../../common/utils/formatters';
-export type Formatter = (options: {
+export type ObservabilityRuleTypeFormatter = (options: {
fields: ParsedTechnicalFields & Record;
formatters: { asDuration: AsDuration; asPercent: AsPercent };
}) => { reason: string; link: string };
+export interface ObservabilityRuleTypeModel
+ extends AlertTypeModel {
+ format: ObservabilityRuleTypeFormatter;
+}
+
export function createObservabilityRuleTypeRegistry(alertTypeRegistry: AlertTypeRegistryContract) {
- const formatters: Array<{ typeId: string; fn: Formatter }> = [];
+ const formatters: Array<{ typeId: string; fn: ObservabilityRuleTypeFormatter }> = [];
+
return {
- register: (type: AlertTypeModel & { format: Formatter }) => {
+ register: (type: ObservabilityRuleTypeModel) => {
const { format, ...rest } = type;
formatters.push({ typeId: type.id, fn: format });
alertTypeRegistry.register(rest);
diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts
index d820a6c0a6f76..868e234fcb2a1 100644
--- a/x-pack/plugins/observability/server/plugin.ts
+++ b/x-pack/plugins/observability/server/plugin.ts
@@ -38,47 +38,49 @@ export class ObservabilityPlugin implements Plugin {
}
public setup(core: CoreSetup, plugins: PluginSetup) {
- plugins.features.registerKibanaFeature({
- id: casesFeatureId,
- name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', {
- defaultMessage: 'Cases',
- }),
- order: 1100,
- category: DEFAULT_APP_CATEGORIES.observability,
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: [observabilityFeatureId],
- privileges: {
- all: {
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: {
- all: [observabilityFeatureId],
- },
- api: [],
- savedObject: {
- all: [],
- read: [],
- },
- ui: ['crud_cases', 'read_cases'], // uiCapabilities[casesFeatureId].crud_cases or read_cases
- },
- read: {
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: {
- read: [observabilityFeatureId],
+ const config = this.initContext.config.get();
+
+ if (config.unsafe.cases.enabled) {
+ plugins.features.registerKibanaFeature({
+ id: casesFeatureId,
+ name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', {
+ defaultMessage: 'Cases',
+ }),
+ order: 1100,
+ category: DEFAULT_APP_CATEGORIES.observability,
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: [observabilityFeatureId],
+ privileges: {
+ all: {
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: {
+ all: [observabilityFeatureId],
+ },
+ api: [],
+ savedObject: {
+ all: [],
+ read: [],
+ },
+ ui: ['crud_cases', 'read_cases'], // uiCapabilities[casesFeatureId].crud_cases or read_cases
},
- api: [],
- savedObject: {
- all: [],
- read: [],
+ read: {
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: {
+ read: [observabilityFeatureId],
+ },
+ api: [],
+ savedObject: {
+ all: [],
+ read: [],
+ },
+ ui: ['read_cases'], // uiCapabilities[uiCapabilities[casesFeatureId]].read_cases
},
- ui: ['read_cases'], // uiCapabilities[uiCapabilities[casesFeatureId]].read_cases
},
- },
- });
-
- const config = this.initContext.config.get();
+ });
+ }
let annotationsApiPromise: Promise | undefined;
@@ -99,6 +101,7 @@ export class ObservabilityPlugin implements Plugin {
const start = () => core.getStartServices().then(([coreStart]) => coreStart);
const ruleDataClient = plugins.ruleRegistry.ruleDataService.getRuleDataClient(
+ 'observability',
plugins.ruleRegistry.ruleDataService.getFullAssetName(),
() => Promise.resolve()
);
diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx
new file mode 100644
index 0000000000000..2de5ab11664ae
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx
@@ -0,0 +1,91 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React, { useEffect, useMemo, useState } from 'react';
+
+import { Direction } from '../../common/search_strategy';
+import { AgentStatusBar } from './action_agents_status_bar';
+import { ActionAgentsStatusBadges } from './action_agents_status_badges';
+import { useActionResults } from './use_action_results';
+
+interface ActionAgentsStatusProps {
+ actionId: string;
+ expirationDate?: string;
+ agentIds?: string[];
+}
+
+const ActionAgentsStatusComponent: React.FC = ({
+ actionId,
+ expirationDate,
+ agentIds,
+}) => {
+ const [isLive, setIsLive] = useState(true);
+ const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [
+ expirationDate,
+ ]);
+ const {
+ // @ts-expect-error update types
+ data: { aggregations },
+ } = useActionResults({
+ actionId,
+ activePage: 0,
+ agentIds,
+ limit: 0,
+ direction: Direction.asc,
+ sortField: '@timestamp',
+ isLive,
+ });
+
+ const agentStatus = useMemo(() => {
+ const notRespondedCount = !agentIds?.length ? 0 : agentIds.length - aggregations.totalResponded;
+
+ return {
+ success: aggregations.successful,
+ pending: notRespondedCount,
+ failed: aggregations.failed,
+ };
+ }, [agentIds?.length, aggregations.failed, aggregations.successful, aggregations.totalResponded]);
+
+ useEffect(
+ () =>
+ setIsLive(() => {
+ if (!agentIds?.length || expired) return false;
+
+ return !!(aggregations.totalResponded !== agentIds?.length);
+ }),
+ [agentIds?.length, aggregations.totalResponded, expired]
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const ActionAgentsStatus = React.memo(ActionAgentsStatusComponent);
diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx
new file mode 100644
index 0000000000000..95b96ca454610
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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 { EuiFlexGroup, EuiHealth, EuiNotificationBadge, EuiFlexItem } from '@elastic/eui';
+import React, { memo } from 'react';
+
+import {
+ AGENT_STATUSES,
+ getColorForAgentStatus,
+ getLabelForAgentStatus,
+} from './services/agent_status';
+import type { ActionAgentStatus } from './types';
+
+export const ActionAgentsStatusBadges = memo<{
+ agentStatus: { [k in ActionAgentStatus]: number };
+ expired: boolean;
+}>(({ agentStatus, expired }) => (
+
+ {AGENT_STATUSES.map((status) => (
+
+
+
+ ))}
+
+));
+
+ActionAgentsStatusBadges.displayName = 'ActionAgentsStatusBadges';
+
+const AgentStatusBadge = memo<{ expired: boolean; status: ActionAgentStatus; count: number }>(
+ ({ expired, status, count }) => (
+ <>
+
+
+ {getLabelForAgentStatus(status, expired)}
+
+
+ {count}
+
+
+
+
+ >
+ )
+);
+
+AgentStatusBadge.displayName = 'AgentStatusBadge';
diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx
new file mode 100644
index 0000000000000..21866566cb7e3
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 styled from 'styled-components';
+import { EuiColorPaletteDisplay } from '@elastic/eui';
+import React, { useMemo } from 'react';
+
+import { AGENT_STATUSES, getColorForAgentStatus } from './services/agent_status';
+import type { ActionAgentStatus } from './types';
+
+const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)`
+ &.osquery-action-agent-status-bar {
+ border: none;
+ border-radius: 0;
+ &:after {
+ border: none;
+ }
+ }
+`;
+
+export const AgentStatusBar: React.FC<{
+ agentStatus: { [k in ActionAgentStatus]: number };
+}> = ({ agentStatus }) => {
+ const palette = useMemo(() => {
+ let stop = 0;
+ return AGENT_STATUSES.reduce((acc, status) => {
+ stop += agentStatus[status] || 0;
+ acc.push({
+ stop,
+ color: getColorForAgentStatus(status),
+ });
+ return acc;
+ }, [] as Array<{ stop: number; color: string }>);
+ }, [agentStatus]);
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
index 257c89047aab0..bf4c97d63d74c 100644
--- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
+++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
@@ -8,37 +8,18 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { i18n } from '@kbn/i18n';
-import {
- EuiLink,
- EuiFlexGroup,
- EuiFlexItem,
- EuiCard,
- EuiTextColor,
- EuiSpacer,
- EuiDescriptionList,
- EuiInMemoryTable,
- EuiCodeBlock,
- EuiProgress,
-} from '@elastic/eui';
-import React, { useCallback, useMemo, useState } from 'react';
-import styled from 'styled-components';
+import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { PLUGIN_ID } from '../../../fleet/common';
-import { pagePathGetters } from '../../../fleet/public';
+import { AgentIdToName } from '../agents/agent_id_to_name';
import { useActionResults } from './use_action_results';
import { useAllResults } from '../results/use_all_results';
import { Direction } from '../../common/search_strategy';
-import { useKibana } from '../common/lib/kibana';
-
-const StyledEuiCard = styled(EuiCard)`
- position: relative;
-`;
interface ActionResultsSummaryProps {
actionId: string;
- expirationDate: Date;
+ expirationDate?: string;
agentIds?: string[];
- isLive?: boolean;
}
const renderErrorMessage = (error: string) => (
@@ -51,14 +32,15 @@ const ActionResultsSummaryComponent: React.FC = ({
actionId,
expirationDate,
agentIds,
- isLive,
}) => {
- const getUrlForApp = useKibana().services.application.getUrlForApp;
// @ts-expect-error update types
const [pageIndex, setPageIndex] = useState(0);
// @ts-expect-error update types
const [pageSize, setPageSize] = useState(50);
- const expired = useMemo(() => expirationDate < new Date(), [expirationDate]);
+ const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [
+ expirationDate,
+ ]);
+ const [isLive, setIsLive] = useState(true);
const {
// @ts-expect-error update types
data: { aggregations, edges },
@@ -69,7 +51,7 @@ const ActionResultsSummaryComponent: React.FC = ({
limit: pageSize,
direction: Direction.asc,
sortField: '@timestamp',
- isLive: !expired && isLive,
+ isLive,
});
const { data: logsResults } = useAllResults({
@@ -82,72 +64,10 @@ const ActionResultsSummaryComponent: React.FC = ({
direction: Direction.asc,
},
],
- isLive: !expired && isLive,
+ isLive,
});
- const notRespondedCount = useMemo(() => {
- if (!agentIds || !aggregations.totalResponded) {
- return '-';
- }
-
- return agentIds.length - aggregations.totalResponded;
- }, [aggregations.totalResponded, agentIds]);
-
- const listItems = useMemo(
- () => [
- {
- title: i18n.translate(
- 'xpack.osquery.liveQueryActionResults.summary.agentsQueriedLabelText',
- {
- defaultMessage: 'Agents queried',
- }
- ),
- description: agentIds?.length,
- },
- {
- title: i18n.translate('xpack.osquery.liveQueryActionResults.summary.successfulLabelText', {
- defaultMessage: 'Successful',
- }),
- description: aggregations.successful,
- },
- {
- title: expired
- ? i18n.translate('xpack.osquery.liveQueryActionResults.summary.expiredLabelText', {
- defaultMessage: 'Expired',
- })
- : i18n.translate('xpack.osquery.liveQueryActionResults.summary.pendingLabelText', {
- defaultMessage: 'Not yet responded',
- }),
- description: notRespondedCount,
- },
- {
- title: i18n.translate('xpack.osquery.liveQueryActionResults.summary.failedLabelText', {
- defaultMessage: 'Failed',
- }),
- description: (
-
- {aggregations.failed}
-
- ),
- },
- ],
- [agentIds, aggregations.failed, aggregations.successful, notRespondedCount, expired]
- );
-
- const renderAgentIdColumn = useCallback(
- (agentId) => (
-
- {agentId}
-
- ),
- [getUrlForApp]
- );
+ const renderAgentIdColumn = useCallback((agentId) => , []);
const renderRowsColumn = useCallback(
(_, item) => {
@@ -236,30 +156,26 @@ const ActionResultsSummaryComponent: React.FC = ({
[]
);
- return (
- <>
-
-
-
- {!expired && notRespondedCount ? : null}
-
-
-
-
+ useEffect(() => {
+ setIsLive(() => {
+ if (!agentIds?.length || expired) return false;
- {edges.length ? (
- <>
-
-
- >
- ) : null}
- >
- );
+ const uniqueAgentsRepliedCount =
+ // @ts-expect-error update types
+ logsResults?.rawResponse.aggregations?.unique_agents.value ?? 0;
+
+ return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed);
+ });
+ }, [
+ agentIds?.length,
+ aggregations.failed,
+ expired,
+ logsResults?.rawResponse.aggregations?.unique_agents,
+ ]);
+
+ return edges.length ? (
+
+ ) : null;
};
export const ActionResultsSummary = React.memo(ActionResultsSummaryComponent);
diff --git a/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx
new file mode 100644
index 0000000000000..39a033f49ec90
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 { euiPaletteColorBlindBehindText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import type { ActionAgentStatus } from '../types';
+
+const visColors = euiPaletteColorBlindBehindText();
+const colorToHexMap = {
+ default: '#d3dae6',
+ primary: visColors[1],
+ secondary: visColors[0],
+ accent: visColors[2],
+ warning: visColors[5],
+ danger: visColors[9],
+};
+
+export const AGENT_STATUSES: ActionAgentStatus[] = ['success', 'pending', 'failed'];
+
+export function getColorForAgentStatus(agentStatus: ActionAgentStatus): string {
+ switch (agentStatus) {
+ case 'success':
+ return colorToHexMap.secondary;
+ case 'pending':
+ return colorToHexMap.default;
+ case 'failed':
+ return colorToHexMap.danger;
+ default:
+ throw new Error(`Unsupported action agent status ${agentStatus}`);
+ }
+}
+
+export function getLabelForAgentStatus(agentStatus: ActionAgentStatus, expired: boolean): string {
+ switch (agentStatus) {
+ case 'success':
+ return i18n.translate('xpack.osquery.liveQueryActionResults.summary.successfulLabelText', {
+ defaultMessage: 'Successful',
+ });
+ case 'pending':
+ return expired
+ ? i18n.translate('xpack.osquery.liveQueryActionResults.summary.expiredLabelText', {
+ defaultMessage: 'Expired',
+ })
+ : i18n.translate('xpack.osquery.liveQueryActionResults.summary.pendingLabelText', {
+ defaultMessage: 'Not yet responded',
+ });
+ case 'failed':
+ return i18n.translate('xpack.osquery.liveQueryActionResults.summary.failedLabelText', {
+ defaultMessage: 'Failed',
+ });
+ default:
+ throw new Error(`Unsupported action agent status ${agentStatus}`);
+ }
+}
diff --git a/x-pack/plugins/osquery/public/action_results/types.ts b/x-pack/plugins/osquery/public/action_results/types.ts
new file mode 100644
index 0000000000000..ce9415986ba02
--- /dev/null
+++ b/x-pack/plugins/osquery/public/action_results/types.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export type ActionAgentStatus = 'success' | 'pending' | 'failed';
diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts
index ab69bf86dc326..29bff0819956a 100644
--- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts
+++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts
@@ -83,7 +83,7 @@ export const useActionResults = ({
const totalResponded =
// @ts-expect-error update types
- responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count;
+ responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count ?? 0;
const aggsBuckets =
// @ts-expect-error update types
responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.responses.buckets;
@@ -120,7 +120,7 @@ export const useActionResults = ({
failed: 0,
},
},
- refetchInterval: isLive ? 1000 : false,
+ refetchInterval: isLive ? 5000 : false,
keepPreviousData: true,
enabled: !skip && !!agentIds?.length,
onSuccess: () => setErrorToast(),
diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx
index 0ee928ad8aa14..045c1f67b070d 100644
--- a/x-pack/plugins/osquery/public/actions/actions_table.tsx
+++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx
@@ -9,6 +9,7 @@ import { isArray } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui';
import React, { useState, useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
import { useAllActions } from './use_all_actions';
import { Direction } from '../../common/search_strategy';
@@ -27,6 +28,7 @@ const ActionTableResultsButton = React.memo(({ ac
ActionTableResultsButton.displayName = 'ActionTableResultsButton';
const ActionsTableComponent = () => {
+ const { push } = useHistory();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(20);
@@ -67,6 +69,16 @@ const ActionsTableComponent = () => {
[]
);
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ query: item._source?.data?.query,
+ },
+ }),
+ [push]
+ );
+
const columns = useMemo(
() => [
{
@@ -106,6 +118,11 @@ const ActionsTableComponent = () => {
defaultMessage: 'View details',
}),
actions: [
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{
render: renderActionsColumn,
},
@@ -113,6 +130,7 @@ const ActionsTableComponent = () => {
},
],
[
+ handlePlayClick,
renderActionsColumn,
renderAgentsColumn,
renderCreatedByColumn,
@@ -135,6 +153,7 @@ const ActionsTableComponent = () => {
= ({ policyId }
const href = useMemo(
() =>
getUrlForApp(PLUGIN_ID, {
- path: `#` + pagePathGetters.policy_details({ policyId }),
+ path: `#` + pagePathGetters.policy_details({ policyId })[1],
}),
[getUrlForApp, policyId]
);
@@ -38,7 +38,7 @@ const AgentsPolicyLinkComponent: React.FC = ({ policyId }
event.preventDefault();
return navigateToApp(PLUGIN_ID, {
- path: `#` + pagePathGetters.policy_details({ policyId }),
+ path: `#` + pagePathGetters.policy_details({ policyId })[1],
});
}
},
diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts
index 6f87610667198..c51f2d2f44a5c 100644
--- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts
+++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts
@@ -30,7 +30,6 @@ export const useAgentPolicies = () => {
}),
{
initialData: { items: [], total: 0, page: 1, perPage: 100 },
- placeholderData: [],
keepPreviousData: true,
select: (response) => response.items,
onSuccess: () => setErrorToast(),
diff --git a/x-pack/plugins/osquery/public/agents/agent_id_to_name.tsx b/x-pack/plugins/osquery/public/agents/agent_id_to_name.tsx
new file mode 100644
index 0000000000000..6db21c028ece8
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/agent_id_to_name.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 { EuiLink } from '@elastic/eui';
+import React from 'react';
+
+import { useAgentDetails } from './use_agent_details';
+import { PLUGIN_ID } from '../../../fleet/common';
+import { pagePathGetters } from '../../../fleet/public';
+import { useKibana } from '../common/lib/kibana';
+
+interface AgentIdToNameProps {
+ agentId: string;
+}
+
+const AgentIdToNameComponent: React.FC = ({ agentId }) => {
+ const getUrlForApp = useKibana().services.application.getUrlForApp;
+ const { data } = useAgentDetails({ agentId });
+
+ return (
+
+ {data?.item.local_metadata.host.name ?? agentId}
+
+ );
+};
+
+export const AgentIdToName = React.memo(AgentIdToNameComponent);
diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx
index 7e8f49c051614..53e2ce1d53420 100644
--- a/x-pack/plugins/osquery/public/agents/agents_table.tsx
+++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx
@@ -21,7 +21,12 @@ import {
generateAgentSelection,
} from './helpers';
-import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations';
+import {
+ SELECT_AGENT_LABEL,
+ generateSelectedAgentsMessage,
+ ALL_AGENTS_LABEL,
+ AGENT_POLICY_LABEL,
+} from './translations';
import {
AGENT_GROUP_KEY,
@@ -72,8 +77,17 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
useEffect(() => {
if (agentSelection && !defaultValueInitialized.current && options.length) {
- if (agentSelection.policiesSelected) {
- const policyOptions = find(['label', 'Policy'], options);
+ if (agentSelection.allAgentsSelected) {
+ const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options);
+
+ if (allAgentsOptions?.options) {
+ setSelectedOptions(allAgentsOptions.options);
+ defaultValueInitialized.current = true;
+ }
+ }
+
+ if (agentSelection.policiesSelected.length) {
+ const policyOptions = find(['label', AGENT_POLICY_LABEL], options);
if (policyOptions) {
const defaultOptions = policyOptions.options?.filter((option) =>
@@ -82,12 +96,12 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
if (defaultOptions?.length) {
setSelectedOptions(defaultOptions);
+ defaultValueInitialized.current = true;
}
- defaultValueInitialized.current = true;
}
}
}
- }, [agentSelection, options]);
+ }, [agentSelection, options, selectedOptions]);
useEffect(() => {
// update the groups when groups or agents have changed
diff --git a/x-pack/plugins/osquery/public/agents/use_agent_details.ts b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
new file mode 100644
index 0000000000000..1a0663812dec3
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { useQuery } from 'react-query';
+
+import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common';
+import { useErrorToast } from '../common/hooks/use_error_toast';
+import { useKibana } from '../common/lib/kibana';
+
+interface UseAgentDetails {
+ agentId: string;
+}
+
+export const useAgentDetails = ({ agentId }: UseAgentDetails) => {
+ const { http } = useKibana().services;
+ const setErrorToast = useErrorToast();
+ return useQuery(
+ ['agentDetails', agentId],
+ () => http.get(agentRouteService.getInfoPath(agentId)),
+ {
+ enabled: agentId.length > 0,
+ onSuccess: () => setErrorToast(),
+ onError: (error) =>
+ setErrorToast(error as Error, {
+ title: i18n.translate('xpack.osquery.agentDetails.fetchError', {
+ defaultMessage: 'Error while fetching agent details',
+ }),
+ }),
+ }
+ );
+};
diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
index 30ba4d2f57907..cda15cc805437 100644
--- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts
+++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
@@ -38,7 +38,7 @@ export const useAllAgents = (
let kuery = `last_checkin_status: online and (${policyFragment})`;
if (searchValue) {
- kuery += `and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
+ kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
}
return http.get(agentRouteService.getListPath(), {
diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
index 28d69a6a7b15a..63036f5f693f7 100644
--- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
+++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
@@ -57,7 +57,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
return getUrlForApp(PLUGIN_ID, {
path:
`#` +
- pagePathGetters.policy_details({ policyId: policy?.policy_id }) +
+ pagePathGetters.policy_details({ policyId: policy?.policy_id })[1] +
'?openEnrollmentFlyout=true',
});
}, [getUrlForApp, policy?.policy_id]);
diff --git a/x-pack/plugins/osquery/public/index.ts b/x-pack/plugins/osquery/public/index.ts
index f0e956b64ee06..fadd61cce85ef 100644
--- a/x-pack/plugins/osquery/public/index.ts
+++ b/x-pack/plugins/osquery/public/index.ts
@@ -13,4 +13,4 @@ import { OsqueryPlugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new OsqueryPlugin(initializerContext);
}
-export { OsqueryPluginSetup, OsqueryPluginStart } from './types';
+export type { OsqueryPluginSetup, OsqueryPluginStart } from './types';
diff --git a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx b/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx
deleted file mode 100644
index d1ef18e2e12ea..0000000000000
--- a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx
+++ /dev/null
@@ -1,30 +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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiCodeBlock, EuiSpacer } from '@elastic/eui';
-import React from 'react';
-import { useParams } from 'react-router-dom';
-
-import { useActionDetails } from '../../actions/use_action_details';
-import { ResultsTable } from '../../results/results_table';
-
-const QueryAgentResultsComponent = () => {
- const { actionId, agentId } = useParams<{ actionId: string; agentId: string }>();
- const { data } = useActionDetails({ actionId });
-
- return (
- <>
-
- {data?.actionDetails._source?.data?.query}
-
-
-
- >
- );
-};
-
-export const QueryAgentResults = React.memo(QueryAgentResultsComponent);
diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
index 9e952810e3352..8654a74fecfb4 100644
--- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx
+++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
@@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback, useMemo, useState } from 'react';
import { useMutation } from 'react-query';
+import deepMerge from 'deepmerge';
import { UseField, Form, FormData, useForm, useFormData, FIELD_TYPES } from '../../shared_imports';
import { AgentsTableField } from './agents_table_field';
@@ -33,12 +34,19 @@ const FORM_ID = 'liveQueryForm';
export const MAX_QUERY_LENGTH = 2000;
+const GhostFormField = () => <>>;
+
interface LiveQueryFormProps {
+ agentId?: string | undefined;
defaultValue?: Partial | undefined;
onSuccess?: () => void;
}
-const LiveQueryFormComponent: React.FC = ({ defaultValue, onSuccess }) => {
+const LiveQueryFormComponent: React.FC = ({
+ agentId,
+ defaultValue,
+ onSuccess,
+}) => {
const { http } = useKibana().services;
const [showSavedQueryFlyout, setShowSavedQueryFlyout] = useState(false);
const setErrorToast = useErrorToast();
@@ -71,8 +79,6 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on
}
);
- const expirationDate = useMemo(() => new Date(data?.actions[0].expiration), [data?.actions]);
-
const formSchema = {
query: {
type: FIELD_TYPES.TEXT,
@@ -100,9 +106,18 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on
options: {
stripEmptyFields: false,
},
- defaultValue: defaultValue ?? {
- query: '',
- },
+ defaultValue: deepMerge(
+ {
+ agentSelection: {
+ agents: [],
+ allAgentsSelected: false,
+ platformsSelected: [],
+ policiesSelected: [],
+ },
+ query: '',
+ },
+ defaultValue ?? {}
+ ),
});
const { submit } = form;
@@ -147,6 +162,59 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on
const flyoutFormDefaultValue = useMemo(() => ({ query }), [query]);
+ const queryFieldStepContent = useMemo(
+ () => (
+ <>
+
+
+
+ {!agentId && (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ >
+ ),
+ [
+ agentId,
+ agentSelected,
+ handleShowSaveQueryFlout,
+ queryComponentProps,
+ queryValueProvided,
+ resultsStatus,
+ submit,
+ ]
+ );
+
+ const resultsStepContent = useMemo(
+ () =>
+ actionId ? (
+
+ ) : null,
+ [actionId, agentIds, data?.actions]
+ );
+
const formSteps: EuiContainedStepProps[] = useMemo(
() => [
{
@@ -160,73 +228,34 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on
title: i18n.translate('xpack.osquery.liveQueryForm.steps.queryStepHeading', {
defaultMessage: 'Enter query',
}),
- children: (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- ),
+ children: queryFieldStepContent,
status: queryStatus,
},
{
title: i18n.translate('xpack.osquery.liveQueryForm.steps.resultsStepHeading', {
defaultMessage: 'Check results',
}),
- children: actionId ? (
-
- ) : null,
+ children: resultsStepContent,
status: resultsStatus,
},
],
- [
- actionId,
- agentIds,
- agentSelected,
- handleShowSaveQueryFlout,
- queryComponentProps,
- queryStatus,
- queryValueProvided,
- expirationDate,
- resultsStatus,
- submit,
- ]
+ [agentSelected, queryFieldStepContent, queryStatus, resultsStepContent, resultsStatus]
+ );
+
+ const singleAgentForm = useMemo(
+ () => (
+
+
+ {queryFieldStepContent}
+ {resultsStepContent}
+
+ ),
+ [queryFieldStepContent, resultsStepContent]
);
return (
<>
-
+
{showSavedQueryFlyout ? (
= ({ disabled, field }) => {
const { value, setValue, errors } = field;
const error = errors[0]?.message;
+ const savedQueriesDropdownRef = useRef(null);
const handleSavedQueryChange = useCallback(
(savedQuery) => {
- setValue(savedQuery.query);
+ setValue(savedQuery?.query ?? '');
},
[setValue]
);
const handleEditorChange = useCallback(
(newValue) => {
+ savedQueriesDropdownRef.current?.clearSelection();
setValue(newValue);
},
[setValue]
@@ -39,7 +44,11 @@ const LiveQueryQueryFieldComponent: React.FC = ({ disa
return (
<>
-
+
}>
diff --git a/x-pack/plugins/osquery/public/plugin.ts b/x-pack/plugins/osquery/public/plugin.ts
index 631f3adba4c47..12f9025e406db 100644
--- a/x-pack/plugins/osquery/public/plugin.ts
+++ b/x-pack/plugins/osquery/public/plugin.ts
@@ -31,6 +31,7 @@ import {
LazyOsqueryManagedPolicyEditExtension,
LazyOsqueryManagedCustomButtonExtension,
} from './fleet_integration';
+import { getLazyOsqueryAction } from './shared_components';
export function toggleOsqueryPlugin(
updater$: Subject,
@@ -160,7 +161,14 @@ export class OsqueryPlugin implements Plugin = ({
- actionId,
- agentIds,
- expirationDate,
- endDate,
- isLive,
- startDate,
-}) => {
- const tabs = useMemo(
- () => [
- {
- id: 'status',
- name: 'Status',
- content: (
- <>
-
-
- >
- ),
- },
- {
- id: 'results',
- name: 'Results',
- content: (
- <>
-
-
- >
- ),
- },
- ],
- [actionId, agentIds, endDate, isLive, startDate, expirationDate]
- );
-
- return (
-
- );
-};
-
-export const ResultTabs = React.memo(ResultTabsComponent);
diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx
index 6ff60d30d23bf..d82737ab51e7c 100644
--- a/x-pack/plugins/osquery/public/results/results_table.tsx
+++ b/x-pack/plugins/osquery/public/results/results_table.tsx
@@ -14,6 +14,8 @@ import {
EuiDataGridColumn,
EuiLink,
EuiLoadingContent,
+ EuiProgress,
+ EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
@@ -37,17 +39,16 @@ interface ResultsTableComponentProps {
selectedAgent?: string;
agentIds?: string[];
endDate?: string;
- isLive?: boolean;
startDate?: string;
}
const ResultsTableComponent: React.FC = ({
actionId,
agentIds,
- isLive,
startDate,
endDate,
}) => {
+ const [isLive, setIsLive] = useState(true);
const {
// @ts-expect-error update types
data: { aggregations },
@@ -60,13 +61,13 @@ const ResultsTableComponent: React.FC = ({
sortField: '@timestamp',
isLive,
});
-
+ const expired = useMemo(() => (!endDate ? false : new Date(endDate) < new Date()), [endDate]);
const { getUrlForApp } = useKibana().services.application;
const getFleetAppUrl = useCallback(
(agentId) =>
getUrlForApp('fleet', {
- path: `#` + pagePathGetters.agent_details({ agentId }),
+ path: `#` + pagePathGetters.agent_details({ agentId })[1],
}),
[getUrlForApp]
);
@@ -216,29 +217,56 @@ const ResultsTableComponent: React.FC = ({
[actionId, endDate, startDate]
);
- if (!aggregations.totalResponded) {
- return ;
- }
+ useEffect(
+ () =>
+ setIsLive(() => {
+ if (!agentIds?.length || expired) return false;
+
+ const uniqueAgentsRepliedCount =
+ // @ts-expect-error-type
+ allResultsData?.rawResponse.aggregations?.unique_agents.value ?? 0;
+
+ return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed);
+ }),
+ [
+ agentIds?.length,
+ aggregations.failed,
+ // @ts-expect-error-type
+ allResultsData?.rawResponse.aggregations?.unique_agents.value,
+ expired,
+ ]
+ );
- if (aggregations.totalResponded && isFetched && !allResultsData?.edges.length) {
- return ;
+ if (!isFetched) {
+ return ;
}
return (
- // @ts-expect-error update types
-
-
-
+ <>
+ {isLive && }
+
+ {isFetched && !allResultsData?.edges.length ? (
+ <>
+
+
+ >
+ ) : (
+ // @ts-expect-error update types
+
+
+
+ )}
+ >
);
};
diff --git a/x-pack/plugins/osquery/public/results/translations.ts b/x-pack/plugins/osquery/public/results/translations.ts
index 8e77e78ec76e2..e4f71d818f01d 100644
--- a/x-pack/plugins/osquery/public/results/translations.ts
+++ b/x-pack/plugins/osquery/public/results/translations.ts
@@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
export const generateEmptyDataMessage = (agentsResponded: number): string => {
return i18n.translate('xpack.osquery.results.multipleAgentsResponded', {
defaultMessage:
- '{agentsResponded, plural, one {# agent has} other {# agents have}} responded, but no osquery data has been reported.',
+ '{agentsResponded, plural, one {# agent has} other {# agents have}} responded, no osquery data has been reported.',
values: { agentsResponded },
});
};
diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts
index 1121898410278..a13fceedfa07a 100644
--- a/x-pack/plugins/osquery/public/results/use_all_results.ts
+++ b/x-pack/plugins/osquery/public/results/use_all_results.ts
@@ -78,7 +78,7 @@ export const useAllResults = ({
};
},
{
- refetchInterval: isLive ? 1000 : false,
+ refetchInterval: isLive ? 5000 : false,
enabled: !skip,
onSuccess: () => setErrorToast(),
onError: (error: Error) =>
diff --git a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx
index e4f1bb447a15a..02f5c8b6fb2a5 100644
--- a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx
@@ -6,54 +6,24 @@
*/
import { get } from 'lodash';
-import {
- EuiButtonEmpty,
- EuiTextColor,
- EuiFlexGroup,
- EuiFlexItem,
- EuiCodeBlock,
- EuiSpacer,
- EuiDescriptionList,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
-} from '@elastic/eui';
+import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiCodeBlock, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
import { useParams } from 'react-router-dom';
-import styled from 'styled-components';
-import { Direction } from '../../../../common/search_strategy';
import { useRouterNavigate } from '../../../common/lib/kibana';
import { WithHeaderLayout } from '../../../components/layouts';
-import { useActionResults } from '../../../action_results/use_action_results';
import { useActionDetails } from '../../../actions/use_action_details';
import { ResultTabs } from '../../saved_queries/edit/tabs';
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
-const Divider = styled.div`
- width: 0;
- height: 100%;
- border-left: ${({ theme }) => theme.eui.euiBorderThin};
-`;
-
const LiveQueryDetailsPageComponent = () => {
const { actionId } = useParams<{ actionId: string }>();
useBreadcrumbs('live_query_details', { liveQueryId: actionId });
const liveQueryListProps = useRouterNavigate('live_queries');
const { data } = useActionDetails({ actionId });
- const expirationDate = useMemo(() => new Date(data?.actionDetails._source.expiration), [
- data?.actionDetails,
- ]);
- const expired = useMemo(() => expirationDate < new Date(), [expirationDate]);
- const { data: actionResultsData } = useActionResults({
- actionId,
- activePage: 0,
- limit: 0,
- direction: Direction.asc,
- sortField: '@timestamp',
- });
const LeftColumn = useMemo(
() => (
@@ -82,72 +52,14 @@ const LiveQueryDetailsPageComponent = () => {
[liveQueryListProps]
);
- const failed = useMemo(() => {
- let result = actionResultsData?.aggregations.failed;
- if (expired) {
- result = '-';
- if (data?.actionDetails?.fields?.agents && actionResultsData?.aggregations) {
- result =
- data.actionDetails.fields.agents.length - actionResultsData.aggregations.successful;
- }
- }
- return result;
- }, [expired, actionResultsData?.aggregations, data?.actionDetails?.fields?.agents]);
-
- const RightColumn = useMemo(
- () => (
-
-
- <>>
-
-
-
-
-
- {/* eslint-disable-next-line react-perf/jsx-no-new-object-as-prop */}
-
-
-
-
-
- {data?.actionDetails?.fields?.agents?.length ?? '0'}
-
-
-
-
-
-
-
- {/* eslint-disable-next-line react-perf/jsx-no-new-object-as-prop */}
-
-
-
-
-
- {failed}
-
-
-
-
- ),
- [data?.actionDetails?.fields?.agents?.length, failed]
- );
-
return (
-
+
{data?.actionDetails._source?.data?.query}
{
useBreadcrumbs('live_query_new');
+ const { replace } = useHistory();
const location = useLocation();
const liveQueryListProps = useRouterNavigate('live_queries');
const formDefaultValue = useMemo(() => {
const queryParams = qs.parse(location.search);
+ if (location.state?.form.query) {
+ replace({ state: null });
+ return { query: location.state?.form.query };
+ }
+
if (queryParams?.agentPolicyId) {
return {
agentSelection: {
@@ -37,7 +43,7 @@ const NewLiveQueryPageComponent = () => {
}
return undefined;
- }, [location.search]);
+ }, [location.search, location.state, replace]);
const LeftColumn = useMemo(
() => (
diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
index 4aaf8e4fc4fc3..5bdba133fad72 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
@@ -33,7 +33,7 @@ const EditSavedQueryPageComponent = () => {
const updateSavedQueryMutation = useUpdateSavedQuery({ savedQueryId });
const deleteSavedQueryMutation = useDeleteSavedQuery({ savedQueryId });
- useBreadcrumbs('saved_query_edit', { savedQueryId: savedQueryDetails?.attributes?.id ?? '' });
+ useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.attributes?.id ?? '' });
const handleCloseDeleteConfirmationModal = useCallback(() => {
setIsDeleteModalVisible(false);
diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx
index 1946cd6dd3450..1f56daaa3bdb5 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx
@@ -10,12 +10,11 @@ import React, { useMemo } from 'react';
import { ResultsTable } from '../../../results/results_table';
import { ActionResultsSummary } from '../../../action_results/action_results_summary';
+import { ActionAgentsStatus } from '../../../action_results/action_agents_status';
interface ResultTabsProps {
actionId: string;
agentIds?: string[];
- expirationDate: Date;
- isLive?: boolean;
startDate?: string;
endDate?: string;
}
@@ -24,8 +23,6 @@ const ResultTabsComponent: React.FC = ({
actionId,
agentIds,
endDate,
- expirationDate,
- isLive,
startDate,
}) => {
const tabs = useMemo(
@@ -39,7 +36,6 @@ const ResultTabsComponent: React.FC = ({
@@ -55,23 +51,26 @@ const ResultTabsComponent: React.FC = ({
>
),
},
],
- [actionId, agentIds, endDate, expirationDate, isLive, startDate]
+ [actionId, agentIds, endDate, startDate]
);
return (
-
+ <>
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
index 7e8e8e543dfab..8738c06d06597 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
@@ -16,6 +16,7 @@ import {
import React, { useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory } from 'react-router-dom';
import { SavedObject } from 'kibana/public';
import { WithHeaderLayout } from '../../../components/layouts';
@@ -51,6 +52,7 @@ const EditButton = React.memo(EditButtonComponent);
const SavedQueriesPageComponent = () => {
useBreadcrumbs('saved_queries');
+ const { push } = useHistory();
const newQueryLinkProps = useRouterNavigate('saved_queries/new');
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
@@ -59,21 +61,15 @@ const SavedQueriesPageComponent = () => {
const { data } = useSavedQueries({ isLive: true });
- // const handlePlayClick = useCallback(
- // (item) =>
- // push({
- // search: qs.stringify({
- // tab: 'live_query',
- // }),
- // state: {
- // query: {
- // id: item.id,
- // query: item.attributes.query,
- // },
- // },
- // }),
- // [push]
- // );
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ savedQueryId: item.id,
+ },
+ }),
+ [push]
+ );
const renderEditAction = useCallback(
(item: SavedObject<{ name: string }>) => (
@@ -96,45 +92,53 @@ const SavedQueriesPageComponent = () => {
() => [
{
field: 'attributes.id',
- name: 'Query ID',
+ name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', {
+ defaultMessage: 'Query ID',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.description',
- name: 'Description',
+ name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', {
+ defaultMessage: 'Description',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.created_by',
- name: 'Created by',
+ name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', {
+ defaultMessage: 'Created by',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.updated_at',
- name: 'Last updated at',
+ name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', {
+ defaultMessage: 'Last updated at',
+ }),
sortable: (item: SavedObject<{ updated_at: string }>) =>
item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0,
truncateText: true,
render: renderUpdatedAt,
},
{
- name: 'Actions',
+ name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', {
+ defaultMessage: 'Actions',
+ }),
actions: [
- // {
- // name: 'Live query',
- // description: 'Run live query',
- // type: 'icon',
- // icon: 'play',
- // onClick: handlePlayClick,
- // },
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{ render: renderEditAction },
],
},
],
- [renderEditAction, renderUpdatedAt]
+ [handlePlayClick, renderEditAction, renderUpdatedAt]
);
const onTableChange = useCallback(({ page = {}, sort = {} }) => {
diff --git a/x-pack/plugins/osquery/public/saved_queries/form/index.tsx b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx
index 174227eb5e6e5..9bbf847c4d2a0 100644
--- a/x-pack/plugins/osquery/public/saved_queries/form/index.tsx
+++ b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx
@@ -7,6 +7,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ALL_OSQUERY_VERSIONS_OPTIONS } from '../../scheduled_query_groups/queries/constants';
@@ -57,7 +58,12 @@ const SavedQueryFormComponent = () => (
euiFieldProps={{
noSuggestions: false,
singleSelection: { asPlainText: true },
- placeholder: ALL_OSQUERY_VERSIONS_OPTIONS[0].label,
+ placeholder: i18n.translate(
+ 'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel',
+ {
+ defaultMessage: 'ALL',
+ }
+ ),
options: ALL_OSQUERY_VERSIONS_OPTIONS,
onCreateOption: undefined,
}}
diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
index e30954a695b2d..fc7cee2fc804c 100644
--- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
+++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
@@ -6,45 +6,83 @@
*/
import { find } from 'lodash/fp';
-import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui';
-import React, { useCallback, useState } from 'react';
+import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiTextColor } from '@elastic/eui';
+import React, {
+ forwardRef,
+ useCallback,
+ useEffect,
+ useImperativeHandle,
+ useMemo,
+ useState,
+} from 'react';
import { SimpleSavedObject } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory, useLocation } from 'react-router-dom';
+import styled from 'styled-components';
import { useSavedQueries } from './use_saved_queries';
+export interface SavedQueriesDropdownRef {
+ clearSelection: () => void;
+}
+
+const TextTruncate = styled.div`
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+const StyledEuiCodeBlock = styled(EuiCodeBlock)`
+ .euiCodeBlock__line {
+ white-space: nowrap;
+ }
+`;
+
interface SavedQueriesDropdownProps {
disabled?: boolean;
onChange: (
- value: SimpleSavedObject<{
- id: string;
- description?: string | undefined;
- query: string;
- }>['attributes']
+ value:
+ | SimpleSavedObject<{
+ id: string;
+ description?: string | undefined;
+ query: string;
+ }>['attributes']
+ | null
) => void;
}
-const SavedQueriesDropdownComponent: React.FC = ({
- disabled,
- onChange,
-}) => {
+const SavedQueriesDropdownComponent = forwardRef<
+ SavedQueriesDropdownRef,
+ SavedQueriesDropdownProps
+>(({ disabled, onChange }, ref) => {
+ const { replace } = useHistory();
+ const location = useLocation();
const [selectedOptions, setSelectedOptions] = useState([]);
const { data } = useSavedQueries({});
- const queryOptions =
- data?.savedObjects?.map((savedQuery) => ({
- label: savedQuery.attributes.id ?? '',
- value: {
- id: savedQuery.attributes.id,
- description: savedQuery.attributes.description,
- query: savedQuery.attributes.query,
- },
- })) ?? [];
+ const queryOptions = useMemo(
+ () =>
+ data?.savedObjects?.map((savedQuery) => ({
+ label: savedQuery.attributes.id ?? '',
+ value: {
+ savedObjectId: savedQuery.id,
+ id: savedQuery.attributes.id,
+ description: savedQuery.attributes.description,
+ query: savedQuery.attributes.query,
+ },
+ })) ?? [],
+ [data?.savedObjects]
+ );
const handleSavedQueryChange = useCallback(
(newSelectedOptions) => {
+ if (!newSelectedOptions.length) {
+ onChange(null);
+ setSelectedOptions(newSelectedOptions);
+ return;
+ }
+
const selectedSavedQuery = find(
['attributes.id', newSelectedOptions[0].value.id],
data?.savedObjects
@@ -62,17 +100,41 @@ const SavedQueriesDropdownComponent: React.FC = ({
({ value }) => (
<>
{value.id}
-
- {value.description}
-
-
- {value.query}
-
+
+ {value.description}
+
+
+ {value.query.split('\n').join(' ')}
+
>
),
[]
);
+ const clearSelection = useCallback(() => setSelectedOptions([]), []);
+
+ useEffect(() => {
+ const savedQueryId = location.state?.form?.savedQueryId;
+
+ if (savedQueryId) {
+ const savedQueryOption = find(['value.savedObjectId', savedQueryId], queryOptions);
+
+ if (savedQueryOption) {
+ handleSavedQueryChange([savedQueryOption]);
+ }
+
+ replace({ state: null });
+ }
+ }, [handleSavedQueryChange, replace, location.state, queryOptions]);
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ clearSelection,
+ }),
+ [clearSelection]
+ );
+
return (
= ({
selectedOptions={selectedOptions}
onChange={handleSavedQueryChange}
renderOption={renderOption}
- rowHeight={90}
+ rowHeight={110}
/>
);
-};
+});
export const SavedQueriesDropdown = React.memo(SavedQueriesDropdownComponent);
diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts
index 324d4aace1647..bb5a73d9d50fa 100644
--- a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts
+++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts
@@ -40,7 +40,7 @@ export const useSavedQueries = ({
{
keepPreviousData: true,
// Refetch the data every 10 seconds
- refetchInterval: isLive ? 10000 : false,
+ refetchInterval: isLive ? 5000 : false,
}
);
};
diff --git a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
index 1260413676a4e..6f4aa51710811 100644
--- a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
+++ b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
@@ -56,7 +56,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps)
i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', {
defaultMessage: 'Successfully updated "{savedQueryName}" query',
values: {
- savedQueryName: payload.attributes?.name ?? '',
+ savedQueryName: payload.attributes?.id ?? '',
},
})
);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
index b37c315849f60..95a31efeaf135 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
@@ -18,9 +18,10 @@ import {
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
- EuiDescribedFormGroup,
+ EuiText,
} from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { satisfies } from 'semver';
@@ -70,10 +71,14 @@ const QueryFlyoutComponent: React.FC = ({
[integrationPackageVersion]
);
- const { submit, setFieldValue } = form;
+ const { submit, setFieldValue, reset } = form;
const handleSetQueryValue = useCallback(
(savedQuery) => {
+ if (!savedQuery) {
+ return reset();
+ }
+
setFieldValue('id', savedQuery.id);
setFieldValue('query', savedQuery.query);
@@ -93,7 +98,7 @@ const QueryFlyoutComponent: React.FC = ({
setFieldValue('version', [savedQuery.version]);
}
},
- [isFieldSupported, setFieldValue]
+ [isFieldSupported, setFieldValue, reset]
);
return (
@@ -128,10 +133,7 @@ const QueryFlyoutComponent: React.FC = ({
- Set heading level based on context}
- description={'Will be wrapped in a small, subdued EuiText block.'}
- >
+
= ({
+
+
+
+
+ }
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
euiFieldProps={{
isDisabled: !isFieldSupported,
noSuggestions: false,
singleSelection: { asPlainText: true },
- placeholder: ALL_OSQUERY_VERSIONS_OPTIONS[0].label,
+ placeholder: i18n.translate(
+ 'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel',
+ {
+ defaultMessage: 'ALL',
+ }
+ ),
options: ALL_OSQUERY_VERSIONS_OPTIONS,
onCreateOption: undefined,
}}
@@ -160,7 +177,7 @@ const QueryFlyoutComponent: React.FC = ({
euiFieldProps={{ disabled: !isFieldSupported }}
/>
-
+
{!isFieldSupported ? (
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
index d8dbaad2f17e8..0b23ce924f930 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -65,14 +65,6 @@ export const formSchema = {
defaultMessage="Minimum Osquery version"
/>
-
-
-
-
-
) as unknown) as string,
validations: [],
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
index 36d15587086f2..01acf2dc0d826 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
@@ -258,7 +258,7 @@ const ViewResultsInDiscoverActionComponent: React.FC (props) => {
+ const OsqueryAction = lazy(() => import('./osquery_action'));
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx
new file mode 100644
index 0000000000000..cf8a85cea244c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx
@@ -0,0 +1,99 @@
+/*
+ * 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 { EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui';
+import React, { useEffect, useState } from 'react';
+import { QueryClientProvider } from 'react-query';
+import { KibanaContextProvider, useKibana } from '../../common/lib/kibana';
+
+import { LiveQueryForm } from '../../live_queries/form';
+import { queryClient } from '../../query_client';
+
+interface OsqueryActionProps {
+ hostId?: string | undefined;
+}
+
+const OsqueryActionComponent: React.FC = ({ hostId }) => {
+ const [agentId, setAgentId] = useState();
+ const { indexPatterns, search } = useKibana().services.data;
+
+ useEffect(() => {
+ if (hostId) {
+ const findAgent = async () => {
+ const searchSource = await search.searchSource.create();
+ const indexPattern = await indexPatterns.find('.fleet-agents');
+
+ searchSource.setField('index', indexPattern[0]);
+ searchSource.setField('filter', [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ key: 'local_metadata.host.id',
+ value: hostId,
+ },
+ query: {
+ match_phrase: {
+ 'local_metadata.host.id': hostId,
+ },
+ },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ key: 'active',
+ value: 'true',
+ },
+ query: {
+ match_phrase: {
+ active: 'true',
+ },
+ },
+ },
+ ]);
+
+ const response = await searchSource.fetch$().toPromise();
+
+ if (response.rawResponse.hits.hits.length && response.rawResponse.hits.hits[0]._id) {
+ setAgentId(response.rawResponse.hits.hits[0]._id);
+ }
+ };
+
+ findAgent();
+ }
+ });
+
+ if (!agentId) {
+ return ;
+ }
+
+ return (
+ // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
+
+ );
+};
+
+export const OsqueryAction = React.memo(OsqueryActionComponent);
+
+// @ts-expect-error update types
+const OsqueryActionWrapperComponent = ({ services, ...props }) => (
+
+
+
+
+
+
+
+);
+
+const OsqueryActionWrapper = React.memo(OsqueryActionWrapperComponent);
+
+// eslint-disable-next-line import/no-default-export
+export { OsqueryActionWrapper as default };
diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts
index 9a466dfc619b6..fd21b39d25504 100644
--- a/x-pack/plugins/osquery/public/types.ts
+++ b/x-pack/plugins/osquery/public/types.ts
@@ -16,11 +16,13 @@ import {
TriggersAndActionsUIPublicPluginSetup,
TriggersAndActionsUIPublicPluginStart,
} from '../../triggers_actions_ui/public';
+import { getLazyOsqueryAction } from './shared_components';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface OsqueryPluginSetup {}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface OsqueryPluginStart {}
+export interface OsqueryPluginStart {
+ OsqueryAction?: ReturnType;
+}
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
diff --git a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts
index 537b6d7874ab8..5535d707cf5c0 100644
--- a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts
+++ b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts
@@ -24,7 +24,7 @@ export const savedQuerySavedObjectMappings: SavedObjectsType['mappings'] = {
type: 'date',
},
created_by: {
- type: 'text',
+ type: 'keyword',
},
platform: {
type: 'keyword',
@@ -36,7 +36,7 @@ export const savedQuerySavedObjectMappings: SavedObjectsType['mappings'] = {
type: 'date',
},
updated_by: {
- type: 'text',
+ type: 'keyword',
},
interval: {
type: 'keyword',
@@ -57,19 +57,19 @@ export const packSavedObjectMappings: SavedObjectsType['mappings'] = {
type: 'text',
},
name: {
- type: 'text',
+ type: 'keyword',
},
created_at: {
type: 'date',
},
created_by: {
- type: 'text',
+ type: 'keyword',
},
updated_at: {
type: 'date',
},
updated_by: {
- type: 'text',
+ type: 'keyword',
},
queries: {
properties: {
@@ -77,7 +77,7 @@ export const packSavedObjectMappings: SavedObjectsType['mappings'] = {
type: 'keyword',
},
interval: {
- type: 'text',
+ type: 'keyword',
},
},
},
diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts
index 63b1b207f02e3..55bec687d3e2c 100644
--- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts
+++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import type { estypes } from '@elastic/elasticsearch';
+
import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common';
import { AgentsRequestOptions } from '../../../../../../common/search_strategy';
// import { createQueryFilterClauses } from '../../../../../../common/utils/build_query';
@@ -24,10 +26,23 @@ export const buildActionsQuery = ({
body: {
// query: { bool: { filter } },
query: {
- term: {
- type: {
- value: 'INPUT_ACTION',
- },
+ bool: {
+ must: [
+ {
+ term: {
+ type: {
+ value: 'INPUT_ACTION',
+ },
+ },
+ },
+ {
+ term: {
+ input_type: {
+ value: 'osquery',
+ },
+ },
+ },
+ ] as estypes.QueryDslQueryContainer[],
},
},
from: cursorStart,
diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts
index b560fd3c364e9..406ff26991f0e 100644
--- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts
+++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts
@@ -47,12 +47,17 @@ export const buildResultsQuery = ({
size: 10000,
},
},
+ unique_agents: {
+ cardinality: {
+ field: 'elastic_agent.id',
+ },
+ },
},
query: { bool: { filter } },
from: activePage * querySize,
size: querySize,
track_total_hits: true,
- fields: agentId ? ['osquery.*'] : ['agent.*', 'osquery.*'],
+ fields: ['elastic_agent.*', 'agent.*', 'osquery.*'],
sort:
sort?.map((sortConfig) => ({
[sortConfig.field]: {
diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
index 9ce249aa32a1d..8007acad93e4b 100644
--- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
@@ -424,7 +424,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -480,7 +480,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -522,7 +521,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -1489,7 +1487,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -1531,7 +1528,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -2512,7 +2508,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -2554,7 +2549,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -3582,7 +3576,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -3624,7 +3617,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -4685,7 +4677,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -4727,7 +4718,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -5755,7 +5745,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -5797,7 +5786,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -6825,7 +6813,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -6867,7 +6854,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -7895,7 +7881,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -7937,7 +7922,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -8965,7 +8949,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
@@ -9007,7 +8990,6 @@ exports[`ReportListing Report job listing with some items 1`] = `
className="euiTableCellContent euiTableCellContent--overflowingContent"
>
{ILM_POLICY_NAME},
}}
@@ -33,7 +33,10 @@ const i18nTexts = {
buttonLabel: i18n.translate(
'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesButtonLabel',
{
- defaultMessage: 'Migrate indices',
+ defaultMessage: 'Apply {ilmPolicyName} policy',
+ values: {
+ ilmPolicyName: ILM_POLICY_NAME,
+ },
}
),
migrateErrorTitle: i18n.translate(
@@ -45,7 +48,7 @@ const i18nTexts = {
migrateSuccessTitle: i18n.translate(
'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesSuccessTitle',
{
- defaultMessage: 'Successfully migrated reporting indices',
+ defaultMessage: 'Reporting policy active for all reporting indices',
}
),
};
diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx
index 749e42de526d3..dd41314b4883f 100644
--- a/x-pack/plugins/reporting/public/management/report_listing.tsx
+++ b/x-pack/plugins/reporting/public/management/report_listing.tsx
@@ -151,6 +151,7 @@ class ReportListingUi extends Component {
return (
<>
@@ -375,7 +376,7 @@ class ReportListingUi extends Component {
}),
render: (objectTitle: string, record: Job) => {
return (
-
+