diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 4547d5cae5b62..5b62ca1dc9dee 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -15477,7 +15477,6 @@ "xpack.datasetQuality.details.updateFieldLimitFailed": "Nous n'avons pas pu mettre à jour la limite du champ.", "xpack.datasetQuality.details.viewDashboardsActionText": "Afficher les tableaux de bord", "xpack.datasetQuality.editPipeline.strong.Label": "2.", - "xpack.datasetQuality.emptyState.noPrivileges.message": "Vous ne disposez pas des autorisations requises pour voir les données de logs. Assurez-vous d'avoir les autorisations requises pour voir {datasetPattern}.", "xpack.datasetQuality.emptyState.noPrivileges.title": "Impossible de charger les ensembles de données", "xpack.datasetQuality.failedDocsColumnName": "Documents échoués (%)", "xpack.datasetQuality.failedDocsColumnTooltip": "Le pourcentage de documents envoyés au stockage d'échec à cause d'un problème durant l'ingestion.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index efa648c1d4fae..75bb62d5e5946 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -15495,7 +15495,6 @@ "xpack.datasetQuality.details.updateFieldLimitFailed": "フィールド上限を更新できませんでした。", "xpack.datasetQuality.details.viewDashboardsActionText": "ダッシュボードを表示", "xpack.datasetQuality.editPipeline.strong.Label": "2.", - "xpack.datasetQuality.emptyState.noPrivileges.message": "ログデータを表示するために必要な権限がありません。{datasetPattern}を表示するための十分な権限があることを確認してください。", "xpack.datasetQuality.emptyState.noPrivileges.title": "データセットを読み込めませんでした", "xpack.datasetQuality.failedDocsColumnName": "失敗したドキュメント(%)", "xpack.datasetQuality.failedDocsColumnTooltip": "インジェスト時の問題により失敗ストアに送信されたドキュメントの割合。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 6722b27d14ea4..427c147a7dc11 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -15491,7 +15491,6 @@ "xpack.datasetQuality.details.updateFieldLimitFailed": "无法更新字段限制。", "xpack.datasetQuality.details.viewDashboardsActionText": "查看仪表板", "xpack.datasetQuality.editPipeline.strong.Label": "2.", - "xpack.datasetQuality.emptyState.noPrivileges.message": "您没有查看日志数据所需的权限。请确保您具有足够的权限,可以查看 {datasetPattern}。", "xpack.datasetQuality.emptyState.noPrivileges.title": "无法加载数据集", "xpack.datasetQuality.failedDocsColumnName": "失败的文档 (%)", "xpack.datasetQuality.failedDocsColumnTooltip": "集成期间由于出现问题发送到失败存储的文档百分比。", diff --git a/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts b/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts index 11e9bffed0667..ecacf24059a09 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts @@ -12,15 +12,29 @@ const userPrivilegesRt = rt.type({ canReadFailureStore: rt.boolean, }); -const datasetUserPrivilegesRt = rt.intersection([ - userPrivilegesRt, - rt.type({ - canRead: rt.boolean, - canViewIntegrations: rt.boolean, - }), -]); +const datasetPrivilegeRt = rt.record( + rt.string, + rt.intersection([ + userPrivilegesRt, + rt.type({ + canRead: rt.boolean, + }), + ]) +); + +const datasetUserPrivilegesRt = rt.type({ + datasetsPrivilages: datasetPrivilegeRt, + canViewIntegrations: rt.boolean, +}); export type DatasetUserPrivileges = rt.TypeOf; +export type DatasetTypesPrivileges = rt.TypeOf; + +export const getDataStreamsTypesPrivilegesResponseRt = rt.exact( + rt.type({ + datasetTypesPrivileges: datasetPrivilegeRt, + }) +); export const dataStreamStatRt = rt.intersection([ rt.type({ diff --git a/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/types.ts b/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/types.ts index bbc017e846b50..fd791504393a5 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/types.ts @@ -7,6 +7,12 @@ import { APIClientRequestParamsOf, APIReturnType } from '../rest'; +export type GetDataStreamsTypesPrivilegesParams = + APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/types_privileges`>['params']; +export type GetDataStreamsTypesPrivilegesQuery = GetDataStreamsTypesPrivilegesParams['query']; +export type GetDataStreamsTypesPrivilegesResponse = + APIReturnType<`GET /internal/dataset_quality/data_streams/types_privileges`>; + export type GetDataStreamsStatsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/stats`>['params']; export type GetDataStreamsStatsQuery = GetDataStreamsStatsParams['query']; diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx index 1fabd455ec9c2..28e1003d8d92b 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx @@ -6,19 +6,18 @@ */ import React from 'react'; -import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DEFAULT_LOGS_DATA_VIEW } from '../../../../common/constants'; -import { useEmptyState } from '../../../hooks/use_empty_state'; +import { useDatasetQualityState } from '../../../hooks/use_dataset_quality_state'; // Allow for lazy loading // eslint-disable-next-line import/no-default-export export default function EmptyStateWrapper({ children }: { children: React.ReactNode }) { - const { canReadDataset } = useEmptyState(); + const { canUserMonitorAnyDataset, statsLoading } = useDatasetQualityState(); - if (!canReadDataset) { + if (!statsLoading && !canUserMonitorAnyDataset) { return ( {DEFAULT_LOGS_DATA_VIEW}, - }} + defaultMessage="You don't have the required privileges to view data sets data. Make sure you have sufficient privileges to view data sets." /> {/* TODO: Learn more link to docs */}

diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/filters/filters.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/filters/filters.tsx index 1114cb75fe5b2..1507f937562db 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/filters/filters.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/filters/filters.tsx @@ -70,9 +70,9 @@ export default function Filters() { integrations={integrations} onIntegrationsChange={onIntegrationsChange} /> - {isDatasetQualityAllSignalsAvailable && ( + {isDatasetQualityAllSignalsAvailable && types.length > 1 && ( (null); const { isAlertingAvailable } = getAlertingCapabilities(alerting, capabilities); - const { isDatasetQualityAllSignalsAvailable } = useDatasetQualityFilters(); + const { isDatasetQualityAllSignalsAvailable, authorizedDatasetTypes } = + useDatasetQualityFilters(); const validTypes = useMemo( - () => (isDatasetQualityAllSignalsAvailable ? KNOWN_TYPES : [DEFAULT_DATASET_TYPE]), - [isDatasetQualityAllSignalsAvailable] + () => (isDatasetQualityAllSignalsAvailable ? authorizedDatasetTypes : [DEFAULT_DATASET_TYPE]), + [isDatasetQualityAllSignalsAvailable, authorizedDatasetTypes] ); return ( @@ -79,7 +79,7 @@ export default function Header() { /> } rightSideItems={ - isAlertingAvailable + isAlertingAvailable && validTypes.length ? [ <> { resultsCount, showInactiveDatasets, showFullDatasetNames, - canUserMonitorDataset, + canUserMonitorAnyDataset, canUserMonitorAnyDataStream, toggleInactiveDatasets, toggleFullDatasetNames, @@ -63,7 +63,7 @@ export const Table = () => { tooltipText={fullDatasetNameDescription} onToggle={toggleFullDatasetNames} /> - {canUserMonitorDataset && canUserMonitorAnyDataStream && ( + {canUserMonitorAnyDataset && canUserMonitorAnyDataStream && ( // eslint-disable-next-line import/no-default-export export default function Warnings() { const { loading, nonAggregatableDatasets } = useDatasetQualityWarnings(); - const { statsLoading, canUserReadFailureStore } = useDatasetQualityState(); + const { statsLoading, canUserReadFailureStore, canUserReadAnyDataset, canUserMonitorAnyDataset } = + useDatasetQualityState(); + + const canAccessAnyDataset = canUserReadAnyDataset || canUserMonitorAnyDataset; return ( @@ -95,7 +98,7 @@ export default function Warnings() { )} - {!statsLoading && !canUserReadFailureStore && ( + {!statsLoading && !canUserReadFailureStore && canAccessAnyDataset && ( diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_details_state.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_details_state.ts index 6b41837e9e3c4..78af87d99f2f1 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_details_state.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_details_state.ts @@ -100,7 +100,7 @@ export const useDatasetQualityDetailsState = () => { ); const canUserReadFailureStore = Boolean( - dataStreamSettings?.datasetUserPrivileges?.canReadFailureStore + dataStreamSettings?.datasetUserPrivileges?.datasetsPrivilages?.[dataStream]?.canReadFailureStore ); const dataStreamDetails = useSelector(service, (state) => diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_filters.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_filters.ts index 6c324bd5f8103..c90cfd3a2ef75 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_filters.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_filters.ts @@ -8,7 +8,7 @@ import { OnRefreshChangeProps } from '@elastic/eui'; import { useSelector } from '@xstate/react'; import { useCallback, useMemo } from 'react'; -import { DEFAULT_DATASET_TYPE, KNOWN_TYPES } from '../../common/constants'; +import { DEFAULT_DATASET_TYPE } from '../../common/constants'; import { DataStreamType, QualityIndicators } from '../../common/types'; import { Integration } from '../../common/data_streams_stats/integration'; import { useDatasetQualityContext } from '../components/dataset_quality/context'; @@ -23,8 +23,14 @@ export const useDatasetQualityFilters = () => { const isLoading = useSelector( service, (state) => - state.matches('integrations.fetching') && - (state.matches('stats.datasets.fetching') || state.matches('stats.degradedDocs.fetching')) + state.matches('initializing') || + (state.matches('main.integrations.fetching') && + (state.matches('main.stats.datasets.fetching') || + state.matches('main.stats.degradedDocs.fetching'))) + ); + + const authorizedDatasetTypes = useSelector(service, (state) => + !state.matches('initializing') ? state.context.authorizedDatasetTypes : [] ); const { @@ -174,14 +180,14 @@ export const useDatasetQualityFilters = () => { const typeItems: Item[] = useMemo(() => { const validTypeItems = isDatasetQualityAllSignalsAvailable - ? KNOWN_TYPES + ? authorizedDatasetTypes : [DEFAULT_DATASET_TYPE]; return validTypeItems.map((type) => ({ label: type, checked: selectedTypes.includes(type) ? 'on' : undefined, })); - }, [isDatasetQualityAllSignalsAvailable, selectedTypes]); + }, [authorizedDatasetTypes, isDatasetQualityAllSignalsAvailable, selectedTypes]); const onTypesChange = useCallback( (newTypeItems: Item[]) => { @@ -214,6 +220,7 @@ export const useDatasetQualityFilters = () => { namespaces: namespaceItems, qualities: qualityItems, types: typeItems, + authorizedDatasetTypes, onIntegrationsChange, onNamespacesChange, onQualitiesChange, diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_state.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_state.ts index 964dcfffab79d..6762e6b35f71c 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_state.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_state.ts @@ -11,14 +11,34 @@ import { useDatasetQualityContext } from '../components/dataset_quality/context' export const useDatasetQualityState = () => { const { service } = useDatasetQualityContext(); - const { datasetUserPrivileges } = useSelector(service, (state) => state.context) ?? {}; + const { datasetUserPrivileges, dataStreamStats } = + useSelector(service, (state) => state.context) ?? {}; - const statsLoading = useSelector(service, (state) => state.matches('stats.datasets.fetching')); + const statsLoading = useSelector( + service, + (state) => state.matches('initializing') || state.matches('main.stats.datasets.fetching') + ); - const canUserReadFailureStore = Boolean(datasetUserPrivileges?.canReadFailureStore); + const canUserReadFailureStore = Boolean( + dataStreamStats?.some((ds) => ds.userPrivileges.canReadFailureStore) + ); + + const canUserMonitorAnyDataset = Boolean( + Object.values(datasetUserPrivileges?.datasetsPrivilages ?? {})?.some( + (privilege) => privilege.canMonitor + ) + ); + + const canUserReadAnyDataset = Boolean( + Object.values(datasetUserPrivileges?.datasetsPrivilages ?? {})?.some( + (privilege) => privilege.canRead + ) + ); return { statsLoading, canUserReadFailureStore, + canUserMonitorAnyDataset, + canUserReadAnyDataset, }; }; diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx index a65182a692482..ff0e34c0cc86b 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx @@ -38,14 +38,11 @@ export const useDatasetQualityTable = () => { } = useKibanaContextForPlugin(); const { service } = useDatasetQualityContext(); - const { canUserReadFailureStore: canReadFailureStore } = useDatasetQualityState(); + const { canUserReadFailureStore: canReadFailureStore, canUserMonitorAnyDataset } = + useDatasetQualityState(); const { page, rowsPerPage, sort } = useSelector(service, (state) => state.context.table); - const canUserMonitorDataset = useSelector( - service, - (state) => state.context.datasetUserPrivileges.canMonitor - ); const canUserMonitorAnyDataStream = useSelector( service, (state) => @@ -62,28 +59,30 @@ export const useDatasetQualityTable = () => { qualities, query, } = useSelector(service, (state) => state.context.filters); - const showInactiveDatasets = inactive || !canUserMonitorDataset; + + const showInactiveDatasets = inactive || !canUserMonitorAnyDataset; const loading = useSelector( service, (state) => - state.matches('stats.datasets.fetching') || - state.matches('stats.docsStats.fetching') || - state.matches('integrations.fetching') || - state.matches('stats.degradedDocs.fetching') || - state.matches('stats.failedDocs.fetching') + state.matches('initializing') || + state.matches('main.stats.datasets.fetching') || + state.matches('main.stats.docsStats.fetching') || + state.matches('main.integrations.fetching') || + state.matches('main.stats.degradedDocs.fetching') || + state.matches('main.stats.failedDocs.fetching') ); const loadingDataStreamStats = useSelector(service, (state) => - state.matches('stats.datasets.fetching') + state.matches('main.stats.datasets.fetching') ); const loadingDocStats = useSelector(service, (state) => - state.matches('stats.docsStats.fetching') + state.matches('main.stats.docsStats.fetching') ); const loadingDegradedStats = useSelector(service, (state) => - state.matches('stats.degradedDocs.fetching') + state.matches('main.stats.degradedDocs.fetching') ); const loadingFailedStats = useSelector(service, (state) => - state.matches('stats.failedDocs.fetching') + state.matches('main.stats.failedDocs.fetching') ); const datasets = useSelector(service, (state) => state.context.datasets); @@ -107,7 +106,7 @@ export const useDatasetQualityTable = () => { () => getDatasetQualityTableColumns({ fieldFormats, - canUserMonitorDataset, + canUserMonitorAnyDataset, canUserMonitorAnyDataStream, loadingDataStreamStats, loadingDocStats, @@ -121,7 +120,7 @@ export const useDatasetQualityTable = () => { }), [ fieldFormats, - canUserMonitorDataset, + canUserMonitorAnyDataset, canUserMonitorAnyDataStream, loadingDataStreamStats, loadingDocStats, @@ -220,7 +219,7 @@ export const useDatasetQualityTable = () => { resultsCount, showInactiveDatasets, showFullDatasetNames, - canUserMonitorDataset, + canUserMonitorAnyDataset, canUserMonitorAnyDataStream, toggleInactiveDatasets, toggleFullDatasetNames, diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_warnings.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_warnings.ts index f5ba4d01f8983..720b7b8b3bead 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_warnings.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_warnings.ts @@ -16,7 +16,7 @@ export function useDatasetQualityWarnings() { ); const isNonAggregatableDatasetsLoading = useSelector(service, (state) => - state.matches('stats.nonAggregatableDatasets.fetching') + state.matches('main.stats.nonAggregatableDatasets.fetching') ); return { diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_empty_state.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_empty_state.ts deleted file mode 100644 index 89ef4b884095d..0000000000000 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_empty_state.ts +++ /dev/null @@ -1,18 +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 { useSelector } from '@xstate/react'; -import { useDatasetQualityContext } from '../components/dataset_quality/context'; - -export function useEmptyState() { - const { service } = useDatasetQualityContext(); - - const canReadDataset = useSelector( - service, - (state) => state.context.datasetUserPrivileges.canRead - ); - return { canReadDataset }; -} diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_summary_panel.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_summary_panel.ts index 8207201175f5f..acde6a08fe261 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_summary_panel.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_summary_panel.ts @@ -18,7 +18,7 @@ const useSummaryPanel = () => { const { service } = useDatasetQualityContext(); const { filteredItems, - canUserMonitorDataset, + canUserMonitorAnyDataset, canUserMonitorAnyDataStream, loading: isTableLoading, } = useDatasetQualityTable(); @@ -35,7 +35,7 @@ const useSummaryPanel = () => { >; const isDegradedDocsLoading = useSelector(service, (state) => - state.matches('stats.degradedDocs.fetching') + state.matches('main.stats.degradedDocs.fetching') ); const isDatasetsQualityLoading = isDegradedDocsLoading || isTableLoading; @@ -47,7 +47,9 @@ const useSummaryPanel = () => { ); const isUserAuthorizedForDataset = !isTableLoading - ? canUserMonitorDataset && canUserMonitorAnyDataStream && canUserMonitorAllFilteredDataStreams + ? canUserMonitorAnyDataset && + canUserMonitorAnyDataStream && + canUserMonitorAllFilteredDataStreams : true; /* @@ -62,7 +64,7 @@ const useSummaryPanel = () => { }; const isDatasetsActivityLoading = useSelector(service, (state) => - state.matches('stats.datasets.fetching') + state.matches('main.stats.datasets.fetching') ); /* @@ -76,7 +78,8 @@ const useSummaryPanel = () => { const isEstimatedDataLoading = useSelector( service, (state) => - state.matches('stats.datasets.fetching') || state.matches('stats.degradedDocs.fetching') + state.matches('main.stats.datasets.fetching') || + state.matches('main.stats.degradedDocs.fetching') ); return { diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index f389bbe80c8b2..6c1b20bf3a78e 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -15,6 +15,7 @@ import { getDataStreamDegradedDocsResponseRt, getDataStreamFailedDocsResponseRt, getDataStreamsStatsResponseRt, + getDataStreamsTypesPrivilegesResponseRt, getDataStreamTotalDocsResponseRt, getIntegrationsResponseRt, getNonAggregatableDatasetsRt, @@ -29,6 +30,8 @@ import { GetDataStreamsStatsQuery, GetDataStreamsStatsResponse, GetDataStreamsTotalDocsQuery, + GetDataStreamsTypesPrivilegesQuery, + GetDataStreamsTypesPrivilegesResponse, GetNonAggregatableDataStreamsParams, } from '../../../common/data_streams_stats'; import { Integration } from '../../../common/data_streams_stats/integration'; @@ -38,6 +41,40 @@ import { IDataStreamsStatsClient } from './types'; export class DataStreamsStatsClient implements IDataStreamsStatsClient { constructor(private readonly http: HttpStart) {} + public async getDataStreamsTypesPrivileges( + params: GetDataStreamsTypesPrivilegesQuery + ): Promise { + const types = + 'types' in params + ? rison.encodeArray(params.types.length === 0 ? KNOWN_TYPES : params.types) + : undefined; + + const response = await this.http + .get( + '/internal/dataset_quality/data_streams/types_privileges', + { + query: { + ...params, + types, + }, + } + ) + .catch((error) => { + throw new DatasetQualityError( + `Failed to fetch data streams types privileges: ${error}`, + error + ); + }); + + const { datasetTypesPrivileges } = decodeOrThrow( + getDataStreamsTypesPrivilegesResponseRt, + (message: string) => + new DatasetQualityError(`Failed to decode data streams types privileges: ${message}`) + )(response); + + return { datasetTypesPrivileges }; + } + public async getDataStreamsStats( params: GetDataStreamsStatsQuery ): Promise { @@ -88,12 +125,11 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { } public async getDataStreamsDegradedStats(params: GetDataStreamsDegradedDocsStatsQuery) { - const types = params.types.length === 0 ? KNOWN_TYPES : params.types; const response = await this.http .get('/internal/dataset_quality/data_streams/degraded_docs', { query: { ...params, - types: rison.encodeArray(types), + types: rison.encodeArray(params.types), }, }) .catch((error) => { @@ -115,12 +151,11 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { } public async getDataStreamsFailedStats(params: GetDataStreamsFailedDocsStatsQuery) { - const types = params.types.length === 0 ? KNOWN_TYPES : params.types; const response = await this.http .get('/internal/dataset_quality/data_streams/failed_docs', { query: { ...params, - types: rison.encodeArray(types), + types: rison.encodeArray(params.types), }, }) .catch((error) => { @@ -139,12 +174,11 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { } public async getNonAggregatableDatasets(params: GetNonAggregatableDataStreamsParams) { - const types = params.types.length === 0 ? KNOWN_TYPES : params.types; const response = await this.http .get('/internal/dataset_quality/data_streams/non_aggregatable', { query: { ...params, - types: rison.encodeArray(types), + types: rison.encodeArray(params.types), }, }) .catch((error) => { diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts index 4e9c4c31c3927..f8194f9e4313f 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts @@ -13,6 +13,8 @@ import { GetDataStreamsFailedDocsStatsQuery, GetDataStreamsStatsQuery, GetDataStreamsTotalDocsQuery, + GetDataStreamsTypesPrivilegesQuery, + GetDataStreamsTypesPrivilegesResponse, GetNonAggregatableDataStreamsParams, } from '../../../common/data_streams_stats'; import { Integration } from '../../../common/data_streams_stats/integration'; @@ -28,6 +30,9 @@ export interface DataStreamsStatsServiceStartDeps { } export interface IDataStreamsStatsClient { + getDataStreamsTypesPrivileges( + params: GetDataStreamsTypesPrivilegesQuery + ): Promise; getDataStreamsStats(params?: GetDataStreamsStatsQuery): Promise; getDataStreamsDegradedStats( params?: GetDataStreamsDegradedDocsStatsQuery diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts index 7e39673307abc..34c1be9ac1062 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts @@ -32,11 +32,10 @@ export const DEFAULT_CONTEXT: DefaultDatasetQualityControllerState = { }, }, datasetUserPrivileges: { - canRead: true, - canMonitor: true, + datasetsPrivilages: {}, canViewIntegrations: true, - canReadFailureStore: false, }, + authorizedDatasetTypes: [], dataStreamStats: [], degradedDocStats: [], failedDocStats: [], diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts index f798256f65149..cc08c13bfa940 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts @@ -9,6 +9,15 @@ import { IToasts } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { DataStreamType } from '../../../../common/types'; +export const fetchDatasetTypesPrivilegesFailedNotifier = (toasts: IToasts, error: Error) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.fetchDatasetTypesPrivilegesFailed', { + defaultMessage: "We couldn't get your data set types privileges.", + }), + text: error.message, + }); +}; + export const fetchDatasetStatsFailedNotifier = (toasts: IToasts, error: Error) => { toasts.addDanger({ title: i18n.translate('xpack.datasetQuality.fetchDatasetStatsFailed', { diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts index e26866938a278..72b726f0b0d80 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts @@ -9,12 +9,16 @@ import type { IToasts } from '@kbn/core/public'; import { getDateISORange } from '@kbn/timerange'; import { assign, createMachine, DoneInvokeEvent, InterpreterFrom } from 'xstate'; import { + DatasetTypesPrivileges, DataStreamDocsStat, DataStreamStat, NonAggregatableDatasets, } from '../../../../common/api_types'; import { DEFAULT_DATASET_TYPE, KNOWN_TYPES } from '../../../../common/constants'; -import { DataStreamStatServiceResponse } from '../../../../common/data_streams_stats'; +import { + DataStreamStatServiceResponse, + GetDataStreamsTypesPrivilegesResponse, +} from '../../../../common/data_streams_stats'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { DataStreamType } from '../../../../common/types'; import { IDataStreamsStatsClient } from '../../../services/data_streams_stats'; @@ -23,6 +27,7 @@ import { fetchNonAggregatableDatasetsFailedNotifier } from '../../common/notific import { DEFAULT_CONTEXT } from './defaults'; import { fetchDatasetStatsFailedNotifier, + fetchDatasetTypesPrivilegesFailedNotifier, fetchDegradedStatsFailedNotifier, fetchFailedStatsFailedNotifier, fetchIntegrationsFailedNotifier, @@ -34,274 +39,318 @@ import { DatasetQualityControllerTypeState, } from './types'; -const generateInvokePerType = ({ src }: { src: string }) => ({ - invoke: KNOWN_TYPES.map((type) => ({ - id: `${type}`, - src, - data: { type }, - })), -}); +const getValidDatasetTypes = ( + context: DatasetQualityControllerContext, + isDatasetQualityAllSignalsAvailable: boolean +) => + (isDatasetQualityAllSignalsAvailable + ? context.filters.types.length + ? context.filters.types + : context.authorizedDatasetTypes + : [DEFAULT_DATASET_TYPE]) as DataStreamType[]; + +const extractAuthorizedDatasetTypes = (datasetTypesPrivileges: DatasetTypesPrivileges) => + Object.entries(datasetTypesPrivileges) + .filter(([_type, priv]) => priv.canMonitor || priv.canRead) + .map(([type, _priv]) => type.replace(/-\*-\*$/, '')) as DataStreamType[]; + +const generateInvokePerType = ({ src }: { src: string }) => { + return { + invoke: KNOWN_TYPES.map((type) => ({ + id: `${type}`, + src, + data: { type }, + })), + }; +}; const isTypeSelected = (type: DataStreamType, context: DatasetQualityControllerContext) => - context.filters.types.length === 0 || context.filters.types.includes(type); + (context.filters.types.length === 0 && context.authorizedDatasetTypes.includes(type)) || + context.filters.types.includes(type); export const createPureDatasetQualityControllerStateMachine = ( initialContext: DatasetQualityControllerContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVszoIoFdUAbAS3QE8BhAewDt0AnaoosBgOlk3VnYgyw5YAYgDKAQQBqAUQD6ycQBVxoxQCVp4gLKzVS0QG0ADAF1EoAA7VYZEnXMgAHogCMANgDMAdnYAONwBMbgAsAKxh3kYAnL4ANCDkiMHBHsHsRgG+vh4xIb6hLgC+hfFomNh4hKQUNPRMLGyc3Lz85ULCAHIA8ooAkgBiAJrySirqmjp6iqKy-eK9ADLSyMZmSCBWNuh2tA7OCO7efoEh4aGRMfGJCCkevunBLsmBOQEeHsWlAhUExGRUdEYzFYHC4GBa33aAFUAAoKRRyPpaORqcQdADi0lWDk2tns632hx8-iCYQiXmicQSSQCwQC6XCRlCQRcHjcLlCnxAZUElT+NUB9RBTXBfEhPGEGn6GlEAAkRspsetcdt8aBCZ5iScyecKZdqTcXFEXOwvC5zXcjMEvMFfEUStzxb9qgC6sDGmCeHwwFAGKgIJBkNQAMYiCQyeTSdGo5DLeRdSgzKaGUw46x43YE1ya46ks4XKnXYJRUJRdieKJ0sJRY0FLk8n5Vf61IENUHNb2+-2BkMibp9IaR6PiWPIeOJ3TKaazeZLFap5Xp1WZ9XZo4k07kylXJJuPfsKJeLLsm1eUJW+tOpsCt1tkVegNdgMQIOhzo9AbDWPD0fjpNTmY5kWZYDBcNZLCXHY9jXLU8y3fUi38Dxy2ZIwPGiLwPHOD4HQbHBnWbQV3XbUVHz9Z9XxEWF4URXpkVkVEMSxBcIK2KCswOHMNx1AsdxuLw3B8Pdgk1VIvFrS82j5F0WyFD0OzI7sX17SVpGlaQ5QVcQlVYjNoM49dtXzPVCySM0fA8AIXC8AIAiPay7Uk3kCJvVthU9XhaDocQoF9H0BAAI1YPCJXDORug6WRxHRaMo1GAAhJYtNEaRph0jZILVJwYNzTddW3A0cn8dgglCUJskPDIjScxt+VdNz5NFLzaB8vyoEC4LxT7D9BwiqKYo0dEEqS+EVFSwDZxAliMrYrKNUMuD8oQxBvByErizcNDfCiTbMhq-Dr3quSSK9ZrWoYfzMCCsAQu6gdhj66LYqG5RErkUaUunIC51A8CZr0jiiVyniTL4u4oiMEr7Ih1IRKMIwvH26TCNvdyOzO3yLvaq7Oqkqi4SUWj6MYzF0pVdjVwM2C8t4wqKoPLDvErHaUn8JGXKO4j7087zMcu1Brtu1T1M00aycyldsqp4HjIK65vG29hS1pEIonQ9DOVwq86tkrmSHoH0-WXfGaNkJEUTRUnpvJubEEPPiRJs9gIjso1fAyNwonZw7dbvfX0ENjAdhEKUZXlMXrYl-T7YNETbPYc00NCG0nlZNxvZ1oi-YNrtjeEajCdkXoOgRYc+i6DoUz+m3Jf2GOiyNE0Nb3EI2RtAIM5krPhX9wO84LhFZA6bQNJhcRKA08XZtru2vAdzIfACNWjE2le3nTrWpI532e5zo3g-zgnB9wKFxAWXo+knyPp+jufY7uZDMitc43F8cTxM7lGGo4Xvc4PgfESDBhFfauUdAYuDtM7ZIh5X7WXcA7SyyFHi0m2mhJeWRP6uWOuwX++86Am0LifaQahBhTwBpTKI8dKzRAyDtSyKcHYZFCMcKqm0vA2gKB3Tezkfbd0aLgoO+D2AADMcDBgABb6ygMICAdAwA4NoAAN2oAAa3kSFbefCf570EbQXgoj0ASKkQgfWyjgy6NWGQ5c+lQivz8FkCB8MV55DvkWZkbhTQp3POeBGGDuG1S7qjfhOjjYiLEZI2g0i2BMA4BYIgGBhHUAYAAW3YBo3hQTtEBz-kIgxRjIkmKUSGCxpgrEUylrY+4WQ7Tu3hsJN+DsQgeOSC4IwRoFbMi9v4g6mdMkKOyXgvR7AiDUCUofU2r0kqUDUBfYhvRtLX3IVLfwZZbRGF8LaM0ENHhRAduVNIZU1ZL09hkFImDObZwGbo3gIyxmKC6DFJKxdx59AjB9caZTbYIHyCacIFJrSVjPN4FwDsRImm2lhcS1pU6ay+FvDJ39+l92DsM0Zz5hD3MeXIfoUIFgLGSqlIeI8q5phvhxDZyFWnMzXm-CBrikhsmYRDdk+QNnJ0POcnejRhFEHINQfA6AxDSCWJQRQQ9pAAHUCWKE+TPBANkwYligTWVkNY7R2Q3nCnhvTEU8r5QK4QlAFhdBSrMBYgwuhQhlYs6xHEFUGlpMhN+ORwiPAVncWFjp4U6uwXq-l6AFG2D+AALykTIuR951Ha0Cbq3l-rA3bBDVI2V+lWS0gPBA4IGRLLlTcOVPitI0KmnXiWV+JZHLdORlgrmfqBUJpIEmyJ7AMZtQ6jdcUYTDEROkbI2g8jTGqKjd6mNvq411v1kG0goam0tqxm2kKnb8lQEKWYkpJgU0cQ2ukM0WQSwFCwpkPiTxKwlVsUedeAkbI4S1QEr+o79UBonYmqdUjm281bTjdtUlF3duENEpJ7A4kJKSak9JPqa1jsfbQSdJBp1QDfS1Pm2MBa415D+4xA7zHLksTa8p+wt0IwgdtMqrJmSmQOOVReYQjR0nEoeIInKtEiMg-Wxt8HZ380Fh23tYBxmF2LqIXo6JZTTnNgxS2zFQFkspsyZhZ5X6+CXmmtW9LOLuxKu8Ta7JKF2XeIxvptaoMwbgwh86nHUMVD4HIoVIqxXvOtVJpZ+xPBpCtBDCkR4UiljcEeoqJVPZ0prGeSs9ob09JHRBh9rGX1NtaKgUQjAwCoGSaIHA2xIn6PCWGnjCjlFqLSdGu9kX41PobTF+DcWEsXWS6l9A6WoCZa7RhopWGdg4cc7aymdJ7gcmqW07wbDggFreCaMIngl42jQp5-TsaoulbY2KTAVWkspbS1IxrS6-0MBiYB+J6BEkpIK8Oord5DPRdg6+yriWatrYy+hgpmG10bq65kBO5Usj9YEgjIbDrLJpGSNaR4eaggf0rZogzLH5vlcW-F67q26vrfO8GsAvQQkooEcuZAOBUAkCIBt39OWB35bAxF07kPoPPou7FgQy2bsI7u1D2DKO0dCIxzsLHmBcf4+a6u7DpTcNfLIweEITi0KCSyKEAttL2DvBsoJR4GzFczfvSVinZWqcVZp3D2r9XeCM+R6jq5oS2d0A5zjvH92onbYA0B-bIGjvatJ8KM7+vLta+q-D3XSPmdG-Ryz2gZuueW5XcUvn66BdyqFyzUXbcJcFvYfcVIEuyor0zcr4r461cLaux7nXiP9c++Raz-3aBYDiICqMhgEBueRPDX23Lg6He3urWTubWfoc55W3nhn7emeG6L0Mk3AesDl8r9X4Pj2w-PYqVmhOGzX6e2LLkVTHh3AePYW8MqyCKQr3T631Xxm3dLe17dhr3v+85MHyXkfFfUBV5r1bnbtuDugcKy3535PD-U+P7n0-eve8G7+7-6+6m435j4P4h6tZ0DtakpOaICMhz4iQrJL7+Ar4BClimibQHoRDmiIxg4Ioq6Z5f6a4-5d5-7n5AFIqX6l6j537j55K-r-qxJ7Yv5N7hYnYf5t7EEw606e754AGF6X7AED40G3734T4tZPZgSwGdYz6QyZoL7MzL5gwIzIRHg5BebwxZoIx76cEH6U4mad7JaB4W4MHZYRpE5DqO4cHcqf4GFH6w4e4mEQGT5tb84dZ4YMppBsgQzZABArwryVgeBHpGjMKKZLxvAtKKbRC6G2FcH2Hf6OErbOGW5bZP4sH24k42EcAu4AEOG8EpFmEPaSFT4R76QpDeEnJ+EBGL7BEGiJz0jZDUYchLyoShZerWHv5xH6Hq6GHu7JHY5B48Z8aDwCZCYiYzBiYkySYyGeE3CpAy5VGWQ1FBFHp1J+DQrWQwwQxcJhZVoXJ6FEEJEkFJHGGDEW7DHxQaDiAADSyAXQEqkU-QvQwqY4lAsoEm0++wJISs9G9C7Ir85wIRRa2QnCKQvhiCsRORdhvR+RcOWOT4kA-QJAYARA9BWWtehORSxOb+Bx3RRxsJiRBRgcz4yJqJ6JTWxRvObh4eHhgu0QwupY1CWQ7CLg6BR6MQJoLcea0QVkIQZy+B4G++BJ2e-RZxiJEAZJaJD+aRNuGRh2WRXR0J8RhJJxxJEpUpFJS6kBT2ZRHE54ZYO0TJWaLJZo7JDq32KEIkAKgQqeUJzGKpoppB4p5ESJKJ0pVmfaIxcgYxwmomdEFsTEXx8B7wCcERbSdIh4O0qmRGzCgkEMtiJGiZeBex4Os2PRTppxySCJrpkp7p4+wxACQ4MYcYzxrxkx4gb0sg0ysyMyCydJcq2QkMX2rSym6ynsHJDJ7CycqhlYSB16HRzeeJypGZ0OWA2ADA6A8U1WKisiAA7rQJqb0LANIKGKkViXllYUOVyiOSKWObABOVOTOfOYufmcuauS4SUTScGTcIJP5naJhGVJkAUGeGDH4X4DulaO4AUHWIKU7viUZscewOOWwEeUlrOdQAuUuSuWuUUY-nKcBgqbiTuQ6aORrsBQeaBdOeBSedBReRIdSdAe4bMV8o7PedZFhGRi+ZLoVJhGWFkO8DplRe0YqcOahXWrcuikaiatiuapag5iRXKpQnxDZP9qtAjC8BwimYOewUqexQGsGCMtgBAMIF0MAk8XxVajeT4QePDGaSDqgjRdcOgSaKWlZDuucBAqkMUA6F5AGPAOsKxTuYJfpAALS2J8R2IljsLeC2Tz7hC+D2keQuWAzhDgpGTwTkZYSeBKyEY7TOoAmepOVMYeQw4VAOW6SyGEjlT0URVLTkaPBWinoTbBblSMpBUKRdSW4hWUxGhHglSKaJnuzWi0hGVJBGhlh3DQr0auosXIUpWVV4yopKQ1VSx1X3CZDoF5rNWiVtXzF2K6gg7PBqxhAVWkQkk9ihijXZWKzcSyzLQ3DJz3CL6ljwzZCvxrUPgbXKSwUYlQDbWIC2RtKmhvzxUcjwzGiqbWjZDlgnDuBZriS7EyX7EoWpWKQUS9jDXPgPUIBPUmhHjiSwI+KfUOwxD3AwLlShHJzsiXUtDXWUTsD4C0CoACriJJJM4QAw1w0vWI12jI3WSNL7gtFNIPmMy42mZIbzpdQw29a5WLS0zyw8mMnoFsgIxLzs0cbIZcZDVwVU3oGGmHLeJHjnABBvmhnmitKCTuCslJX9V9KpWS1c1DWcWQBy0YF7olhMjK2WSMIZD+bRXqwZCRES3vpzqfq3SE3E2k3k3I6U2LjSZSy2Tm2K1W35A220V3DOyRFPBMgQrWj2lD4ZX-RZVJC7KxytJOrWibSVToSewJ2UGy3+1wEICy7lgli2RL6eCr4-ZFh5o+BPAbSZDbQ1hA3JV9KJ1Q2m1F0p0IDUJKzlTnDwxnAQqNL5BKyOxvCr65ocj2mGYw2uXkYcjNK80CTuwNGt163pl7ka4w1Zp8TZCmWr5aEpB5oBWz0wkLaG3u3igw01i+atJ+AWiMxhV8nn2OnQ5X0oZfpoaF2ZVzFPAxl0iUpXraYxBGlv1oUmaf3S1oY8Yw3ZCJ5vw2SULuw2TsJHrMhNwvxZpNIFB7gQPb19HOl8EZbwPkZWQpAHgxDWh6gnKarA1pmEGAWqk8En705n6-3J3-3WjDaUM7QAm+XeCbIEPMOZm8Hd5n4F4X6DJJ01yppsgFpYHOz0J3CezoQljBAiNI5wm-7sP-7GaCEyNUGDLOE80bKKMQx+DGjwyZCBBmgz1-nZHyXaNElsNe5SOUFD6FF3VU3h3GXiSQysqKYJ5tKKZaOu6uO6PuMCHSPXLGO6IpFwPd1zGKbIRWiFr0ab5S4rxKwWhPCK2sgDlt1b2iMd5ikkOSMxOePX5l5iHV5mM12PW5Ay5TXiTvC+IiThN5GRNkF6MUEgFX4DOiHgHVXJNfJvCq2FTRBIJPBvxWgCTgOONyW5HcFGEVP6MGGGNxNeNgF0EtByI81nUy5hBWSZAljoS0hgytkHjWlMKLUlhdOrPlMSMbO9FbPG41O0HiFE0k3oBk0MAU2HPrLHOYNnOD2XOFToE+AVFNItzoSYSPNAVrMvOelgA82BBgwKxR1o2YSnhmiIssNrOmNjNCVp3XDGidWoLsjWRO1hNLNsUrNIvlPeOUn3Ukupo8P1EezFpnia3JArwbIEtiPwnnH7N9ow2YQeI1g1i2KMUpAM31Fsk+DrL+GWS7pbSaP0soWMuEvMvXWamyNgIUJkuuDiQTX+GA13A0YcpatMY6vCtOH6v5kP482cvXBGhMj93eXWg9lGj0PFNMMuNqkisanOuou32QLGgpDvx03z4FptLNlV1tICRkjSUBsZ6lPoUgWTnYWoAQVQVnkwWGsB2EhGhgzA5R2cLux+FYRCv7mHm5v5unnknnm3Wsvosr5nj0g5CWRBDry2R9XHbLMX31tYXHmQXNtomttitovst2ohCmizPUuoRRWr0fnRmshMKr5pub2Bsm1+1-1fJPBrR0jYSK6UI5CqZvCHjpDCQjatKYSDudEMssaKXWBd2Htyp9vljWmNzIOWQliKP13+EkjGhuz8M2WFBAA */ createMachine< DatasetQualityControllerContext, DatasetQualityControllerEvent, DatasetQualityControllerTypeState >( { + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVszoIoFdUAbAS3QE8BhAewDt0AnaoosBgOhNrJOJIC8uUAMQQ6YTrQBu1ANYS0mbHkKkKNekxZtJPPoNpQEXGQGMMJOgG0ADAF1bdxKAAO1WDzrOQAD0QBmAA5-dgBWQIBOACYAdgiANniI0IBGfxSAGhByRCj49hSAFht-KJtQm0CYlPjQwqiAXwasxSwcAj51OkZmVg4uPVIDETFaCRM5BQw2lU6qbq0+3XReIaFjaWpzFet7KxSnJBA3Dx3abz8EKpt2fxioiv9-aMLQ6Kycq5Tb+pSYwPCMTiUSqTRa02UHTU800vR0AxW+iEwjYTA4LiIGAAZtQGABbditSGqMgwnrafrcRFrQwbMwWXYOezeE6ec5HS5FG6BSKBQoRGo8mp1D65J7sf6JRI2BIpPJ1MEgIntEldWEU9h41BcdiwTDoWDsCAQnCwYQAVQACsgAIIAFQAogB9O0ASQAss6AEo2gByAHEHY4We42RdEEUYqEJVFincKoDQjFRQgUilghK4pF4tVahUIorlbNoRpyUstTq9RhDcalKbhF6HQAxRsAZQAEk7bXabcGjqyzuHU4UozG41HylUkym0+LkoU7rG7kUoo1mkqTcXSaXFjoK7Rdfqa5vDVicKYABbI0bjTbyQmbqHbhZwjj7w-Vo0n9hn9CX9YTNsli0I4fauKGg4chGCRRLcNjxGm8GBLGwTJtkEZlNGkQRDYxShEk-xRoWj6qmSu5vtqB5VgaX51jRv7-oYKIMGi7AYtiuIEkWT5qmWe6UR+NG1jMp7nletKAQyIF7My-YQcBQ4pDBcEISkSEoXcM6zmENjpskDzlPcxF0TxZGvpqAnUTWYBQAwqAQJAyBbGaVrds6bqek6PoBkGsngacClQcOo73OOCZTmhnwpKk3wxK8Dz1IEtQRIUxkzKZO7me+VlGjZdkORATmmGajYtg6HZdvavZ+cc8leEFkbRqFJQTomkURoUCHsAKEQxP4NgDdUAL+GlxJzJlGrZUeuW2fZjnOT+YnXuIkgyPe3GkRN5aWdNDmzQVRWiX+4lGJJZygTVA6BaAnIJDcIJpIUan9TUTwzqEeQSnUhTIaUTyVPEo0quNL6TTtn57fl83FYtx3IqiuJsZi6A4viD4mZtoPbZWu15XNhULQxJ10lsUkXYc-lhg1d3sA9-hPSUunxG96GpvErzsDyEQ8kE6ZSkDW68eRFk4xDeMHYTS1MQj6LI6jXEkSD6rY1RuP7dDR2MadmxAYy+wU7VAX1Td0HxPdgSPc9TMs1FC6FLcSZPDEA3FBEI3rhtSt8RRov0dqrAE8VFrWva7ket6fqBmBhtUybwVNbGLXhVG7UICnYSlFGNSxHUUQFh7isllj-G+6e-saw2zZtp23bVQbV3G74HUhYn8aTinKbOzEEo2PcsT0xb9NruCGNe8LU2fli5eB5rJ2iCtEzrYXz7KyXquT9Ph2w1rJO69JTL13V7Jx0pnUFBEbuPXFukAimqGc0lSQAgzAqBALGXFz769+yQAdb0T8MWKI3YijTi6N0qY1Xl-QSZdf4a23sTM6wFyYhiNsfJuqYUr5CUpfem190yhBTMkbuNhVxqQGv4dm-Jh4blHkXKBItv6wL-pLOG0sgGyw4mjT29DvaMJgT+TerCd5IL1gcVBscMGn2wRfdIeDigEJTE-dgiRQj0zuEkfqaj36QL4RPH+LCYYAPYaxEB8twFjV4ePcGBj4HGO1vSc6ewoiHzQYpLB59cEjgUbfVmcVow-WSGoshVR4g0J4SvPRNiazOVbEeYQrYbQADV3IAHkewABkuypMoK2J0rYex2lbNHBu6DOQjgTmFdu05WZlGSAUBcvdeq4WqE8HRY8srRKNLE+Jvp0muibAATRdOkm0WTkA5LyQU+0eSmw2ldBkh0yASlH0UhUscSdqmp3TLhFRyR0xPDzvBIE7SrGdNLt04qcTqzCD6W6IZIzMnZNyfkwpsz5mLOWeIuSbiGrrOam3NqM4L4RAlIUTqoQkwwQvuE5eQtzlMMubAa5Bpg5uRdOHLykdfKuMkeUluVSgWsyUrs+mYT0g8n+ACU5kTrEXLEFc+JpVq6VR7Cs35J9-mt1ahFGcz8whlC7mbPkMR2Y0vhWDC5tA6A2igLZGy0wABGrAiwuRDo6DFnlvJR0uqsv5BLNlEs+K3bqOEzYVFiFEem4qzKSsRdK2gsr5VQCVSqk8lcyoVVruyvFzdKmGt5bU-wdQCi1EqL3EcyEFQFzobShFAiHVOoYAqzAyqwCqoQctMYq1JgWOBmcu1CaZVyuTS61Nbq6KzwAjrMmMlcWQTjmUPO7BKjqOqOmWIYSUyxnTKa7mQRnYymijaraa8i2OpLSm1AaaM32OYqYuWYCIkSpVuOpNU6Z3fnsbvWtB8JENowU20FraFztuQqKqI3aYq3ASNzYoZQ0glBHZ-fhOVE2TrLdOitIlM0mOAYu7hcLbWrrfcW51rr01bqlg40mTiHDfMpgey4R6W3BFPWmc9Xag2fTNnkN2vMkhu2fQw98XB0B4zOGq9FHkI4+R9UhiMH0+U8i+oKqIgp2PM2I1EnUZGKPARKlXcqNcqr0eulIpjxLgiBG6vKSNPJAgRu43Sg8fHZqUbRaHJ0rpfSOn9D6N0qTfTFN1RyiTl7iWuzCPydmCFX7RGU-GtTdkNOuS076G0npWyWhtJQcqYnG6ckk1FCo9sUqKeduQ-qgMY0QI6YW5zUkqNadwOaMZro3T+dM761MwWIxmxuGS-kxRKj3F0o5hL9B+N0GSxqu0gzLRZfreJzkVqZMLitcE+4dxervSeuwX4fwkoDs6jFkecWC2rsS659VzpUsOi9IMgLZTEBJm7k8NR5Qh4fUoTOWM9tkLBH7uCj6vUKtTaq+pgTv6RgLzvFMWNK6x3Teu9u0R+9ltDjCbBYhuEh3xEU3kGccUQh1CTOUaFsRzvPcuy5170H53-q4QrR7wGYfkauzVm7O64OfaCt97q4PXaClIfEPlYSVEWzUv4w7rxofQJe1jog1B8aabqzaAAQosp0lAvQZYW66Ou+6WuIBeGEAH97O1RiqHy3SBRIVqL+ilcooR6f8MZ7QQ0zPWd2lSf6f03OdO+bdCk1lNpWwOiKXjuOOFu4IQBOx6ICF7gzhzMe9MFtXi6UoaKtXpHYdJfYNrgqwhdf6+502c0GTxlVQt3aJ0HmvPW4wZEbuyEAeO90n8AH70MxhJKDhN4ec1Gq8VNKhy8AjjLrRwwYXgXEAAFpYywX6mUYIN8BR9RTA35mPc1GSmZht4dsXLFxo1AiVYAghB15WwgBvp84Jt5epEP4-hu0hGd5tpI+FdI-TfiP-NY+lgT-0FJJs08Z9DnSFhU+ZRdLRDkWT1mA8HaQoGjnCXKQ1dgDxC4CgKKwBL8goL4wsBREhXhs4OsUw5FOZKEpx6gYoAY-dKIgC458IUw7YCg-hSFggigfo0xkDS5UCpEesNlAVA1PgPpvgFwL5UgUp8IgQv8D9BYa9X1doTxiDylYgyCeUO5iVScCh-heoaglINFCDEVhJlAq1DBOCIxIsVEZROpDspdCFiU5QmphVcIqF-txCBFJDTQg8WcCpZCEAyFu5Fdeo3ZfhIgtJ6haYC94xwUHg7hdCcpIZ8ZDoTCVxu4AVeCakoo8EW16Z8IigqhYxfdmCP4SMul3CJYjFoMvD5CzYsFlDRUowZxXgQh7gEJvsihHp99xtR8ntoE3DxZ4Fg9IATC2sbgkhopdJ8I3Yc9LNe8Ices84gQ4oYhXC1YoYZ52B8BaBUB8B0ALxcQBBKifkctqi9k6jopNEmiop2YQh0hcIcIahmZC9uiN44EZ4vD6gfDuVk5-DEBhUJRmZScfpwhrVIjdEVMBEp4dj-4EjJiGNUwkjFCM9MN0jWZCMwg0hmZB17glJYVUdR0SjpoHjDEtcjCJjEMRdTD4wJQV8+pXgL5opO44pz5ooglopIU4gtjbE+jpV0BXRf9WA8QwAqsIAqjEShDV9USBRVDPgglKdldog0g+RIUCTmF4EBihiRixj+BYSY5XirUQpkTgi0SmSTiEhUM3gKg0xnY8585CjD9ii2CIYelqw9juDfCjjtlcxaZGj4JgiEJS9VSWCwSNShItT6Jni4T69TDdJuQRwow7puZ0xgUygW0dtcC1IrVuZuSkUUVoT8YqjnTOZXS3hkiLZAhu05R7Z2NUI0MPcgyGVkVpo+ThjRiGBxjqSXj4SH0XSgRoyZRYzZcN9XpYywlOpyggz31wNy1INK0dSDjCUKDcgAzaY4hO8kpLjUobj4sQNpoGzS0INZ17SRT4TsDvhkilCQQVD18OZhCS8LUNj6ywMxymyM0Kj8yHTZ8Hhe1i9Wle4rVg119IU4IRxg0cw1ELYNyJ1Gyv1myf0syBTcyhS9ypzHTDyZNjzH1utzzakKgZMLYoxDyFFcIByLSoieNVMA9KMvDcIZxShYJVxahcxUg+pzTaEJsj90dqtNcbsTDnhYJmZFMyUlx8JpTUwqgQg2tEg3YPp2YrVdCNdQzjCCzHSL58gIthDgh6YeLgU5dIVngkxIwLYEgmgmggA */ context: initialContext, predictableActionArguments: true, id: 'DatasetQualityController', - type: 'parallel', + initial: 'initializing', states: { - stats: { + initializing: { + invoke: { + src: 'loadDatasetTypesPrivileges', + onDone: [ + { + target: 'main', + actions: ['storeAuthorizedDatasetTypes'], + cond: 'hasAuthorizedTypes', + }, + { + target: 'emptyState', + }, + ], + onError: { + target: 'initializationFailed', + actions: ['notifyFetchDatasetTypesPrivilegesFailed'], + }, + }, + }, + initializationFailed: {}, + emptyState: {}, + main: { type: 'parallel', states: { - datasets: { - initial: 'fetching', + stats: { + type: 'parallel', states: { - fetching: { - invoke: { - src: 'loadDataStreamStats', - onDone: { - target: 'loaded', - actions: ['storeDataStreamStats', 'storeDatasets'], + datasets: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadDataStreamStats', + onDone: { + target: 'loaded', + actions: ['storeDataStreamStats', 'storeDatasets'], + }, + onError: { + target: 'loaded', + actions: ['notifyFetchDatasetStatsFailed'], + }, + }, }, - onError: { - target: 'loaded', - actions: ['notifyFetchDatasetStatsFailed'], + loaded: {}, + }, + on: { + UPDATE_TIME_RANGE: { + target: 'datasets.fetching', + actions: ['storeTimeRange'], + }, + REFRESH_DATA: { + target: 'datasets.fetching', }, }, }, - loaded: {}, - }, - on: { - UPDATE_TIME_RANGE: { - target: 'datasets.fetching', - actions: ['storeTimeRange'], - }, - REFRESH_DATA: { - target: 'datasets.fetching', - }, - }, - }, - degradedDocs: { - initial: 'fetching', - states: { - fetching: { - invoke: { - src: 'loadDegradedDocs', - onDone: { - target: 'loaded', - actions: ['storeDegradedDocStats', 'storeDatasets'], - }, - onError: [ - { - target: 'unauthorized', - cond: 'checkIfActionForbidden', - }, - { - target: 'loaded', - actions: ['notifyFetchDegradedStatsFailed'], + degradedDocs: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadDegradedDocs', + onDone: { + target: 'loaded', + actions: ['storeDegradedDocStats', 'storeDatasets'], + }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchDegradedStatsFailed'], + }, + ], }, - ], + }, + loaded: {}, + unauthorized: { type: 'final' }, + }, + on: { + UPDATE_TIME_RANGE: { + target: 'degradedDocs.fetching', + actions: ['storeTimeRange'], + }, + REFRESH_DATA: { + target: 'degradedDocs.fetching', + }, }, }, - loaded: {}, - unauthorized: { type: 'final' }, - }, - on: { - UPDATE_TIME_RANGE: { - target: 'degradedDocs.fetching', - actions: ['storeTimeRange'], - }, - REFRESH_DATA: { - target: 'degradedDocs.fetching', + failedDocs: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadFailedDocs', + onDone: { + target: 'loaded', + actions: ['storeFailedDocStats', 'storeDatasets'], + }, + onError: [ + { + target: 'notImplemented', + cond: 'checkIfNotImplemented', + }, + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchFailedStatsFailed'], + }, + ], + }, + }, + loaded: {}, + notImplemented: {}, + unauthorized: { type: 'final' }, + }, + on: { + UPDATE_TIME_RANGE: { + target: 'failedDocs.fetching', + actions: ['storeTimeRange'], + }, + REFRESH_DATA: { + target: 'failedDocs.fetching', + }, + }, }, - }, - }, - failedDocs: { - initial: 'fetching', - states: { - fetching: { - invoke: { - src: 'loadFailedDocs', - onDone: { - target: 'loaded', - actions: ['storeFailedDocStats', 'storeDatasets'], + docsStats: { + initial: 'fetching', + states: { + fetching: { + ...generateInvokePerType({ + src: 'loadDataStreamDocsStats', + }), }, - onError: [ - { - target: 'notImplemented', - cond: 'checkIfNotImplemented', - }, + loaded: {}, + unauthorized: { type: 'final' }, + }, + on: { + SAVE_TOTAL_DOCS_STATS: { + target: 'docsStats.loaded', + actions: ['storeTotalDocStats', 'storeDatasets'], + }, + NOTIFY_TOTAL_DOCS_STATS_FAILED: [ { - target: 'unauthorized', + target: 'docsStats.unauthorized', cond: 'checkIfActionForbidden', }, { - target: 'loaded', - actions: ['notifyFetchFailedStatsFailed'], + target: 'docsStats.loaded', + actions: ['notifyFetchTotalDocsFailed'], }, ], + UPDATE_TIME_RANGE: { + target: 'docsStats.fetching', + actions: ['storeTimeRange'], + }, + REFRESH_DATA: { + target: 'docsStats.fetching', + }, }, }, - loaded: {}, - notImplemented: {}, - unauthorized: { type: 'final' }, - }, - on: { - UPDATE_TIME_RANGE: { - target: 'failedDocs.fetching', - actions: ['storeTimeRange'], - }, - REFRESH_DATA: { - target: 'failedDocs.fetching', - }, - }, - }, - docsStats: { - initial: 'fetching', - states: { - fetching: { - ...generateInvokePerType({ - src: 'loadDataStreamDocsStats', - }), - }, - loaded: {}, - unauthorized: { type: 'final' }, - }, - on: { - SAVE_TOTAL_DOCS_STATS: { - target: 'docsStats.loaded', - actions: ['storeTotalDocStats', 'storeDatasets'], - }, - NOTIFY_TOTAL_DOCS_STATS_FAILED: [ - { - target: 'docsStats.unauthorized', - cond: 'checkIfActionForbidden', + nonAggregatableDatasets: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadNonAggregatableDatasets', + onDone: { + target: 'loaded', + actions: ['storeNonAggregatableDatasets'], + }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchNonAggregatableDatasetsFailed'], + }, + ], + }, + }, + loaded: {}, + unauthorized: { type: 'final' }, }, - { - target: 'docsStats.loaded', - actions: ['notifyFetchTotalDocsFailed'], + on: { + UPDATE_TIME_RANGE: { + target: 'nonAggregatableDatasets.fetching', + }, + REFRESH_DATA: { + target: 'nonAggregatableDatasets.fetching', + }, }, - ], - UPDATE_TIME_RANGE: { - target: 'docsStats.fetching', - actions: ['storeTimeRange'], - }, - REFRESH_DATA: { - target: 'docsStats.fetching', }, }, }, - nonAggregatableDatasets: { + integrations: { initial: 'fetching', states: { fetching: { invoke: { - src: 'loadNonAggregatableDatasets', + src: 'loadIntegrations', onDone: { target: 'loaded', - actions: ['storeNonAggregatableDatasets'], + actions: ['storeIntegrations', 'storeDatasets'], + }, + onError: { + target: 'loaded', + actions: [ + 'notifyFetchIntegrationsFailed', + 'storeEmptyIntegrations', + 'storeDatasets', + ], + }, + }, + }, + loaded: { + on: { + UPDATE_TABLE_CRITERIA: { + target: 'loaded', + actions: ['storeTableOptions'], + }, + TOGGLE_INACTIVE_DATASETS: { + target: 'loaded', + actions: ['storeInactiveDatasetsVisibility', 'resetPage'], + }, + TOGGLE_FULL_DATASET_NAMES: { + target: 'loaded', + actions: ['storeFullDatasetNamesVisibility'], }, - onError: [ - { - target: 'unauthorized', - cond: 'checkIfActionForbidden', - }, - { - target: 'loaded', - actions: ['notifyFetchNonAggregatableDatasetsFailed'], - }, - ], }, }, - loaded: {}, - unauthorized: { type: 'final' }, }, on: { UPDATE_TIME_RANGE: { - target: 'nonAggregatableDatasets.fetching', + target: 'integrations.fetching', + actions: ['storeTimeRange'], }, REFRESH_DATA: { - target: 'nonAggregatableDatasets.fetching', + target: 'integrations.fetching', }, - }, - }, - }, - }, - integrations: { - initial: 'fetching', - states: { - fetching: { - invoke: { - src: 'loadIntegrations', - onDone: { - target: 'loaded', - actions: ['storeIntegrations', 'storeDatasets'], + UPDATE_INTEGRATIONS: { + target: 'integrations.loaded', + actions: ['storeIntegrationsFilter'], }, - onError: { - target: 'loaded', - actions: [ - 'notifyFetchIntegrationsFailed', - 'storeEmptyIntegrations', - 'storeDatasets', - ], + UPDATE_NAMESPACES: { + target: 'integrations.loaded', + actions: ['storeNamespaces'], }, - }, - }, - loaded: { - on: { - UPDATE_TABLE_CRITERIA: { - target: 'loaded', - actions: ['storeTableOptions'], + UPDATE_QUALITIES: { + target: 'integrations.loaded', + actions: ['storeQualities'], }, - TOGGLE_INACTIVE_DATASETS: { - target: 'loaded', - actions: ['storeInactiveDatasetsVisibility', 'resetPage'], + UPDATE_TYPES: { + target: '#DatasetQualityController.main.stats', + actions: ['storeTypes'], }, - TOGGLE_FULL_DATASET_NAMES: { - target: 'loaded', - actions: ['storeFullDatasetNamesVisibility'], + UPDATE_QUERY: { + actions: ['storeQuery'], }, }, }, }, - on: { - UPDATE_TIME_RANGE: { - target: 'integrations.fetching', - actions: ['storeTimeRange'], - }, - REFRESH_DATA: { - target: 'integrations.fetching', - }, - UPDATE_INTEGRATIONS: { - target: 'integrations.loaded', - actions: ['storeIntegrationsFilter'], - }, - UPDATE_NAMESPACES: { - target: 'integrations.loaded', - actions: ['storeNamespaces'], - }, - UPDATE_QUALITIES: { - target: 'integrations.loaded', - actions: ['storeQualities'], - }, - UPDATE_TYPES: { - target: '#DatasetQualityController.stats', - actions: ['storeTypes'], - }, - UPDATE_QUERY: { - actions: ['storeQuery'], - }, - }, }, }, }, @@ -396,15 +445,32 @@ export const createPureDatasetQualityControllerStateMachine = ( } : {}; }), + storeAuthorizedDatasetTypes: assign( + (context, event: DoneInvokeEvent) => { + const authorizedDatasetTypes = extractAuthorizedDatasetTypes( + event.data.datasetTypesPrivileges + ); + + const filterTypes = context.filters.types as DataStreamType[]; + + // This is to prevent the user from selecting types that are not authorized through the url + const validTypes = filterTypes.filter( + (type) => authorizedDatasetTypes.includes(type) && KNOWN_TYPES.includes(type) + ); + + return { + filters: { + ...context.filters, + types: validTypes, + }, + authorizedDatasetTypes, + }; + } + ), storeDataStreamStats: assign( (_context, event: DoneInvokeEvent) => { const dataStreamStats = event.data.dataStreamsStats as DataStreamStat[]; - const datasetUserPrivileges = { - ...event.data.datasetUserPrivileges, - canReadFailureStore: - event.data.datasetUserPrivileges.canReadFailureStore || - dataStreamStats.some((ds) => ds.userPrivileges.canReadFailureStore), - }; + const datasetUserPrivileges = event.data.datasetUserPrivileges; return { dataStreamStats, @@ -462,6 +528,15 @@ export const createPureDatasetQualityControllerStateMachine = ( }), }, guards: { + hasAuthorizedTypes: (_context, event) => { + return !!( + 'data' in event && + typeof event.data === 'object' && + event.data && + 'datasetTypesPrivileges' in event.data && + extractAuthorizedDatasetTypes(event.data.datasetTypesPrivileges).length > 0 + ); + }, checkIfActionForbidden: (_context, event) => { return ( 'data' in event && @@ -497,6 +572,8 @@ export const createDatasetQualityControllerStateMachine = ({ }: DatasetQualityControllerStateMachineDependencies) => createPureDatasetQualityControllerStateMachine(initialContext).withConfig({ actions: { + notifyFetchDatasetTypesPrivilegesFailed: (_context, event: DoneInvokeEvent) => + fetchDatasetTypesPrivilegesFailedNotifier(toasts, event.data), notifyFetchDatasetStatsFailed: (_context, event: DoneInvokeEvent) => fetchDatasetStatsFailedNotifier(toasts, event.data), notifyFetchDegradedStatsFailed: (_context, event: DoneInvokeEvent) => @@ -511,12 +588,14 @@ export const createDatasetQualityControllerStateMachine = ({ fetchFailedStatsFailedNotifier(toasts, event.data), }, services: { + loadDatasetTypesPrivileges: () => { + return dataStreamStatsClient.getDataStreamsTypesPrivileges({ + types: KNOWN_TYPES, + }); + }, loadDataStreamStats: (context, _event) => { - const validTypes = isDatasetQualityAllSignalsAvailable - ? context.filters.types - : [DEFAULT_DATASET_TYPE]; return dataStreamStatsClient.getDataStreamsStats({ - types: validTypes as DataStreamType[], + types: getValidDatasetTypes(context, isDatasetQualityAllSignalsAvailable), datasetQuery: context.filters.query, }); }, @@ -549,7 +628,7 @@ export const createDatasetQualityControllerStateMachine = ({ const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); return dataStreamStatsClient.getDataStreamsDegradedStats({ - types: context.filters.types as DataStreamType[], + types: getValidDatasetTypes(context, isDatasetQualityAllSignalsAvailable), datasetQuery: context.filters.query, start, end, @@ -559,7 +638,7 @@ export const createDatasetQualityControllerStateMachine = ({ const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); return dataStreamStatsClient.getDataStreamsFailedStats({ - types: context.filters.types as DataStreamType[], + types: getValidDatasetTypes(context, isDatasetQualityAllSignalsAvailable), datasetQuery: context.filters.query, start, end, @@ -569,7 +648,7 @@ export const createDatasetQualityControllerStateMachine = ({ const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); return dataStreamStatsClient.getNonAggregatableDatasets({ - types: context.filters.types as DataStreamType[], + types: getValidDatasetTypes(context, isDatasetQualityAllSignalsAvailable), start, end, }); diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts index 9d71984ec51d0..2f522f5ab66ea 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts @@ -16,6 +16,7 @@ import { DataStreamStat, DataStreamStatServiceResponse, DataStreamStatType, + GetDataStreamsTypesPrivilegesResponse, } from '../../../../common/data_streams_stats'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { @@ -37,6 +38,10 @@ interface FiltersCriteria { query?: string; } +export interface WithAuthorizedDatasetTypes { + authorizedDatasetTypes: DataStreamType[]; +} + export interface WithTableOptions { table: TableCriteria; } @@ -83,6 +88,7 @@ export type DefaultDatasetQualityControllerState = WithTableOptions & WithFailedDocs & WithDatasets & WithFilters & + WithAuthorizedDatasetTypes & WithNonAggregatableDatasets & Partial; @@ -90,35 +96,47 @@ type DefaultDatasetQualityStateContext = DefaultDatasetQualityControllerState; export type DatasetQualityControllerTypeState = | { - value: 'stats.datasets.fetching'; + value: 'initializing'; + context: DefaultDatasetQualityStateContext; + } + | { + value: 'initializationFailed'; + context: DefaultDatasetQualityStateContext; + } + | { + value: 'emptyState'; + context: DefaultDatasetQualityStateContext; + } + | { + value: 'main.stats.datasets.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'stats.datasets.loaded'; + value: 'main.stats.datasets.loaded'; context: DefaultDatasetQualityStateContext; } | { - value: 'stats.docsStats.fetching'; + value: 'main.stats.docsStats.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'stats.degradedDocs.fetching'; + value: 'main.stats.degradedDocs.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'stats.failedDocs.fetching'; + value: 'main.stats.failedDocs.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'stats.nonAggregatableDatasets.fetching'; + value: 'main.stats.nonAggregatableDatasets.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'integrations.fetching'; + value: 'main.integrations.fetching'; context: DefaultDatasetQualityStateContext; } | { - value: 'nonAggregatableDatasets.fetching'; + value: 'main.nonAggregatableDatasets.fetching'; context: DefaultDatasetQualityStateContext; }; @@ -166,6 +184,7 @@ export type DatasetQualityControllerEvent = type: 'UPDATE_TYPES'; types: DataStreamType[]; } + | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_details_controller/state_machine.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_details_controller/state_machine.ts index 832cab3c8d4b5..afb35cebcd765 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_details_controller/state_machine.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_details_controller/state_machine.ts @@ -772,7 +772,11 @@ export const createPureDatasetQualityDetailsControllerStateMachine = ( canReadFailureStore: (context) => { return ( 'dataStreamSettings' in context && - Boolean(context.dataStreamSettings.datasetUserPrivileges?.canReadFailureStore) + Boolean( + context.dataStreamSettings.datasetUserPrivileges?.datasetsPrivilages[ + context.dataStream + ].canReadFailureStore + ) ); }, }, diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts index 4f1c4ea845703..2f2d7d52b3eb8 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts @@ -12,9 +12,18 @@ import { getDataStreams } from '.'; const mockGetMockMatchingDataStreams = jest.fn().mockImplementation(() => MATCHING_DATA_STREAMS); const mockGetDatasetPrivileges = jest.fn().mockImplementation(() => ({ - canRead: true, - canMonitor: true, - canViewIntegrations: true, + datasetsPrivilages: { + 'logs-*-*': { + canRead: true, + canMonitor: true, + canReadFailureStore: true, + }, + 'metrics-*-*': { + canRead: true, + canMonitor: true, + canReadFailureStore: true, + }, + }, })); const mockGetMockDataStreamPrivileges = jest.fn().mockImplementation(() => DATA_STREAMS_PRIVILEGES); @@ -48,7 +57,8 @@ describe('getDataStreams', () => { 'logs-*-*,metrics-*-*' ); - expect(result.datasetUserPrivileges.canMonitor).toBe(true); + expect(result.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canMonitor).toBe(true); + expect(result.datasetUserPrivileges.datasetsPrivilages['metrics-*-*'].canMonitor).toBe(true); }); it('Passes datasetQuery parameter to the DataStreamService', async () => { @@ -63,7 +73,7 @@ describe('getDataStreams', () => { 'logs-nginx-*' ); - expect(result.datasetUserPrivileges.canMonitor).toBe(true); + expect(result.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canMonitor).toBe(true); }); describe('uncategorized only option', () => { diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/index.ts b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/index.ts index 376d2923220ce..60e5f315d5ef8 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/index.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_data_streams/index.ts @@ -30,10 +30,14 @@ export async function getDataStreams(options: { const datasetUserPrivileges = await datasetQualityPrivileges.getDatasetPrivileges( esClient, - datasetNames.join(',') + datasetNames + ); + + const canMonitor = Object.values(datasetUserPrivileges.datasetsPrivilages).some( + (privileges) => privileges.canMonitor ); - if (!datasetUserPrivileges.canMonitor) { + if (!canMonitor) { return { dataStreams: [], datasetUserPrivileges, @@ -76,3 +80,26 @@ export async function getDataStreams(options: { datasetUserPrivileges, }; } + +export async function getDatasetTypesPrivileges(options: { + esClient: ElasticsearchClient; + types: DataStreamType[]; +}) { + const { esClient, types } = options; + + const datasetNames = types.map((type) => + streamPartsToIndexPattern({ + typePattern: type, + datasetPattern: '*-*', + }) + ); + + const { datasetsPrivilages } = await datasetQualityPrivileges.getDatasetPrivileges( + esClient, + datasetNames + ); + + return { + datasetsPrivilages, + }; +} diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_datastream_settings/index.ts b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_datastream_settings/index.ts index 8044c8a5abf6a..f194e5528ff01 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_datastream_settings/index.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/get_datastream_settings/index.ts @@ -20,7 +20,7 @@ export async function getDataStreamSettings({ const [createdOn, [dataStreamInfo], datasetUserPrivileges] = await Promise.all([ getDataStreamCreatedOn(esClient, dataStream), dataStreamService.getMatchingDataStreams(esClient, dataStream), - datasetQualityPrivileges.getDatasetPrivileges(esClient, dataStream), + datasetQualityPrivileges.getDatasetPrivileges(esClient, [dataStream]), ]); const integration = dataStreamInfo?._meta?.package?.name; diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts index a986321125980..8aed4aeae775e 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts @@ -14,6 +14,7 @@ import { DataStreamRolloverResponse, DataStreamSettings, DataStreamStat, + DatasetTypesPrivileges, DatasetUserPrivileges, DegradedFieldAnalysis, DegradedFieldResponse, @@ -28,7 +29,7 @@ import { createDatasetQualityServerRoute } from '../create_datasets_quality_serv import { checkAndLoadIntegration } from './check_and_load_integration'; import { failedDocsRouteRepository } from './failed_docs/routes'; import { getDataStreamDetails } from './get_data_stream_details'; -import { getDataStreams } from './get_data_streams'; +import { getDataStreams, getDatasetTypesPrivileges } from './get_data_streams'; import { getDataStreamsMeteringStats } from './get_data_streams_metering_stats'; import { getDataStreamsStats } from './get_data_streams_stats'; import { getAggregatedDatasetPaginatedResults } from './get_dataset_aggregated_paginated_results'; @@ -41,6 +42,41 @@ import { getNonAggregatableDataStreams } from './get_non_aggregatable_data_strea import { updateFieldLimit } from './update_field_limit'; import { getDataStreamsCreationDate } from './get_data_streams_creation_date'; +const datasetTypesPrivilegesRoute = createDatasetQualityServerRoute({ + endpoint: 'GET /internal/dataset_quality/data_streams/types_privileges', + params: t.type({ + query: t.type({ types: typesRt }), + }), + options: { + tags: [], + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', + }, + }, + async handler(resources): Promise<{ + datasetTypesPrivileges: DatasetTypesPrivileges; + }> { + const { context, params } = resources; + const coreContext = await context.core; + + // Query datastreams as the current user as the Kibana internal user may not have all the required permissions + const esClient = coreContext.elasticsearch.client.asCurrentUser; + + const { datasetsPrivilages } = await getDatasetTypesPrivileges({ + esClient, + ...params.query, + }); + + return { + datasetTypesPrivileges: datasetsPrivilages, + }; + }, +}); + const statsRoute = createDatasetQualityServerRoute({ endpoint: 'GET /internal/dataset_quality/data_streams/stats', params: t.type({ @@ -547,6 +583,7 @@ const rolloverDataStream = createDatasetQualityServerRoute({ }); export const dataStreamsRouteRepository = { + ...datasetTypesPrivilegesRoute, ...statsRoute, ...degradedDocsRoute, ...totalDocsRoute, diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/services/privileges.ts b/x-pack/platform/plugins/shared/dataset_quality/server/services/privileges.ts index ae6c5ca9e2f1c..9443b468cc0c6 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/services/privileges.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/services/privileges.ts @@ -50,30 +50,36 @@ class DatasetQualityPrivileges { public async getDatasetPrivileges( esClient: ElasticsearchClient, - dataset: string, + dataset: string[], space = '*' ): Promise<{ - canRead: boolean; - canMonitor: boolean; + datasetsPrivilages: Record< + string, + { canRead: boolean; canMonitor: boolean; canReadFailureStore: boolean } + >; canViewIntegrations: boolean; - canReadFailureStore: boolean; }> { - const indexPrivileges = await esClient.security.hasPrivileges({ - index: [ + const indexPrivileges = await this.getHasIndexPrivileges(esClient, dataset, [ + 'read', + 'monitor', + 'view_index_metadata', + FAILURE_STORE_PRIVILEGE, + ]); + + const datasetsPrivilages = Object.fromEntries( + Object.entries(indexPrivileges).map(([index, privileges]) => [ + index, { - names: dataset, - privileges: ['read', 'monitor', 'view_index_metadata', FAILURE_STORE_PRIVILEGE], + canRead: privileges.read, + canMonitor: privileges.view_index_metadata, + canReadFailureStore: privileges[FAILURE_STORE_PRIVILEGE], }, - ], - }); - - const canRead = indexPrivileges.index[dataset]?.read ?? false; - const canViewIndexMetadata = indexPrivileges.index[dataset]?.view_index_metadata ?? false; - const canReadFailureStore = indexPrivileges.index[dataset]?.[FAILURE_STORE_PRIVILEGE] ?? false; + ]) + ); const canViewIntegrations = await this.getCanViewIntegrations(esClient, space); - return { canRead, canMonitor: canViewIndexMetadata, canViewIntegrations, canReadFailureStore }; + return { datasetsPrivilages, canViewIntegrations }; } public async canReadDataset( @@ -89,11 +95,11 @@ class DatasetQualityPrivileges { const datasetUserPrivileges = await datasetQualityPrivileges.getDatasetPrivileges( esClient, - datasetName, + [datasetName], space ); - return datasetUserPrivileges.canRead; + return datasetUserPrivileges.datasetsPrivilages[datasetName].canRead; } public async throwIfCannotReadDataset( diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/dataset_quality/data_stream_settings.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/dataset_quality/data_stream_settings.ts index 2efe713bc1b49..346a43a8e682c 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/dataset_quality/data_stream_settings.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/dataset_quality/data_stream_settings.ts @@ -36,14 +36,18 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const dataStreamName = `${type}-${dataset}-${namespace}`; const syntheticsDataStreamName = `${type}-${syntheticsDataset}-${namespace}`; - const defaultDataStreamPrivileges = { + const defaultDataStreamPrivileges = (dataStream: string) => ({ datasetUserPrivileges: { - canRead: true, - canMonitor: true, + datasetsPrivilages: { + [dataStream]: { + canRead: true, + canMonitor: true, + canReadFailureStore: true, + }, + }, canViewIntegrations: true, - canReadFailureStore: true, }, - }; + }); async function callApiAs({ roleScopedSupertestWithCookieCredentials, @@ -93,7 +97,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { dataStream: nonExistentDataStream, }, }); - expect(resp.body).eql(defaultDataStreamPrivileges); + expect(resp.body).eql(defaultDataStreamPrivileges(nonExistentDataStream)); }); describe('gets the data stream settings for non integrations', () => { @@ -145,7 +149,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { })}-000001` ); expect(resp.body.datasetUserPrivileges).to.eql( - defaultDataStreamPrivileges.datasetUserPrivileges + defaultDataStreamPrivileges(dataStreamName).datasetUserPrivileges ); }); @@ -229,7 +233,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { })}-000001` ); expect(resp.body.datasetUserPrivileges).to.eql( - defaultDataStreamPrivileges.datasetUserPrivileges + defaultDataStreamPrivileges(syntheticsDataStreamName).datasetUserPrivileges ); }); diff --git a/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts b/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts index fbcb6ed4ea934..bc562af1e6986 100644 --- a/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts +++ b/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts @@ -69,8 +69,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns user authorization as false for noAccessUser', async () => { const resp = await callApiAs('noAccessUser'); - expect(resp.body.datasetUserPrivileges.canRead).to.be(false); - expect(resp.body.datasetUserPrivileges.canMonitor).to.be(false); + expect(resp.body.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canRead).to.be(false); + expect(resp.body.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canMonitor).to.be( + false + ); expect(resp.body.datasetUserPrivileges.canViewIntegrations).to.be(false); expect(resp.body.dataStreamsStats).to.eql([]); }); @@ -79,9 +81,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const resp = await callApiAs('adminUser'); expect(resp.body.datasetUserPrivileges).to.eql({ - canRead: true, - canMonitor: true, - canReadFailureStore: true, + datasetsPrivilages: { + 'logs-*-*': { + canRead: true, + canMonitor: true, + canReadFailureStore: true, + }, + }, canViewIntegrations: true, }); }); @@ -89,8 +95,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('get empty stats for a readUser', async () => { const resp = await callApiAs('readUser'); - expect(resp.body.datasetUserPrivileges.canRead).to.be(true); - expect(resp.body.datasetUserPrivileges.canMonitor).to.be(false); + expect(resp.body.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canRead).to.be(true); + expect(resp.body.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canMonitor).to.be( + false + ); expect(resp.body.datasetUserPrivileges.canViewIntegrations).to.be(false); expect(resp.body.dataStreamsStats).to.eql([]); }); @@ -110,7 +118,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { await ingestDocuments({ dataset: 'test.2' }); const resp = await callApiAs('datasetQualityMonitorUser'); - expect(resp.body.datasetUserPrivileges.canMonitor).to.be(true); + expect(resp.body.datasetUserPrivileges.datasetsPrivilages['logs-*-*'].canMonitor).to.be( + true + ); expect( resp.body.dataStreamsStats .map(({ name, userPrivileges: { canMonitor: hasPrivilege } }) => ({ diff --git a/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/types_privileges.spec.ts b/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/types_privileges.spec.ts new file mode 100644 index 0000000000000..d89c1119079c7 --- /dev/null +++ b/x-pack/solutions/observability/test/dataset_quality_api_integration/tests/data_streams/types_privileges.spec.ts @@ -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 expect from '@kbn/expect'; +import rison from '@kbn/rison'; +import { DatasetQualityApiClientKey } from '../../common/config'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const datasetQualityApiClient = getService('datasetQualityApiClient'); + + async function callApiAs( + user: DatasetQualityApiClientKey, + types: Array<'logs' | 'metrics' | 'traces' | 'synthetics'> = ['logs'] + ) { + return await datasetQualityApiClient[user]({ + endpoint: 'GET /internal/dataset_quality/data_streams/types_privileges', + params: { + query: { + types: rison.encodeArray(types), + }, + }, + }); + } + + registry.when('Api Key privileges check', { config: 'basic' }, () => { + describe('types privileges', function () { + // This disables the forward-compatibility test for Kibana 8.19 with ES upgraded to 9.0. + // These versions are not expected to work together. + // The tests raise "unknown index privilege [read_failure_store]" error in ES 9.0. + this.onlyEsVersion('8.19 || >=9.1'); + + const noPrivileges = { + canRead: false, + canMonitor: false, + canReadFailureStore: false, + }; + + const fullPrivileges = { + canRead: true, + canMonitor: true, + canReadFailureStore: true, + }; + + it('returns no privileges for noAccessUser with single type', async () => { + const resp = await callApiAs('noAccessUser', ['logs']); + + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.eql(noPrivileges); + }); + + it('returns no privileges for noAccessUser with multiple types', async () => { + const resp = await callApiAs('noAccessUser', ['logs', 'metrics']); + + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.eql(noPrivileges); + expect(resp.body.datasetTypesPrivileges['metrics-*-*']).to.eql(noPrivileges); + }); + + it('returns correct privileges for adminUser with single type', async () => { + const resp = await callApiAs('adminUser', ['logs']); + + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.eql(fullPrivileges); + }); + + it('returns correct privileges for adminUser with multiple types', async () => { + const resp = await callApiAs('adminUser', ['logs', 'metrics', 'traces']); + + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.eql(fullPrivileges); + expect(resp.body.datasetTypesPrivileges['metrics-*-*']).to.eql(fullPrivileges); + expect(resp.body.datasetTypesPrivileges['traces-*-*']).to.eql(fullPrivileges); + }); + + it('returns correct privileges for adminUser with all types', async () => { + const resp = await callApiAs('adminUser', ['logs', 'metrics', 'traces', 'synthetics']); + + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.eql(fullPrivileges); + expect(resp.body.datasetTypesPrivileges['metrics-*-*']).to.eql(fullPrivileges); + expect(resp.body.datasetTypesPrivileges['traces-*-*']).to.eql(fullPrivileges); + expect(resp.body.datasetTypesPrivileges['synthetics-*-*']).to.eql(fullPrivileges); + }); + + it('returns expected structure for response', async () => { + const resp = await callApiAs('adminUser', ['logs']); + + expect(resp.body).to.have.property('datasetTypesPrivileges'); + expect(resp.body.datasetTypesPrivileges).to.be.an('object'); + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.have.property('canRead'); + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.have.property('canMonitor'); + expect(resp.body.datasetTypesPrivileges['logs-*-*']).to.have.property( + 'canReadFailureStore' + ); + }); + }); + }); +} diff --git a/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_privileges.ts b/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_privileges.ts index c8f56d09ff24f..c11f6554989d9 100644 --- a/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_privileges.ts +++ b/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_privileges.ts @@ -75,6 +75,90 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid }); }); + describe('User has access to dataset quality with limited privileges', () => { + before(async () => { + await createDatasetQualityUserWithRole(security, 'fullAccess', []); + + await PageObjects.security.login('fullAccess', 'fullAccess-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + // Cleanup the user and role + await PageObjects.security.forceLogout(); + await deleteDatasetQualityUserWithRole(security, 'fullAccess'); + }); + + describe('User cannot monitor any data stream', () => { + before(async () => { + await PageObjects.datasetQuality.navigateTo(); + }); + after(async () => { + // Cleanup the user and role + await PageObjects.security.forceLogout(); + await deleteDatasetQualityUserWithRole(security, 'fullAccess'); + }); + + it('user has access to dataset quality app but cannot read any dataset', async () => { + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityNoPrivilegesEmptyState + ); + }); + }); + + describe('User has access to a single data stream', () => { + before(async () => { + await createDatasetQualityUserWithRole(security, 'fullAccess', [ + { names: ['metrics-*'], privileges: ['read', 'view_index_metadata'] }, + ]); + + await PageObjects.security.login('fullAccess', 'fullAccess-password', { + expectSpaceSelector: false, + }); + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + // Cleanup the user and role + await PageObjects.security.forceLogout(); + await deleteDatasetQualityUserWithRole(security, 'fullAccess'); + }); + + it('should still be able to navigate and use the dataset quality app', async () => { + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityNoPrivilegesEmptyState + ); + }); + + it('types filter should not be rendered', async () => { + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTypesSelectableButton + ); + }); + }); + + describe('User has access to a multipl data streams', () => { + before(async () => { + await createDatasetQualityUserWithRole(security, 'fullAccess', [ + { names: ['logs-*'], privileges: ['read', 'view_index_metadata'] }, + { names: ['metrics-*'], privileges: ['read', 'view_index_metadata'] }, + ]); + + await PageObjects.security.login('fullAccess', 'fullAccess-password', { + expectSpaceSelector: false, + }); + await PageObjects.datasetQuality.navigateTo(); + }); + + it('types filter should be rendered', async () => { + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTypesSelectableButton + ); + }); + }); + }); + describe('User can read logs-*', () => { before(async () => { await createDatasetQualityUserWithRole(security, 'fullAccess', [ diff --git a/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts b/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts index 0d6d4040806ff..11850cc33c528 100644 --- a/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts +++ b/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts @@ -144,6 +144,7 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetQualityNamespacesSelectable: 'datasetQualityNamespacesSelectable', datasetQualityNamespacesSelectableButton: 'datasetQualityNamespacesSelectableButton', datasetQualityTypesSelectable: 'datasetQualityFilterType', + datasetQualityTypesSelectableButton: 'datasetQualityFilterTypeSelectableButton', datasetQualityQualitiesSelectable: 'datasetQualityQualitiesSelectable', datasetQualityQualitiesSelectableButton: 'datasetQualityQualitiesSelectableButton', datasetQualityDetailsEmptyPrompt: 'datasetQualityDetailsEmptyPrompt', @@ -154,8 +155,8 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetQualityDetailsIntegrationRowVersion: 'datasetQualityDetailsFieldsList-version', datasetQualityDetailsLinkToDiscover: 'datasetQualityDetailsLinkToDiscover', datasetQualityInsufficientPrivileges: 'datasetQualityInsufficientPrivileges', - datasetQualityNoDataEmptyState: 'datasetQualityTableNoData', datasetQualityNoPrivilegesEmptyState: 'datasetQualityNoPrivilegesEmptyState', + datasetQualityNoDataEmptyState: 'datasetQualityTableNoData', superDatePickerToggleQuickMenuButton: 'superDatePickerToggleQuickMenuButton', superDatePickerApplyTimeButton: 'superDatePickerApplyTimeButton', superDatePickerQuickMenu: 'superDatePickerQuickMenu',