[Dataset Quality] Added Dataset Quality Locator (#177000)

closes https://github.com/elastic/kibana/issues/170611

## 📝  Summary

This PR adds the infrastructure work for the locators needed to create
the navigation link from the Logs Explorer to the Dataset Quality Page,
but the links themselves are to be added with a later ticket.

## 💡For Reviewers

To be abled to test this PR you can add the below code
[here](https://github.com/elastic/kibana/blob/main/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx#L150)
to make the link visible in the Logs Explorer Page.

`<ConnectedDatasetQualityLink />
  <VerticalRule />`

## 🎥 Demo



1f3ce10a-3b8c-4027-b72d-1ed71b782fa5
This commit is contained in:
mohamedhamed-ahmed 2024-02-22 13:28:17 +02:00 committed by GitHub
parent 03e8e9c5f0
commit 9a473879af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 283 additions and 4 deletions

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SerializableRecord } from '@kbn/utility-types';
export const DATASET_QUALITY_LOCATOR_ID = 'DATASET_QUALITY_LOCATOR';
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type RefreshInterval = {
isPaused: boolean;
interval: number;
};
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type TimeRangeConfig = {
from: string;
to: string;
refresh: RefreshInterval;
};
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type Filters = {
timeRange: TimeRangeConfig;
};
export interface DatasetQualityLocatorParams extends SerializableRecord {
filters?: Filters;
}

View file

@ -9,3 +9,4 @@
export * from './logs_explorer';
export * from './observability_logs_explorer';
export * from './observability_onboarding';
export * from './dataset_quality';

View file

@ -31,8 +31,10 @@ import { mapPercentagesToQualityCounts } from '../../quality_indicator';
import { InfoIndicators } from '../../common';
export function DatasetsQualityIndicators() {
const { datasetsQuality, isDatasetsQualityLoading } = useSummaryPanelContext();
const { datasetsQuality, isDatasetsQualityLoading, datasetsActivity } = useSummaryPanelContext();
const qualityCounts = mapPercentagesToQualityCounts(datasetsQuality.percentages);
const datasetsWithoutIgnoredField =
datasetsActivity.total > 0 ? datasetsActivity.total - datasetsQuality.percentages.length : 0;
return (
<EuiPanel hasBorder>
@ -61,7 +63,7 @@ export function DatasetsQualityIndicators() {
/>
<span css={verticalRule} />
<QualityIndicator
value={qualityCounts.good}
value={qualityCounts.good + datasetsWithoutIgnoredField}
quality="success"
description={summaryPanelQualityGoodText}
isLoading={isDatasetsQualityLoading}

View file

@ -34,7 +34,9 @@ const useSummaryPanel = ({ dataStreamStatsClient, toasts }: SummaryPanelContextD
const isDatasetsQualityLoading = useSelector(
summaryPanelStateService,
(state) =>
state.matches('datasetsQuality.fetching') || state.matches('datasetsQuality.retrying')
state.matches('datasetsQuality.fetching') ||
state.matches('datasetsQuality.retrying') ||
state.matches('datasetsActivity.fetching')
);
/*

View file

@ -0,0 +1,29 @@
/*
* 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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
import {
DatasetQualityLocatorParams,
DATASET_QUALITY_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { DatasetQualityLocatorDependencies } from './types';
import { constructDatasetQualityLocatorPath } from './utils';
export type DatasetQualityLocator = LocatorPublic<DatasetQualityLocatorParams>;
export class DatasetQualityLocatorDefinition
implements LocatorDefinition<DatasetQualityLocatorParams>
{
public readonly id = DATASET_QUALITY_LOCATOR_ID;
constructor(protected readonly deps: DatasetQualityLocatorDependencies) {}
public readonly getLocation = async (params: DatasetQualityLocatorParams) => {
const { useHash } = this.deps;
return constructDatasetQualityLocatorPath({ useHash, locatorParams: params });
};
}

View file

@ -9,7 +9,9 @@ export type { ObservabilityLogsExplorerLocationState } from '@kbn/deeplinks-obse
import { AllDatasetsLocator } from './all_datasets_locator';
import { DataViewLocator } from './data_view_locator';
import { SingleDatasetLocator } from './single_dataset_locator';
import { DatasetQualityLocator } from './dataset_quality_locator';
export * from './dataset_quality_locator';
export * from './single_dataset_locator';
export * from './all_datasets_locator';
export * from './utils';
@ -18,4 +20,5 @@ export interface ObservabilityLogsExplorerLocators {
allDatasetsLocator: AllDatasetsLocator;
dataViewLocator: DataViewLocator;
singleDatasetLocator: SingleDatasetLocator;
datasetQualityLocator: DatasetQualityLocator;
}

View file

@ -8,9 +8,10 @@
import { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability';
import {
AllDatasetsLocatorParams,
ObsLogsExplorerDataViewLocatorParams,
SingleDatasetLocatorParams,
ObsLogsExplorerDataViewLocatorParams,
} from '@kbn/deeplinks-observability/locators';
import { DatasetQualityLocatorDefinition } from './dataset_quality_locator';
import { AllDatasetsLocatorDefinition } from './all_datasets_locator';
import { DataViewLocatorDefinition } from './data_view_locator';
import { SingleDatasetLocatorDefinition } from './single_dataset_locator';
@ -23,11 +24,13 @@ const setup = async () => {
const allDatasetsLocator = new AllDatasetsLocatorDefinition(dep);
const dataViewLocator = new DataViewLocatorDefinition(dep);
const singleDatasetLocator = new SingleDatasetLocatorDefinition(dep);
const datasetQualityLocator = new DatasetQualityLocatorDefinition(dep);
return {
allDatasetsLocator,
dataViewLocator,
singleDatasetLocator,
datasetQualityLocator,
};
};
@ -375,4 +378,41 @@ describe('Observability Logs Explorer Locators', () => {
);
});
});
describe('Dataset Quality Locator', () => {
it('should create a link with correct path and no state', async () => {
const { datasetQualityLocator } = await setup();
const location = await datasetQualityLocator.getLocation({});
expect(location).toMatchObject({
app: OBSERVABILITY_LOGS_EXPLORER_APP_ID,
path: '/dataset-quality?pageState=(v:1)',
state: {},
});
});
it('should create a link with correct timeRange', async () => {
const refresh = {
isPaused: false,
interval: 0,
};
const locatorParams = {
filters: {
timeRange: {
...timeRange,
refresh,
},
},
};
const { datasetQualityLocator } = await setup();
const location = await datasetQualityLocator.getLocation(locatorParams);
expect(location).toMatchObject({
app: OBSERVABILITY_LOGS_EXPLORER_APP_ID,
path: '/dataset-quality?pageState=(filters:(timeRange:(from:now-30m,refresh:(interval:0,isPaused:!f),to:now)),v:1)',
state: {},
});
});
});
});

View file

@ -7,3 +7,7 @@
export interface ObsLogsExplorerLocatorDependencies {
useHash: boolean;
}
export interface DatasetQualityLocatorDependencies {
useHash: boolean;
}

View file

@ -0,0 +1,47 @@
/*
* 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 { DatasetQualityLocatorParams } from '@kbn/deeplinks-observability/locators';
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common';
import { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability';
import {
OBSERVABILITY_DATASET_QUALITY_URL_STATE_KEY,
datasetQualityUrlSchemaV1,
} from '../../url_schema';
import { deepCompactObject } from '../../utils/deep_compact_object';
interface LocatorPathConstructionParams {
locatorParams: DatasetQualityLocatorParams;
useHash: boolean;
}
export const constructDatasetQualityLocatorPath = async (params: LocatorPathConstructionParams) => {
const {
locatorParams: { filters },
useHash,
} = params;
const pageState = datasetQualityUrlSchemaV1.urlSchemaRT.encode(
deepCompactObject({
v: 1,
filters,
})
);
const path = setStateToKbnUrl(
OBSERVABILITY_DATASET_QUALITY_URL_STATE_KEY,
pageState,
{ useHash, storeInHashQuery: false },
'/dataset-quality'
);
return {
app: OBSERVABILITY_LOGS_EXPLORER_APP_ID,
path,
state: {},
};
};

View file

@ -6,3 +6,4 @@
*/
export * from './construct_locator_path';
export * from './construct_dataset_quality_locator_path';

View file

@ -53,3 +53,10 @@ export const feedbackLinkTitle = i18n.translate(
defaultMessage: 'Give feedback',
}
);
export const datasetQualityLinkTitle = i18n.translate(
'xpack.observabilityLogsExplorer.datasetQualityLinkTitle',
{
defaultMessage: 'Datasets',
}
);

View file

@ -0,0 +1,102 @@
/*
* 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 { EuiHeaderLink } from '@elastic/eui';
import {
DatasetQualityLocatorParams,
DATASET_QUALITY_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { LogsExplorerPublicState } from '@kbn/logs-explorer-plugin/public';
import { getRouterLinkProps } from '@kbn/router-utils';
import { BrowserUrlService } from '@kbn/share-plugin/public';
import { MatchedStateFromActor } from '@kbn/xstate-utils';
import { useActor } from '@xstate/react';
import React from 'react';
import { datasetQualityLinkTitle } from '../../common/translations';
import {
ObservabilityLogsExplorerService,
useObservabilityLogsExplorerPageStateContext,
} from '../state_machines/observability_logs_explorer/src';
import { useKibanaContextForPlugin } from '../utils/use_kibana';
export const ConnectedDatasetQualityLink = React.memo(() => {
const {
services: {
share: { url },
},
} = useKibanaContextForPlugin();
const [pageState] = useActor(useObservabilityLogsExplorerPageStateContext());
if (pageState.matches({ initialized: 'validLogsExplorerState' })) {
return <DatasetQualityLink urlService={url} pageState={pageState} />;
} else {
return <DatasetQualityLink urlService={url} />;
}
});
type InitializedPageState = MatchedStateFromActor<
ObservabilityLogsExplorerService,
{ initialized: 'validLogsExplorerState' }
>;
const constructLocatorParams = (
logsExplorerState: LogsExplorerPublicState
): DatasetQualityLocatorParams => {
const { time, refreshInterval } = logsExplorerState;
const locatorParams: DatasetQualityLocatorParams = {
filters: {
timeRange: {
from: time?.from || 'now-24h',
to: time?.to || 'now',
refresh: {
isPaused: refreshInterval ? refreshInterval.pause : false,
interval: refreshInterval ? refreshInterval.value : 60000,
},
},
},
};
return locatorParams;
};
export const DatasetQualityLink = React.memo(
({
urlService,
pageState,
}: {
urlService: BrowserUrlService;
pageState?: InitializedPageState;
}) => {
const locator = urlService.locators.get<DatasetQualityLocatorParams>(
DATASET_QUALITY_LOCATOR_ID
);
const locatorParams: DatasetQualityLocatorParams = pageState
? constructLocatorParams(pageState.context.logsExplorerState)
: {};
const datasetQualityUrl = locator?.useUrl(locatorParams);
const navigateToDatasetQuality = () => {
locator?.navigate(locatorParams);
};
const datasetQualityLinkProps = getRouterLinkProps({
href: datasetQualityUrl,
onClick: navigateToDatasetQuality,
});
return (
<EuiHeaderLink
{...datasetQualityLinkProps}
color="primary"
data-test-subj="logsExplorerDatasetQualityLink"
>
{datasetQualityLinkTitle}
</EuiHeaderLink>
);
}
);

View file

@ -16,6 +16,7 @@ import {
import { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability';
import {
AllDatasetsLocatorDefinition,
DatasetQualityLocatorDefinition,
ObservabilityLogsExplorerLocators,
SingleDatasetLocatorDefinition,
} from '../common/locators';
@ -95,6 +96,12 @@ export class ObservabilityLogsExplorerPlugin
useHash,
})
);
const datasetQualityLocator = share.url.locators.create(
new DatasetQualityLocatorDefinition({
useHash,
})
);
const dataViewLocator = share.url.locators.create(
new DataViewLocatorDefinition({
useHash,
@ -108,6 +115,7 @@ export class ObservabilityLogsExplorerPlugin
this.locators = {
allDatasetsLocator,
datasetQualityLocator,
dataViewLocator,
singleDatasetLocator,
};