diff --git a/src/plugins/data_view_editor/public/components/empty_prompts/empty_prompts.tsx b/src/plugins/data_view_editor/public/components/empty_prompts/empty_prompts.tsx index e5f4e6cec057e..ed6b772b98c3d 100644 --- a/src/plugins/data_view_editor/public/components/empty_prompts/empty_prompts.tsx +++ b/src/plugins/data_view_editor/public/components/empty_prompts/empty_prompts.tsx @@ -20,8 +20,7 @@ import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt'; import { PromptFooter } from './prompt_footer'; import { FLEET_ASSETS_TO_IGNORE } from '../../../../data/common'; -const removeAliases = (item: MatchedItem) => - !(item as unknown as ResolveIndexResponseItemAlias).indices; +const removeAliases = (mItem: MatchedItem) => !mItem.item.indices; interface Props { onCancel: () => void; diff --git a/src/plugins/data_view_editor/public/types.ts b/src/plugins/data_view_editor/public/types.ts index cfeee1df979d8..ed0b19f6e529a 100644 --- a/src/plugins/data_view_editor/public/types.ts +++ b/src/plugins/data_view_editor/public/types.ts @@ -127,21 +127,6 @@ export interface IndexPatternTableItem { sort: string; } -// copied from index pattern management, needs review -export interface MatchedItem { - name: string; - tags: Tag[]; - item: { - name: string; - backing_indices?: string[]; - timestamp_field?: string; - indices?: string[]; - aliases?: string[]; - attributes?: ResolveIndexResponseItemIndexAttrs[]; - data_stream?: string; - }; -} - export enum ResolveIndexResponseItemIndexAttrs { OPEN = 'open', CLOSED = 'closed', diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index ab60fbf34071a..d3cd7e4737de1 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -52,6 +52,7 @@ export type { DataViewFieldMap, DataViewSpec, SourceFilter, + HasDataService, } from './types'; export { DataViewType } from './types'; export type { IndexPatternsContract, DataViewsContract } from './data_views'; diff --git a/src/plugins/data_views/public/data_views_service_public.ts b/src/plugins/data_views/public/data_views_service_public.ts index a5d6dc0a8280c..ceedffb553b66 100644 --- a/src/plugins/data_views/public/data_views_service_public.ts +++ b/src/plugins/data_views/public/data_views_service_public.ts @@ -9,7 +9,7 @@ import { DataViewsService } from '.'; import { DataViewsServiceDeps } from '../common/data_views/data_views'; -import { HasDataService } from '../common/types'; +import { HasDataService } from '../common'; interface DataViewsServicePublicDeps extends DataViewsServiceDeps { getCanSaveSync: () => boolean; diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts index 2a9f1201cc854..1da24d714847b 100644 --- a/src/plugins/data_views/public/index.ts +++ b/src/plugins/data_views/public/index.ts @@ -51,6 +51,9 @@ export type { DataViewsPublicPluginSetup, DataViewsPublicPluginStart, DataViewsContract, + HasDataViewsResponse, + IndicesResponse, + IndicesResponseModified, } from './types'; // Export plugin after all other imports diff --git a/src/plugins/data_views/public/plugin.ts b/src/plugins/data_views/public/plugin.ts index f2472b294e6c4..eb05e2ab209fc 100644 --- a/src/plugins/data_views/public/plugin.ts +++ b/src/plugins/data_views/public/plugin.ts @@ -50,11 +50,8 @@ export class DataViewsPublicPlugin { fieldFormats }: DataViewsPublicStartDependencies ): DataViewsPublicPluginStart { const { uiSettings, http, notifications, savedObjects, theme, overlays, application } = core; - const hasDataStart = this.hasData.start(core); - console.log(core); - console.log({ hasDataStart }); return new DataViewsServicePublic({ - hasData: { ...hasDataStart }, + hasData: this.hasData.start(core), uiSettings: new UiSettingsPublicToCommon(uiSettings), savedObjectsClient: new SavedObjectsClientPublicToCommon(savedObjects.client), apiClient: new DataViewsApiClient(http), diff --git a/src/plugins/data_views/public/services/has_data.ts b/src/plugins/data_views/public/services/has_data.ts index 9166bfde7a3ab..eb12281c2798b 100644 --- a/src/plugins/data_views/public/services/has_data.ts +++ b/src/plugins/data_views/public/services/has_data.ts @@ -6,33 +6,21 @@ * Side Public License, v 1. */ -import { CoreStart, HttpSetup } from 'kibana/public'; -import { - MatchedItem, - ResolveIndexResponseItemAlias, -} from 'src/plugins/data_view_editor/public/types'; -import { getIndices } from '../../../data_view_editor/public/lib'; - -const FLEET_ASSETS_TO_IGNORE = { - LOGS_INDEX_PATTERN: 'logs-*', - METRICS_INDEX_PATTERN: 'metrics-*', - LOGS_DATA_STREAM_TO_IGNORE: 'logs-elastic_agent', // ignore ds created by Fleet server itself - METRICS_DATA_STREAM_TO_IGNORE: 'metrics-elastic_agent', // ignore ds created by Fleet server itself - METRICS_ENDPOINT_INDEX_TO_IGNORE: 'metrics-endpoint.metadata_current_default', // ignore index created by Fleet endpoint package installed by default in Cloud -}; +import { CoreStart, HttpStart } from 'kibana/public'; +import { FLEET_ASSETS_TO_IGNORE } from '../../common'; +import { HasDataViewsResponse, IndicesResponse, IndicesResponseModified } from '../'; export class HasData { - private removeAliases = (item: MatchedItem) => - !(item as unknown as ResolveIndexResponseItemAlias).indices; + private removeAliases = (source: IndicesResponseModified): boolean => !source.item.indices; - private isUserDataIndex = (source: MatchedItem) => { + private isUserDataIndex = (source: IndicesResponseModified): boolean => { // filter out indices that start with `.` if (source.name.startsWith('.')) return false; // filter out sources from FLEET_ASSETS_TO_IGNORE - if (source.name === FLEET_ASSETS_TO_IGNORE.LOGS_DATA_STREAM_TO_IGNORE) return false; - if (source.name === FLEET_ASSETS_TO_IGNORE.METRICS_DATA_STREAM_TO_IGNORE) return false; - if (source.name === FLEET_ASSETS_TO_IGNORE.METRICS_ENDPOINT_INDEX_TO_IGNORE) return false; + for (const key in FLEET_ASSETS_TO_IGNORE) { + if (source.name === (FLEET_ASSETS_TO_IGNORE as any)[key]) return false; + } // filter out empty sources created by apm server if (source.name.startsWith('apm-')) return false; @@ -46,7 +34,7 @@ export class HasData { /** * Check to see if ES data exists */ - hasESData: async () => { + hasESData: async (): Promise => { const hasLocalESData = await this.checkLocalESData(http); if (!hasLocalESData) { const hasRemoteESData = await this.checkRemoteESData(http); @@ -55,55 +43,95 @@ export class HasData { return hasLocalESData; }, /** - * Check to see if user created data views exist + * Check to see if any data view exists */ - hasUserDataView: async () => { - const hasLocalESData = await this.findUserDataViews(); - return hasLocalESData; + hasDataView: async (): Promise => { + const dataViewsCheck = await this.findDataViews(http); + return dataViewsCheck; }, /** - * Check to see if any data view exists + * Check to see if user created data views exist */ - hasDataView: async () => { - const hasLocalESData = await this.findDataViews(); - return hasLocalESData; + hasUserDataView: async (): Promise => { + const userDataViewsCheck = await this.findUserDataViews(http); + return userDataViewsCheck; }, }; } - private checkLocalESData = (http: HttpSetup) => { - return getIndices({ + // ES Data + + private responseToItemArray = (response: IndicesResponse): IndicesResponseModified[] => { + const { indices = [], aliases = [] } = response; + const source: IndicesResponseModified[] = []; + + [...indices, ...aliases, ...(response.data_streams || [])].forEach((item) => { + source.push({ + name: item.name, + item, + }); + }); + + return source; + }; + + private getIndices = async ({ + http, + pattern, + showAllIndices, + }: { + http: HttpStart; + pattern: string; + showAllIndices: boolean; + }): Promise => + http + .get(`/internal/index-pattern-management/resolve_index/${pattern}`, { + query: showAllIndices ? { expand_wildcards: 'all' } : undefined, + }) + .then((response) => { + if (!response) { + return []; + } else { + return this.responseToItemArray(response); + } + }); + + private checkLocalESData = (http: HttpStart): Promise => + this.getIndices({ http, - isRollupIndex: () => false, pattern: '*', showAllIndices: false, - searchClient: data.search.search, - }).then((dataSources) => { + }).then((dataSources: IndicesResponseModified[]) => { return dataSources.some(this.isUserDataIndex); }); - }; - private checkRemoteESData = (http: HttpSetup) => { - return getIndices({ + private checkRemoteESData = (http: HttpStart): Promise => + this.getIndices({ http, - isRollupIndex: () => false, pattern: '*:*', showAllIndices: false, - searchClient: data.search.search, - }).then((dataSources) => { + }).then((dataSources: IndicesResponseModified[]) => { return !!dataSources.filter(this.removeAliases).length; }); - }; - private findDataViews = () => { - return Promise.resolve(true); + // Data Views + + private getHasDataViews = async ({ http }: { http: HttpStart }): Promise => + http.get(`/internal/index_patterns/has_data_views`); + + private findDataViews = (http: HttpStart): Promise => { + return this.getHasDataViews({ http }).then((response: HasDataViewsResponse) => { + const { hasDataView } = response; + return hasDataView; + }); }; - private findUserDataViews = () => { - return Promise.resolve(true); + private findUserDataViews = (http: HttpStart): Promise => { + return this.getHasDataViews({ http }).then((response: HasDataViewsResponse) => { + const { hasUserDataView } = response; + return hasUserDataView; + }); }; } export type HasDataStart = ReturnType; - -// searchClient: data.search.search, diff --git a/src/plugins/data_views/public/types.ts b/src/plugins/data_views/public/types.ts index 0f4c84cc39e62..1c6d1003d1aa8 100644 --- a/src/plugins/data_views/public/types.ts +++ b/src/plugins/data_views/public/types.ts @@ -10,7 +10,57 @@ import { ExpressionsSetup } from 'src/plugins/expressions/public'; import { FieldFormatsSetup, FieldFormatsStart } from 'src/plugins/field_formats/public'; import { PublicMethodsOf } from '@kbn/utility-types'; import { DataViewsService } from './data_views'; -import { HasDataService } from '../common/types'; +import { HasDataService } from '../common'; + +export enum IndicesResponseItemIndexAttrs { + OPEN = 'open', + CLOSED = 'closed', + HIDDEN = 'hidden', + FROZEN = 'frozen', +} + +export interface IndicesResponseModified { + name: string; + item: { + name: string; + backing_indices?: string[]; + timestamp_field?: string; + indices?: string[]; + aliases?: string[]; + attributes?: IndicesResponseItemIndexAttrs[]; + data_stream?: string; + }; +} + +export interface IndicesResponseItem { + name: string; +} + +export interface IndicesResponseItemAlias extends IndicesResponseItem { + indices: string[]; +} + +export interface IndicesResponseItemDataStream extends IndicesResponseItem { + backing_indices: string[]; + timestamp_field: string; +} + +export interface IndicesResponseItemIndex extends IndicesResponseItem { + aliases?: string[]; + attributes?: IndicesResponseItemIndexAttrs[]; + data_stream?: string; +} + +export interface IndicesResponse { + indices?: IndicesResponseItemIndex[]; + aliases?: IndicesResponseItemAlias[]; + data_streams?: IndicesResponseItemDataStream[]; +} + +export interface HasDataViewsResponse { + hasDataView: boolean; + hasUserDataView: boolean; +} export interface DataViewsPublicSetupDependencies { expressions: ExpressionsSetup; @@ -27,14 +77,14 @@ export interface DataViewsPublicStartDependencies { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface DataViewsPublicPluginSetup {} -export interface DataViewsServicePublic extends DataViewsService { +export interface DataViewsServicePublic extends PublicMethodsOf { getCanSaveSync: () => boolean; hasData: HasDataService; } -export type DataViewsContract = PublicMethodsOf; +export type DataViewsContract = DataViewsServicePublic; /** * Data views plugin public Start contract */ -export type DataViewsPublicPluginStart = PublicMethodsOf; +export type DataViewsPublicPluginStart = DataViewsServicePublic; diff --git a/src/plugins/data_views/server/has_user_index_pattern.ts b/src/plugins/data_views/server/has_user_index_pattern.ts index cf64a2c2013da..89790483d6178 100644 --- a/src/plugins/data_views/server/has_user_index_pattern.ts +++ b/src/plugins/data_views/server/has_user_index_pattern.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { ElasticsearchClient, SavedObjectsClientContract } from '../../../core/server'; +import { + ElasticsearchClient, + SavedObjectsClientContract, + SavedObjectsFindResponse, +} from '../../../core/server'; import { IndexPatternSavedObjectAttrs } from '../common/data_views'; import { FLEET_ASSETS_TO_IGNORE } from '../common/constants'; @@ -15,8 +19,11 @@ interface Deps { soClient: SavedObjectsClientContract; } -export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise => { - const indexPatterns = await soClient.find({ +export const hasIndexPattern = async ({ + esClient, + soClient, +}: Deps): Promise> => + soClient.find({ type: 'index-pattern', fields: ['title'], search: `*`, @@ -24,10 +31,19 @@ export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise perPage: 100, }); +export const hasUserIndexPattern = async ( + { esClient, soClient }: Deps, + iPatterns?: SavedObjectsFindResponse +): Promise => { + let indexPatterns = iPatterns; + + if (!indexPatterns) { + indexPatterns = await hasIndexPattern({ esClient, soClient }); + } + if (indexPatterns.total === 0) { return false; } - // If there are any index patterns that are not the default metrics-* and logs-* ones created by Fleet, // assume there are user created index patterns if ( @@ -49,7 +65,6 @@ export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise ); if (hasAnyNonDefaultFleetIndices) return true; - const hasAnyNonDefaultFleetDataStreams = resolveResponse.data_streams.some( (ds) => ds.name !== FLEET_ASSETS_TO_IGNORE.METRICS_DATA_STREAM_TO_IGNORE && @@ -57,6 +72,5 @@ export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise ); if (hasAnyNonDefaultFleetDataStreams) return true; - return false; }; diff --git a/src/plugins/data_views/server/routes.ts b/src/plugins/data_views/server/routes.ts index 1e50e36316ff0..f21d951cab007 100644 --- a/src/plugins/data_views/server/routes.ts +++ b/src/plugins/data_views/server/routes.ts @@ -13,7 +13,8 @@ import { IndexPatternsFetcher } from './fetcher'; import { routes } from './rest_api_routes'; import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from './types'; -import { registerFieldForWildcard } from './fields_for'; +import { registerFieldForWildcard } from './routes/fields_for'; +import { registerHasDataViewsRoute } from './routes/has_data_views'; export function registerRoutes( http: HttpServiceSetup, @@ -38,6 +39,7 @@ export function registerRoutes( routes.forEach((route) => route(router, getStartServices, dataViewRestCounter)); registerFieldForWildcard(router, getStartServices); + registerHasDataViewsRoute(router); router.get( { diff --git a/src/plugins/data_views/server/fields_for.ts b/src/plugins/data_views/server/routes/fields_for.ts similarity index 96% rename from src/plugins/data_views/server/fields_for.ts rename to src/plugins/data_views/server/routes/fields_for.ts index 6bd3f682249a5..77d39a6ea5d8b 100644 --- a/src/plugins/data_views/server/fields_for.ts +++ b/src/plugins/data_views/server/routes/fields_for.ts @@ -12,9 +12,9 @@ import { StartServicesAccessor, RequestHandler, RouteValidatorFullConfig, -} from '../../../core/server'; -import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from './types'; -import { IndexPatternsFetcher } from './fetcher'; +} from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; +import { IndexPatternsFetcher } from '../fetcher'; const parseMetaFields = (metaFields: string | string[]) => { let parsedFields: string[] = []; diff --git a/src/plugins/data_views/server/routes/has_data_views.ts b/src/plugins/data_views/server/routes/has_data_views.ts new file mode 100644 index 0000000000000..9e581d8bb3068 --- /dev/null +++ b/src/plugins/data_views/server/routes/has_data_views.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { IRouter } from '../../../../core/server'; +import { hasIndexPattern, hasUserIndexPattern } from '../has_user_index_pattern'; + +export const registerHasDataViewsRoute = (router: IRouter): void => { + router.get( + { + path: '/internal/index_patterns/has_data_views', + validate: {}, + }, + async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const dataViews = await hasIndexPattern({ + esClient: elasticsearchClient, + soClient: savedObjectsClient, + }); + const checkDataPattern = await hasUserIndexPattern( + { + esClient: elasticsearchClient, + soClient: savedObjectsClient, + }, + dataViews + ); + const response = { + hasDataView: !!dataViews.total, + hasUserDataView: !!checkDataPattern, + }; + return res.ok({ body: response }); + } + ); +};