diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/columns.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/columns.tsx
index 94223e10f316f..56a97dd7c70f8 100644
--- a/x-pack/plugins/dataset_quality/public/components/dataset_quality/columns.tsx
+++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/columns.tsx
@@ -12,6 +12,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
+ EuiLink,
EuiSkeletonRectangle,
EuiText,
EuiToolTip,
@@ -28,6 +29,7 @@ import {
import { DataStreamStat } from '../../../common/data_streams_stats/data_stream_stat';
import loggingIcon from '../../icons/logging.svg';
import { QualityIndicator, QualityPercentageIndicator } from '../quality_indicator';
+import { useLinkToLogExplorer } from '../../hooks/use_link_to_log_explorer';
const nameColumnName = i18n.translate('xpack.datasetQuality.nameColumnName', {
defaultMessage: 'Dataset Name',
@@ -41,6 +43,17 @@ const degradedDocsColumnName = i18n.translate('xpack.datasetQuality.degradedDocs
defaultMessage: 'Degraded Docs',
});
+const lastActivityColumnName = i18n.translate('xpack.datasetQuality.lastActivityColumnName', {
+ defaultMessage: 'Last Activity',
+});
+
+const actionsColumnName = i18n.translate('xpack.datasetQuality.actionsColumnName', {
+ defaultMessage: 'Actions',
+});
+const openActionName = i18n.translate('xpack.datasetQuality.openActionName', {
+ defaultMessage: 'Open',
+});
+
const degradedDocsDescription = (minimimPercentage: number) =>
i18n.translate('xpack.datasetQuality.degradedDocsQualityDescription', {
defaultMessage: 'greater than {minimimPercentage}%',
@@ -83,10 +96,6 @@ const degradedDocsColumnTooltip = (
/>
);
-const lastActivityColumnName = i18n.translate('xpack.datasetQuality.lastActivityColumnName', {
- defaultMessage: 'Last Activity',
-});
-
export const getDatasetQualitTableColumns = ({
fieldFormats,
loadingDegradedStats,
@@ -164,5 +173,17 @@ export const getDatasetQualitTableColumns = ({
.convert(timestamp),
sortable: true,
},
+ {
+ name: actionsColumnName,
+ render: (dataStreamStat: DataStreamStat) => (
+
+ ),
+ width: '100px',
+ },
];
};
+
+const LinkToLogExplorer = ({ dataStreamStat }: { dataStreamStat: DataStreamStat }) => {
+ const url = useLinkToLogExplorer({ dataStreamStat });
+ return {openActionName};
+};
diff --git a/x-pack/plugins/dataset_quality/public/hooks/use_link_to_log_explorer.ts b/x-pack/plugins/dataset_quality/public/hooks/use_link_to_log_explorer.ts
new file mode 100644
index 0000000000000..5d4ef4d6021dd
--- /dev/null
+++ b/x-pack/plugins/dataset_quality/public/hooks/use_link_to_log_explorer.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 {
+ SingleDatasetLocatorParams,
+ SINGLE_DATASET_LOCATOR_ID,
+} from '@kbn/deeplinks-observability';
+import { useMemo } from 'react';
+import { DataStreamStat } from '../../common/data_streams_stats/data_stream_stat';
+import { useKibanaContextForPlugin } from '../utils';
+
+export const useLinkToLogExplorer = ({ dataStreamStat }: { dataStreamStat: DataStreamStat }) => {
+ const {
+ services: { share },
+ } = useKibanaContextForPlugin();
+ const [dataset, namespace] = dataStreamStat.title.split('-');
+ const integration = dataStreamStat.integration?.name;
+
+ const url = useMemo(() => {
+ return share.url.locators
+ .get(SINGLE_DATASET_LOCATOR_ID)
+ ?.getRedirectUrl({
+ dataset,
+ timeRange: {
+ from: 'now-1d',
+ to: 'now',
+ },
+ integration,
+ filterControls: {
+ namespace: {
+ mode: 'include',
+ values: [namespace],
+ },
+ },
+ });
+ }, [dataset, integration, namespace, share.url.locators]);
+
+ return url;
+};
diff --git a/x-pack/plugins/log_explorer/public/controller/public_state.ts b/x-pack/plugins/log_explorer/public/controller/public_state.ts
index 04c12160a8809..5f4aad892bcb9 100644
--- a/x-pack/plugins/log_explorer/public/controller/public_state.ts
+++ b/x-pack/plugins/log_explorer/public/controller/public_state.ts
@@ -15,11 +15,7 @@ import {
DEFAULT_CONTEXT,
LogExplorerControllerContext,
} from '../state_machines/log_explorer_controller';
-import {
- LogExplorerPublicState,
- LogExplorerPublicStateUpdate,
- OptionsListControlOption,
-} from './types';
+import { LogExplorerPublicState, LogExplorerPublicStateUpdate, OptionsListControl } from './types';
export const getPublicStateFromContext = (
context: LogExplorerControllerContext
@@ -80,7 +76,7 @@ const getPublicControlsStateFromControlPanels = (
const getOptionsListPublicControlStateFromControlPanel = (
optionsListControlPanel: ControlPanels[string]
-): OptionsListControlOption => ({
+): OptionsListControl => ({
mode: optionsListControlPanel.explicitInput.exclude ? 'exclude' : 'include',
selection: optionsListControlPanel.explicitInput.existsSelected
? { type: 'exists' }
@@ -113,7 +109,7 @@ const getControlPanelsFromPublicControlsState = (
const getControlPanelFromOptionsListPublicControlState = (
controlId: string,
- publicControlState: OptionsListControlOption
+ publicControlState: OptionsListControl
): ControlPanels[string] => {
const defaultControlPanelConfig = controlPanelConfigs[controlId];
diff --git a/x-pack/plugins/log_explorer/public/controller/types.ts b/x-pack/plugins/log_explorer/public/controller/types.ts
index 50eb259d38cb3..5947051ad2518 100644
--- a/x-pack/plugins/log_explorer/public/controller/types.ts
+++ b/x-pack/plugins/log_explorer/public/controller/types.ts
@@ -41,19 +41,21 @@ export type LogExplorerDiscoverServices = Pick<
};
export interface OptionsListControlOption {
+ type: 'options';
+ selectedOptions: string[];
+}
+
+export interface OptionsListControlExists {
+ type: 'exists';
+}
+
+export interface OptionsListControl {
mode: 'include' | 'exclude';
- selection:
- | {
- type: 'options';
- selectedOptions: string[];
- }
- | {
- type: 'exists';
- };
+ selection: OptionsListControlOption | OptionsListControlExists;
}
export interface ControlOptions {
- [availableControlsPanels.NAMESPACE]?: OptionsListControlOption;
+ [availableControlsPanels.NAMESPACE]?: OptionsListControl;
}
// we might want to wrap this into an object that has a "state value" laster
diff --git a/x-pack/plugins/log_explorer/public/index.ts b/x-pack/plugins/log_explorer/public/index.ts
index 005b5cca07a14..efd337234ffc6 100644
--- a/x-pack/plugins/log_explorer/public/index.ts
+++ b/x-pack/plugins/log_explorer/public/index.ts
@@ -21,6 +21,7 @@ export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types';
export {
getDiscoverColumnsFromDisplayOptions,
getDiscoverGridFromDisplayOptions,
+ getDiscoverFiltersFromState,
} from './utils/convert_discover_app_state';
export function plugin(context: PluginInitializerContext) {
diff --git a/x-pack/plugins/log_explorer/public/utils/convert_discover_app_state.ts b/x-pack/plugins/log_explorer/public/utils/convert_discover_app_state.ts
index dea02a0bec002..2bb3afee90e50 100644
--- a/x-pack/plugins/log_explorer/public/utils/convert_discover_app_state.ts
+++ b/x-pack/plugins/log_explorer/public/utils/convert_discover_app_state.ts
@@ -7,6 +7,8 @@
import { QueryState } from '@kbn/data-plugin/public';
import { DiscoverAppState } from '@kbn/discover-plugin/public';
+import { ExistsFilter, Filter, FILTERS, PhrasesFilter } from '@kbn/es-query';
+import { PhraseFilterValue } from '@kbn/es-query/src/filters/build_filters';
import { cloneDeep } from 'lodash';
import {
ChartDisplayOptions,
@@ -14,6 +16,7 @@ import {
GridColumnDisplayOptions,
GridRowsDisplayOptions,
} from '../../common';
+import { ControlOptions, OptionsListControlOption } from '../controller';
export const getGridColumnDisplayOptionsFromDiscoverAppState = (
discoverAppState: DiscoverAppState
@@ -71,3 +74,60 @@ export const getDiscoverGridFromDisplayOptions = (
return gridColumns;
}, {}),
});
+
+const createDiscoverPhrasesFilter = ({
+ key,
+ values,
+ negate,
+}: {
+ values: PhraseFilterValue[];
+ key: string;
+ negate?: boolean;
+}): PhrasesFilter =>
+ ({
+ meta: {
+ key,
+ type: FILTERS.PHRASES,
+ params: values,
+ },
+ query: {
+ bool: {
+ should: values.map((value) => ({ match_phrase: { [key]: value.toString() } })),
+ minimum_should_match: 1,
+ },
+ },
+ } as PhrasesFilter);
+
+const createDiscoverExistsFilter = ({
+ key,
+ negate,
+}: {
+ key: string;
+ negate?: boolean;
+}): ExistsFilter => ({
+ meta: {
+ key,
+ negate,
+ type: FILTERS.EXISTS,
+ },
+ query: { exists: { field: key } },
+});
+
+export const getDiscoverFiltersFromState = (filters: Filter[] = [], controls?: ControlOptions) => [
+ ...filters,
+ ...(controls
+ ? Object.keys(controls).map((key) =>
+ controls[key as keyof ControlOptions]?.selection.type === 'exists'
+ ? createDiscoverExistsFilter({
+ key,
+ negate: controls[key as keyof ControlOptions]?.mode === 'exclude',
+ })
+ : createDiscoverPhrasesFilter({
+ key,
+ values: (controls[key as keyof ControlOptions]?.selection as OptionsListControlOption)
+ .selectedOptions,
+ negate: controls[key as keyof ControlOptions]?.mode === 'exclude',
+ })
+ )
+ : []),
+];
diff --git a/x-pack/plugins/observability_log_explorer/public/components/discover_link.tsx b/x-pack/plugins/observability_log_explorer/public/components/discover_link.tsx
index 2d12de11731c0..2d6f658394951 100644
--- a/x-pack/plugins/observability_log_explorer/public/components/discover_link.tsx
+++ b/x-pack/plugins/observability_log_explorer/public/components/discover_link.tsx
@@ -9,7 +9,10 @@ import { EuiHeaderLink } from '@elastic/eui';
import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import { hydrateDatasetSelection } from '@kbn/log-explorer-plugin/common';
-import { getDiscoverColumnsFromDisplayOptions } from '@kbn/log-explorer-plugin/public';
+import {
+ getDiscoverColumnsFromDisplayOptions,
+ getDiscoverFiltersFromState,
+} from '@kbn/log-explorer-plugin/public';
import { MatchedStateFromActor } from '@kbn/xstate-utils';
import { useActor } from '@xstate/react';
import React, { useMemo } from 'react';
@@ -54,7 +57,7 @@ export const DiscoverLinkForValidState = React.memo(
() => ({
breakdownField: logExplorerState.chart.breakdownField ?? undefined,
columns: getDiscoverColumnsFromDisplayOptions(logExplorerState),
- filters: logExplorerState.filters,
+ filters: getDiscoverFiltersFromState(logExplorerState.filters, logExplorerState.controls),
query: logExplorerState.query,
refreshInterval: logExplorerState.refreshInterval,
timeRange: logExplorerState.time,