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,