[IndexPatterns] No data experience to handle default Fleet assets (#108887)

Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
This commit is contained in:
Anton Dosov 2021-08-20 17:58:34 +02:00 committed by GitHub
parent a8c2a43834
commit cd7f26dd81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 644 additions and 26 deletions

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) &gt; [hasUserIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.hasuserindexpattern.md)
## IndexPatternsService.hasUserIndexPattern() method
Checks if current user has a user created index pattern ignoring fleet's server default index patterns
<b>Signature:</b>
```typescript
hasUserIndexPattern(): Promise<boolean>;
```
<b>Returns:</b>
`Promise<boolean>`

View file

@ -45,5 +45,6 @@ export declare class IndexPatternsService
| [createAndSave(spec, override, skipFetchFields)](./kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md) | | Create a new index pattern and save it right away |
| [createSavedObject(indexPattern, override)](./kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md) | | Save a new index pattern |
| [delete(indexPatternId)](./kibana-plugin-plugins-data-public.indexpatternsservice.delete.md) | | Deletes an index pattern from .kibana index |
| [hasUserIndexPattern()](./kibana-plugin-plugins-data-public.indexpatternsservice.hasuserindexpattern.md) | | Checks if current user has a user created index pattern ignoring fleet's server default index patterns |
| [updateSavedObject(indexPattern, saveAttempts, ignoreErrors)](./kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md) | | Save existing index pattern. Will attempt to merge differences if there are conflicts |

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) &gt; [hasUserIndexPattern](./kibana-plugin-plugins-data-server.indexpatternsservice.hasuserindexpattern.md)
## IndexPatternsService.hasUserIndexPattern() method
Checks if current user has a user created index pattern ignoring fleet's server default index patterns
<b>Signature:</b>
```typescript
hasUserIndexPattern(): Promise<boolean>;
```
<b>Returns:</b>
`Promise<boolean>`

View file

@ -45,5 +45,6 @@ export declare class IndexPatternsService
| [createAndSave(spec, override, skipFetchFields)](./kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md) | | Create a new index pattern and save it right away |
| [createSavedObject(indexPattern, override)](./kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md) | | Save a new index pattern |
| [delete(indexPatternId)](./kibana-plugin-plugins-data-server.indexpatternsservice.delete.md) | | Deletes an index pattern from .kibana index |
| [hasUserIndexPattern()](./kibana-plugin-plugins-data-server.indexpatternsservice.hasuserindexpattern.md) | | Checks if current user has a user created index pattern ignoring fleet's server default index patterns |
| [updateSavedObject(indexPattern, saveAttempts, ignoreErrors)](./kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md) | | Save existing index pattern. Will attempt to merge differences if there are conflicts |

View file

@ -15,3 +15,17 @@ export const RUNTIME_FIELD_TYPES = [
'boolean',
'geo_point',
] as const;
/**
* Used to determine if the instance has any user created index patterns by filtering index patterns
* that are created and backed only by Fleet server data
* Should be revised after https://github.com/elastic/kibana/issues/82851 is fixed
* For more background see: https://github.com/elastic/kibana/issues/107020
*/
export 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
};

View file

