Skip to content

Commit db3d9fc

Browse files
Merge branch 'master' into dont-mutate-error
2 parents eb4f516 + 900a829 commit db3d9fc

36 files changed

Lines changed: 1731 additions & 300 deletions

File tree

src/legacy/core_plugins/kibana/public/kibana.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import './management';
5353
import './dev_tools';
5454
import 'ui/agg_response';
5555
import 'ui/agg_types';
56-
import { showAppRedirectNotification } from 'ui/notify';
56+
import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public';
5757
import 'leaflet';
5858
import { localApplicationService } from './local_application_service';
5959

@@ -68,4 +68,6 @@ routes.otherwise({
6868
redirectTo: `/${config.defaultAppId || 'discover'}`,
6969
});
7070

71-
uiModules.get('kibana').run(showAppRedirectNotification);
71+
uiModules
72+
.get('kibana')
73+
.run($location => showAppRedirectNotification($location, npSetup.core.notifications.toasts));

src/legacy/core_plugins/telemetry/server/collection_manager.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { encryptTelemetry } from './collectors';
2121
import { CallCluster } from '../../elasticsearch';
2222
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server';
23+
import { ESLicense } from './telemetry_collection/get_local_license';
2324

2425
export type EncryptedStatsGetterConfig = { unencrypted: false } & {
2526
server: any;
@@ -45,22 +46,38 @@ export interface StatsCollectionConfig {
4546
end: string | number;
4647
}
4748

49+
export interface BasicStatsPayload {
50+
timestamp: string;
51+
cluster_uuid: string;
52+
cluster_name: string;
53+
version: string;
54+
cluster_stats: object;
55+
collection?: string;
56+
stack_stats: object;
57+
}
58+
4859
export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig;
4960
export type ClusterDetailsGetter = (config: StatsCollectionConfig) => Promise<ClusterDetails[]>;
50-
export type StatsGetter = (
61+
export type StatsGetter<T extends BasicStatsPayload = BasicStatsPayload> = (
62+
clustersDetails: ClusterDetails[],
63+
config: StatsCollectionConfig
64+
) => Promise<T[]>;
65+
export type LicenseGetter = (
5166
clustersDetails: ClusterDetails[],
5267
config: StatsCollectionConfig
53-
) => Promise<any[]>;
68+
) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>;
5469

55-
interface CollectionConfig {
70+
interface CollectionConfig<T extends BasicStatsPayload> {
5671
title: string;
5772
priority: number;
5873
esCluster: string;
59-
statsGetter: StatsGetter;
74+
statsGetter: StatsGetter<T>;
6075
clusterDetailsGetter: ClusterDetailsGetter;
76+
licenseGetter: LicenseGetter;
6177
}
6278
interface Collection {
6379
statsGetter: StatsGetter;
80+
licenseGetter: LicenseGetter;
6481
clusterDetailsGetter: ClusterDetailsGetter;
6582
esCluster: string;
6683
title: string;
@@ -70,8 +87,15 @@ export class TelemetryCollectionManager {
7087
private usageGetterMethodPriority = -1;
7188
private collections: Collection[] = [];
7289

73-
public setCollection = (collectionConfig: CollectionConfig) => {
74-
const { title, priority, esCluster, statsGetter, clusterDetailsGetter } = collectionConfig;
90+
public setCollection = <T extends BasicStatsPayload>(collectionConfig: CollectionConfig<T>) => {
91+
const {
92+
title,
93+
priority,
94+
esCluster,
95+
statsGetter,
96+
clusterDetailsGetter,
97+
licenseGetter,
98+
} = collectionConfig;
7599

76100
if (typeof priority !== 'number') {
77101
throw new Error('priority must be set.');
@@ -88,10 +112,14 @@ export class TelemetryCollectionManager {
88112
throw Error('esCluster name must be set for the getCluster method.');
89113
}
90114
if (!clusterDetailsGetter) {
91-
throw Error('Cluser UUIds method is not set.');
115+
throw Error('Cluster UUIds method is not set.');
116+
}
117+
if (!licenseGetter) {
118+
throw Error('License getter method not set.');
92119
}
93120

94121
this.collections.unshift({
122+
licenseGetter,
95123
statsGetter,
96124
clusterDetailsGetter,
97125
esCluster,
@@ -141,7 +169,19 @@ export class TelemetryCollectionManager {
141169
return;
142170
}
143171

144-
return await collection.statsGetter(clustersDetails, statsCollectionConfig);
172+
const [stats, licenses] = await Promise.all([
173+
collection.statsGetter(clustersDetails, statsCollectionConfig),
174+
collection.licenseGetter(clustersDetails, statsCollectionConfig),
175+
]);
176+
177+
return stats.map(stat => {
178+
const license = licenses[stat.cluster_uuid];
179+
return {
180+
...(license ? { license } : {}),
181+
...stat,
182+
collectionSource: collection.title,
183+
};
184+
});
145185
};
146186

147187
public getOptInStats = async (optInStatus: boolean, config: StatsGetterConfig) => {

src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js renamed to src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,32 @@
1717
* under the License.
1818
*/
1919

20+
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
21+
22+
// This can be removed when the ES client improves the types
23+
export interface ESClusterInfo {
24+
cluster_uuid: string;
25+
cluster_name: string;
26+
version: {
27+
number: string;
28+
build_flavor: string;
29+
build_type: string;
30+
build_hash: string;
31+
build_date: string;
32+
build_snapshot?: boolean;
33+
lucene_version: string;
34+
minimum_wire_compatibility_version: string;
35+
minimum_index_compatibility_version: string;
36+
};
37+
}
38+
2039
/**
2140
* Get the cluster info from the connected cluster.
2241
*
2342
* This is the equivalent to GET /
2443
*
2544
* @param {function} callCluster The callWithInternalUser handler (exposed for testing)
26-
* @return {Promise} The response from Elasticsearch.
2745
*/
28-
export function getClusterInfo(callCluster) {
29-
return callCluster('info');
46+
export function getClusterInfo(callCluster: CallCluster) {
47+
return callCluster<ESClusterInfo>('info');
3048
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
21+
import { LicenseGetter } from '../collection_manager';
22+
23+
// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html
24+
export interface ESLicense {
25+
status: string;
26+
uid: string;
27+
type: string;
28+
issue_date: string;
29+
issue_date_in_millis: number;
30+
expiry_date: string;
31+
expirty_date_in_millis: number;
32+
max_nodes: number;
33+
issued_to: string;
34+
issuer: string;
35+
start_date_in_millis: number;
36+
}
37+
let cachedLicense: ESLicense | undefined;
38+
39+
function fetchLicense(callCluster: CallCluster, local: boolean) {
40+
return callCluster<{ license: ESLicense }>('transport.request', {
41+
method: 'GET',
42+
path: '/_license',
43+
query: {
44+
local,
45+
// For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license.
46+
accept_enterprise: 'true',
47+
},
48+
});
49+
}
50+
51+
/**
52+
* Get the cluster's license from the connected node.
53+
*
54+
* This is the equivalent of GET /_license?local=true .
55+
*
56+
* Like any X-Pack related API, X-Pack must installed for this to work.
57+
*/
58+
async function getLicenseFromLocalOrMaster(callCluster: CallCluster) {
59+
// Fetching the local license is cheaper than getting it from the master and good enough
60+
const { license } = await fetchLicense(callCluster, true).catch(async err => {
61+
if (cachedLicense) {
62+
try {
63+
// Fallback to the master node's license info
64+
const response = await fetchLicense(callCluster, false);
65+
return response;
66+
} catch (masterError) {
67+
if (masterError.statusCode === 404) {
68+
// If the master node does not have a license, we can assume there is no license
69+
cachedLicense = undefined;
70+
} else {
71+
// Any other errors from the master node, throw and do not send any telemetry
72+
throw err;
73+
}
74+
}
75+
}
76+
return { license: void 0 };
77+
});
78+
79+
if (license) {
80+
cachedLicense = license;
81+
}
82+
return license;
83+
}
84+
85+
export const getLocalLicense: LicenseGetter = async (clustersDetails, { callCluster }) => {
86+
const license = await getLicenseFromLocalOrMaster(callCluster);
87+
88+
// It should be called only with 1 cluster element in the clustersDetails array, but doing reduce just in case.
89+
return clustersDetails.reduce((acc, { clusterUuid }) => ({ ...acc, [clusterUuid]: license }), {});
90+
};

src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@
1717
* under the License.
1818
*/
1919

20-
import { get, omit } from 'lodash';
21-
// @ts-ignore
22-
import { getClusterInfo } from './get_cluster_info';
20+
import { getClusterInfo, ESClusterInfo } from './get_cluster_info';
2321
import { getClusterStats } from './get_cluster_stats';
24-
// @ts-ignore
2522
import { getKibana, handleKibanaStats, KibanaUsageStats } from './get_kibana';
2623
import { StatsGetter } from '../collection_manager';
2724

@@ -33,35 +30,32 @@ import { StatsGetter } from '../collection_manager';
3330
* @param {Object} clusterInfo Cluster info (GET /)
3431
* @param {Object} clusterStats Cluster stats (GET /_cluster/stats)
3532
* @param {Object} kibana The Kibana Usage stats
36-
* @return {Object} A combined object containing the different responses.
3733
*/
3834
export function handleLocalStats(
3935
server: any,
40-
clusterInfo: any,
41-
clusterStats: any,
36+
{ cluster_name, cluster_uuid, version }: ESClusterInfo,
37+
{ _nodes, cluster_name: clusterName, ...clusterStats }: any,
4238
kibana: KibanaUsageStats
4339
) {
4440
return {
4541
timestamp: new Date().toISOString(),
46-
cluster_uuid: get(clusterInfo, 'cluster_uuid'),
47-
cluster_name: get(clusterInfo, 'cluster_name'),
48-
version: get(clusterInfo, 'version.number'),
49-
cluster_stats: omit(clusterStats, '_nodes', 'cluster_name'),
42+
cluster_uuid,
43+
cluster_name,
44+
version: version.number,
45+
cluster_stats: clusterStats,
5046
collection: 'local',
5147
stack_stats: {
5248
kibana: handleKibanaStats(server, kibana),
5349
},
5450
};
5551
}
5652

53+
export type TelemetryLocalStats = ReturnType<typeof handleLocalStats>;
54+
5755
/**
5856
* Get statistics for all products joined by Elasticsearch cluster.
59-
*
60-
* @param {Object} server The Kibana server instance used to call ES as the internal user
61-
* @param {function} callCluster The callWithInternalUser handler (exposed for testing)
62-
* @return {Promise} The object containing the current Elasticsearch cluster's telemetry.
6357
*/
64-
export const getLocalStats: StatsGetter = async (clustersDetails, config) => {
58+
export const getLocalStats: StatsGetter<TelemetryLocalStats> = async (clustersDetails, config) => {
6559
const { server, callCluster, usageCollection } = config;
6660

6761
return await Promise.all(

src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
* under the License.
1818
*/
1919

20-
// @ts-ignore
2120
export { getLocalStats } from './get_local_stats';
2221
export { getClusterUuids } from './get_cluster_stats';
2322
export { registerCollection } from './register_collection';

src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import { telemetryCollectionManager } from '../collection_manager';
4040
import { getLocalStats } from './get_local_stats';
4141
import { getClusterUuids } from './get_cluster_stats';
42+
import { getLocalLicense } from './get_local_license';
4243

4344
export function registerCollection() {
4445
telemetryCollectionManager.setCollection({
@@ -47,5 +48,6 @@ export function registerCollection() {
4748
priority: 0,
4849
statsGetter: getLocalStats,
4950
clusterDetailsGetter: getClusterUuids,
51+
licenseGetter: getLocalLicense,
5052
});
5153
}

src/legacy/ui/public/notify/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@
1919

2020
export { fatalError, addFatalErrorCallback } from './fatal_error';
2121
export { toastNotifications } from './toasts';
22-
export { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect';
2322
export { banners } from './banners';

src/legacy/ui/public/notify/app_redirect/app_redirect.test.js renamed to src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.test.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,12 @@
1717
* under the License.
1818
*/
1919

20+
import { ILocationService } from 'angular';
21+
import { ToastsStart } from '../../../../../core/public';
2022
import { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect';
2123

2224
let isToastAdded = false;
23-
24-
jest.mock('../toasts', () => ({
25-
toastNotifications: {
26-
addDanger: () => {
27-
isToastAdded = true;
28-
},
29-
},
30-
}));
25+
const toasts: ToastsStart = {} as ToastsStart;
3126

3227
describe('addAppRedirectMessageToUrl', () => {
3328
test('adds a message to the URL', () => {
@@ -39,20 +34,29 @@ describe('addAppRedirectMessageToUrl', () => {
3934
describe('showAppRedirectNotification', () => {
4035
beforeEach(() => {
4136
isToastAdded = false;
37+
toasts.addDanger = (): any => {
38+
isToastAdded = true;
39+
};
4240
});
4341

4442
test(`adds a toast when there's a message in the URL`, () => {
45-
showAppRedirectNotification({
46-
search: () => ({ app_redirect_message: 'redirect message' }),
47-
});
43+
showAppRedirectNotification(
44+
{
45+
search: () => ({ app_redirect_message: 'redirect message' }),
46+
} as ILocationService,
47+
toasts
48+
);
4849

4950
expect(isToastAdded).toBe(true);
5051
});
5152

5253
test(`doesn't add a toast when there's no message in the URL`, () => {
53-
showAppRedirectNotification({
54-
search: () => ({ app_redirect_message: '' }),
55-
});
54+
showAppRedirectNotification(
55+
{
56+
search: () => ({ app_redirect_message: '' }),
57+
} as ILocationService,
58+
toasts
59+
);
5660

5761
expect(isToastAdded).toBe(false);
5862
});

0 commit comments

Comments
 (0)