@ -35,8 +35,9 @@ export const createEnsureDefaultIndexPattern = (
return;
}
// If there is any index pattern created, set the first as default
if (patterns.length >= 1) {
// If there is any user index pattern created, set the first as default
// if there is 0 patterns, then don't even call `hasUserIndexPattern()`
if (patterns.length >= 1 && (await this.hasUserIndexPattern().catch(() => true))) {
defaultId = patterns[0];
await uiSettings.set('defaultIndex', defaultId);
} else {

View file

@ -232,6 +232,13 @@ export class IndexPatternsService {
}
};
/**
* Checks if current user has a user created index pattern ignoring fleet's server default index patterns
*/
async hasUserIndexPattern(): Promise<boolean> {
return this.apiClient.hasUserIndexPattern();
}
/**
* Get field list by providing { pattern }
* @param options

View file

@ -136,6 +136,7 @@ export interface GetFieldsOptionsTimePattern {
export interface IIndexPatternsApiClient {
getFieldsForTimePattern: (options: GetFieldsOptionsTimePattern) => Promise<any>;
getFieldsForWildcard: (options: GetFieldsOptions) => Promise<any>;
hasUserIndexPattern: () => Promise<boolean>;
}
export type { SavedObject };

View file

@ -23,7 +23,7 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient {
this.http = http;
}
private _request(url: string, query: any) {
private _request(url: string, query?: any) {
return this.http
.fetch(url, {
query,
@ -62,4 +62,9 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient {
allow_no_index: allowNoIndex,
}).then((resp: any) => resp.fields || []);
}
async hasUserIndexPattern(): Promise<boolean> {
const response = await this._request(this._getUrl(['has_user_index_pattern']));
return response.result;
}
}

View file

@ -1479,6 +1479,7 @@ export class IndexPatternsService {
getIds: (refresh?: boolean) => Promise<string[]>;
getIdsWithTitle: (refresh?: boolean) => Promise<IndexPatternListItem[]>;
getTitles: (refresh?: boolean) => Promise<string[]>;
hasUserIndexPattern(): Promise<boolean>;
refreshFields: (indexPattern: IndexPattern) => Promise<void>;
savedObjectToSpec: (savedObject: SavedObject<IndexPatternAttributes>) => IndexPatternSpec;
setDefault: (id: string | null, force?: boolean) => Promise<void>;

View file

@ -0,0 +1,174 @@
/*
* 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 { hasUserIndexPattern } from './has_user_index_pattern';
import { elasticsearchServiceMock, savedObjectsClientMock } from '../../../../core/server/mocks';
describe('hasUserIndexPattern', () => {
const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser;
const soClient = savedObjectsClientMock.create();
beforeEach(() => jest.resetAllMocks());
it('returns false when there are no index patterns', async () => {
soClient.find.mockResolvedValue({
page: 1,
per_page: 100,
total: 0,
saved_objects: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns true when there are any index patterns other than metrics-* or logs-*', async () => {
soClient.find.mockResolvedValue({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: '1',
references: [],
type: 'index-pattern',
score: 99,
attributes: { title: 'my-pattern-*' },
},
],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(true);
});
describe('when only metrics-* and logs-* index patterns exist', () => {
beforeEach(() => {
soClient.find.mockResolvedValue({
page: 1,
per_page: 100,
total: 2,
saved_objects: [
{
id: '1',
references: [],
type: 'index-pattern',
score: 99,
attributes: { title: 'metrics-*' },
},
{
id: '2',
references: [],
type: 'index-pattern',
score: 99,
attributes: { title: 'logs-*' },
},
],
});
});
it('calls indices.resolveIndex for the index patterns', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [],
aliases: [],
})
);
await hasUserIndexPattern({ esClient, soClient });
expect(esClient.indices.resolveIndex).toHaveBeenCalledWith({
name: 'logs-*,metrics-*',
});
});
it('returns false if no logs or metrics data_streams exist', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [],
aliases: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns true if any index exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [{ name: 'logs', attributes: [] }],
data_streams: [],
aliases: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(true);
});
it('returns false if only metrics-elastic_agent data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'metrics-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-metrics-elastic_agent'],
},
],
aliases: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns false if only logs-elastic_agent data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'logs-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-logs-elastic_agent'],
},
],
aliases: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns false if only metrics-endpoint.metadata_current_default index exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [
{
name: 'metrics-endpoint.metadata_current_default',
attributes: ['open'],
},
],
aliases: [],
data_streams: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns true if any other data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'other',
timestamp_field: '@timestamp',
backing_indices: ['.ds-other'],
},
],
aliases: [],
})
);
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(true);
});
});
});

View file

@ -0,0 +1,62 @@
/*
* 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 { ElasticsearchClient, SavedObjectsClientContract } from '../../../../core/server';
import { IndexPatternSavedObjectAttrs } from '../../common/index_patterns/index_patterns';
import { FLEET_ASSETS_TO_IGNORE } from '../../common/index_patterns/constants';
interface Deps {
esClient: ElasticsearchClient;
soClient: SavedObjectsClientContract;
}
export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise<boolean> => {
const indexPatterns = await soClient.find<IndexPatternSavedObjectAttrs>({
type: 'index-pattern',
fields: ['title'],
search: `*`,
searchFields: ['title'],
perPage: 100,
});
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 (
indexPatterns.saved_objects.some(
(ip) =>
ip.attributes.title !== FLEET_ASSETS_TO_IGNORE.METRICS_INDEX_PATTERN &&
ip.attributes.title !== FLEET_ASSETS_TO_IGNORE.LOGS_INDEX_PATTERN
)
) {
return true;
}
const resolveResponse = await esClient.indices.resolveIndex({
name: `${FLEET_ASSETS_TO_IGNORE.LOGS_INDEX_PATTERN},${FLEET_ASSETS_TO_IGNORE.METRICS_INDEX_PATTERN}`,
});
const hasAnyNonDefaultFleetIndices = resolveResponse.body.indices.some(
(ds) => ds.name !== FLEET_ASSETS_TO_IGNORE.METRICS_ENDPOINT_INDEX_TO_IGNORE
);
if (hasAnyNonDefaultFleetIndices) return true;
const hasAnyNonDefaultFleetDataStreams = resolveResponse.body.data_streams.some(
(ds) =>
ds.name !== FLEET_ASSETS_TO_IGNORE.METRICS_DATA_STREAM_TO_IGNORE &&
ds.name !== FLEET_ASSETS_TO_IGNORE.LOGS_DATA_STREAM_TO_IGNORE
);
if (hasAnyNonDefaultFleetDataStreams) return true;
return false;
};

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { ElasticsearchClient } from 'kibana/server';
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
import {
GetFieldsOptions,
IIndexPatternsApiClient,
@ -14,10 +14,14 @@ import {
} from '../../common/index_patterns/types';
import { IndexPatternMissingIndices } from '../../common/index_patterns/lib';
import { IndexPatternsFetcher } from './fetcher';
import { hasUserIndexPattern } from './has_user_index_pattern';
export class IndexPatternsApiServer implements IIndexPatternsApiClient {
esClient: ElasticsearchClient;
constructor(elasticsearchClient: ElasticsearchClient) {
constructor(
elasticsearchClient: ElasticsearchClient,
private readonly savedObjectsClient: SavedObjectsClientContract
) {
this.esClient = elasticsearchClient;
}
async getFieldsForWildcard({
@ -50,4 +54,11 @@ export class IndexPatternsApiServer implements IIndexPatternsApiClient {
const indexPatterns = new IndexPatternsFetcher(this.esClient);
return await indexPatterns.getFieldsForTimePattern(options);
}
async hasUserIndexPattern() {
return hasUserIndexPattern({
esClient: this.esClient,
soClient: this.savedObjectsClient,
});
}
}

View file

@ -66,7 +66,7 @@ export const indexPatternsServiceFactory = ({
return new IndexPatternsCommonService({
uiSettings: new UiSettingsServerToCommon(uiSettingsClient),
savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient),
apiClient: new IndexPatternsApiServer(elasticsearchClient),
apiClient: new IndexPatternsApiServer(elasticsearchClient, savedObjectsClient),
fieldFormats: formats,
onError: (error) => {
logger.error(error);

View file

@ -26,6 +26,7 @@ import { registerGetRuntimeFieldRoute } from './routes/runtime_fields/get_runtim
import { registerDeleteRuntimeFieldRoute } from './routes/runtime_fields/delete_runtime_field';
import { registerPutRuntimeFieldRoute } from './routes/runtime_fields/put_runtime_field';
import { registerUpdateRuntimeFieldRoute } from './routes/runtime_fields/update_runtime_field';
import { registerHasUserIndexPatternRoute } from './routes/has_user_index_pattern';
export function registerRoutes(
http: HttpServiceSetup,
@ -49,6 +50,7 @@ export function registerRoutes(
registerDeleteIndexPatternRoute(router, getStartServices);
registerUpdateIndexPatternRoute(router, getStartServices);
registerManageDefaultIndexPatternRoutes(router, getStartServices);
registerHasUserIndexPatternRoute(router, getStartServices);
// Fields API
registerUpdateFieldsRoute(router, getStartServices);

View file

@ -0,0 +1,40 @@
/*
* 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 { handleErrors } from './util/handle_errors';
import { IRouter, StartServicesAccessor } from '../../../../../core/server';
import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin';
export const registerHasUserIndexPatternRoute = (
router: IRouter,
getStartServices: StartServicesAccessor<DataPluginStartDependencies, DataPluginStart>
) => {
router.get(
{
path: '/api/index_patterns/has_user_index_pattern',
validate: {},
},
router.handleLegacyErrors(
handleErrors(async (ctx, req, res) => {
const savedObjectsClient = ctx.core.savedObjects.client;
const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser;
const [, , { indexPatterns }] = await getStartServices();
const indexPatternsService = await indexPatterns.indexPatternsServiceFactory(
savedObjectsClient,
elasticsearchClient
);
return res.ok({
body: {
result: await indexPatternsService.hasUserIndexPattern(),
},
});
})
)
);
};

View file

@ -528,6 +528,7 @@ class IndexPatternsService {
// Warning: (ae-forgotten-export) The symbol "IndexPatternListItem" needs to be exported by the entry point index.d.ts
getIdsWithTitle: (refresh?: boolean) => Promise<IndexPatternListItem[]>;
getTitles: (refresh?: boolean) => Promise<string[]>;
hasUserIndexPattern(): Promise<boolean>;
refreshFields: (indexPattern: IndexPattern) => Promise<void>;
savedObjectToSpec: (savedObject: SavedObject_2<IndexPatternAttributes>) => IndexPatternSpec;
setDefault: (id: string | null, force?: boolean) => Promise<void>;

View file

@ -0,0 +1,98 @@
/*
* 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 { isUserDataIndex } from './empty_prompts';
import { MatchedItem, ResolveIndexResponseItemIndexAttrs } from '../../types';
describe('isUserDataIndex', () => {
test('system index is not data index', () => {
const systemIndexes: MatchedItem[] = [
{
name: '.apm-agent-configuration',
tags: [
{
key: 'index',
name: 'Index',
color: 'default',
},
],
item: {
name: '.apm-agent-configuration',
attributes: [ResolveIndexResponseItemIndexAttrs.OPEN],
},
},
{
name: '.kibana',
tags: [
{
key: 'alias',
name: 'Alias',
color: 'default',
},
],
item: {
name: '.kibana',
indices: ['.kibana_8.0.0_001'],
},
},
];
expect(systemIndexes.some(isUserDataIndex)).toBe(false);
});
test('data index is data index', () => {
const dataIndex: MatchedItem = {
name: 'kibana_sample_data_ecommerce',
tags: [
{
key: 'index',
name: 'Index',
color: 'default',
},
],
item: {
name: 'kibana_sample_data_ecommerce',
attributes: [ResolveIndexResponseItemIndexAttrs.OPEN],
},
};
expect(isUserDataIndex(dataIndex)).toBe(true);
});
test('fleet asset is not data index', () => {
const fleetAssetIndex: MatchedItem = {
name: 'logs-elastic_agent',
tags: [
{
key: 'data_stream',
name: 'Data stream',
color: 'primary',
},
],
item: {
name: 'logs-elastic_agent',
backing_indices: ['.ds-logs-elastic_agent-2021.08.18-000001'],
timestamp_field: '@timestamp',
},
};
expect(isUserDataIndex(fleetAssetIndex)).toBe(false);
});
test('metrics-endpoint.metadata_current_default is not data index', () => {
const fleetAssetIndex: MatchedItem = {
name: 'metrics-endpoint.metadata_current_default',
tags: [{ key: 'index', name: 'Index', color: 'default' }],
item: {
name: 'metrics-endpoint.metadata_current_default',
attributes: [ResolveIndexResponseItemIndexAttrs.OPEN],
},
};
expect(isUserDataIndex(fleetAssetIndex)).toBe(false);
});
});

View file

@ -7,6 +7,7 @@
*/
import React, { useState, useCallback, FC } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { useKibana } from '../../shared_imports';
@ -17,6 +18,7 @@ import { getIndices } from '../../lib';
import { EmptyIndexListPrompt } from './empty_index_list_prompt';
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;
@ -24,25 +26,33 @@ const removeAliases = (item: MatchedItem) =>
interface Props {
onCancel: () => void;
allSources: MatchedItem[];
hasExistingIndexPatterns: boolean;
loadSources: () => void;
}
export const EmptyPrompts: FC<Props> = ({
hasExistingIndexPatterns,
allSources,
onCancel,
children,
loadSources,
}) => {
export function isUserDataIndex(source: MatchedItem) {
// 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;
return true;
}
export const EmptyPrompts: FC<Props> = ({ allSources, onCancel, children, loadSources }) => {
const {
services: { docLinks, application, http, searchClient },
services: { docLinks, application, http, searchClient, indexPatternService },
} = useKibana<IndexPatternEditorContext>();
const [remoteClustersExist, setRemoteClustersExist] = useState<boolean>(false);
const [goToForm, setGoToForm] = useState<boolean>(false);
const hasDataIndices = allSources.some(({ name }: MatchedItem) => !name.startsWith('.'));
const hasDataIndices = allSources.some(isUserDataIndex);
const hasUserIndexPattern = useAsync(() =>
indexPatternService.hasUserIndexPattern().catch(() => true)
);
useCallback(() => {
let isMounted = true;
@ -63,7 +73,9 @@ export const EmptyPrompts: FC<Props> = ({
};
}, [http, hasDataIndices, searchClient]);
if (!hasExistingIndexPatterns && !goToForm) {
if (hasUserIndexPattern.loading) return null; // return null to prevent UI flickering while loading
if (!hasUserIndexPattern.value && !goToForm) {
if (!hasDataIndices && !remoteClustersExist) {
// load data
return (

View file

@ -338,12 +338,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
);
return (
<EmptyPrompts
onCancel={onCancel}
allSources={allSources}
hasExistingIndexPatterns={!!existingIndexPatterns.length}
loadSources={loadSources}
>
<EmptyPrompts onCancel={onCancel} allSources={allSources} loadSources={loadSources}>
<FlyoutPanels.Group flyoutClassName={'indexPatternEditorFlyout'} maxWidth={1180}>
<FlyoutPanels.Item className="fieldEditor__mainFlyoutPanel" border="right">
<EuiTitle data-test-subj="flyoutTitle">

View file

@ -81,7 +81,10 @@ export const IndexPatternTable = ({
);
setIndexPatterns(gettedIndexPatterns);
setIsLoadingIndexPatterns(false);
if (gettedIndexPatterns.length === 0) {
if (
gettedIndexPatterns.length === 0 ||
!(await data.indexPatterns.hasUserIndexPattern().catch(() => false))
) {
setShowCreateDialog(true);
}
})();

View file

@ -93,9 +93,9 @@ export const Overview: FC<Props> = ({ newsFetchResult, solutions, features }) =>
useEffect(() => {
const fetchIsNewKibanaInstance = async () => {
const resp = await indexPatternService.getTitles();
const hasUserIndexPattern = await indexPatternService.hasUserIndexPattern().catch(() => true);
setNewKibanaInstance(resp.length === 0);
setNewKibanaInstance(!hasUserIndexPattern);
};
fetchIsNewKibanaInstance();

View file

@ -0,0 +1,139 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
describe('has user index pattern API', () => {
beforeEach(async () => {
await esArchiver.emptyKibanaIndex();
if ((await es.indices.exists({ index: 'metrics-test' })).body) {
await es.indices.delete({ index: 'metrics-test' });
}
if ((await es.indices.exists({ index: 'logs-test' })).body) {
await es.indices.delete({ index: 'logs-test' });
}
});
it('should return false if no index patterns', async () => {
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(false);
});
it('should return true if has index pattern with user data', async () => {
await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index');
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'basic_index',
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
await esArchiver.unload(
'test/api_integration/fixtures/es_archiver/index_patterns/basic_index'
);
});
it('should return true if has user index pattern without data', async () => {
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'basic_index',
allowNoIndex: true,
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
});
it('should return false if only metric-* index pattern without data', async () => {
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'metrics-*',
allowNoIndex: true,
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(false);
});
it('should return true if metric-* index pattern with user data', async () => {
await es.index({
index: 'metrics-test',
body: {
foo: 'bar',
},
});
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'metrics-*',
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
});
it('should return false if only logs-* index pattern without data', async () => {
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'logs-*',
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(false);
});
it('should return true if logs-* index pattern with user data', async () => {
await es.index({
index: 'logs-test',
body: {
foo: 'bar',
},
});
await supertest.post('/api/index_patterns/index_pattern').send({
override: true,
index_pattern: {
title: 'logs-*',
},
});
const response = await supertest.get('/api/index_patterns/has_user_index_pattern');
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
});
// TODO: should setup fleet first similar to x-pack/test/fleet_functional/apps/home/welcome.ts
// but it is skipped due to flakiness https://github.com/elastic/kibana/issues/109017
it('should return false if logs-* with .ds-logs-elastic_agent only');
it('should return false if metrics-* with .ds-metrics-elastic_agent only');
});
}

View file

@ -0,0 +1,15 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('has user index pattern', () => {
loadTestFile(require.resolve('./has_user_index_pattern'));
});
}

View file

@ -18,5 +18,6 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./runtime_fields_crud'));
loadTestFile(require.resolve('./integration'));
loadTestFile(require.resolve('./deprecations'));
loadTestFile(require.resolve('./has_user_index_pattern'));
});
}