Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Telemetry] change of optin status telemetry #50158

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4860e53
initial push
Bamieh Nov 4, 2019
d7f7f60
self code review
Bamieh Nov 4, 2019
27d6b67
ignore node-fetch type
Bamieh Nov 4, 2019
0037d75
usageFetcher api
Bamieh Nov 4, 2019
4436f3f
user agent metric
Bamieh Nov 6, 2019
2715b43
merge master + add tests
Bamieh Nov 6, 2019
e0c10b6
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 6, 2019
0815769
telemetry plugin collector
Bamieh Nov 6, 2019
df1f45f
remove extra unused method
Bamieh Nov 6, 2019
dae375a
remove unused import
Bamieh Nov 6, 2019
01cfc26
type check
Bamieh Nov 6, 2019
b58d029
fix collections tests
Bamieh Nov 6, 2019
16c8e1d
pass kfetch as dep
Bamieh Nov 6, 2019
78d4d88
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 6, 2019
873640f
add ui metrics integration test for user agent
Bamieh Nov 7, 2019
5fcd303
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 7, 2019
c1926ac
dont start ui metrics when not authenticated
Bamieh Nov 7, 2019
5a62e7d
user agent count always 1
Bamieh Nov 7, 2019
64056ea
fix broken ui-metric integration tests
Bamieh Nov 7, 2019
e51c642
try using config.get
Bamieh Nov 7, 2019
f648bb9
avoid fetching configs if sending
Bamieh Nov 7, 2019
e394a78
type unknown -> string
Bamieh Nov 7, 2019
5818d94
check if fetcher is causing the issue
Bamieh Nov 7, 2019
120b832
disable ui_metric from functional tests
Bamieh Nov 7, 2019
c757cb5
enable ui_metric back again
Bamieh Nov 7, 2019
087ad8e
ignore keyword above 256
Bamieh Nov 7, 2019
076b06c
check requesting app first
Bamieh Nov 7, 2019
b954daa
clean up after all the debugging :)
Bamieh Nov 7, 2019
e5967de
fix tests
Bamieh Nov 7, 2019
7f7b7cc
always return 200 for ui metric reporting
Bamieh Nov 7, 2019
4cb7da9
remove boom import
Bamieh Nov 7, 2019
f8ab109
logout after removing role/user
Bamieh Nov 7, 2019
0db44f2
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 7, 2019
993fcd7
undo some changes in tests
Bamieh Nov 7, 2019
88c8fae
inside try catch
Bamieh Nov 8, 2019
8b0692f
prevent potential race conditions in priorities with =
Bamieh Nov 8, 2019
9a0740c
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 8, 2019
612252a
Merge branch 'master' of github.com:elastic/kibana into telemetry/ser…
Bamieh Nov 11, 2019
176ac34
use snake_case for telemetry plugin collection
Bamieh Nov 11, 2019
20f8906
refactors needed to extract cluster uuid based on collection
Bamieh Nov 11, 2019
cf7e66a
refactoring uuid getters
Bamieh Nov 12, 2019
0c13947
merge master
Bamieh Nov 13, 2019
e781b8a
revert unneeded changes from merge
Bamieh Nov 13, 2019
d8c9ce4
finish server/browser fetching
Bamieh Nov 13, 2019
33ec58a
Merge branch 'master' of github.com:elastic/kibana into telemetry/cha…
Bamieh Nov 13, 2019
e5c89c0
skip a test :(
Bamieh Nov 13, 2019
5d8bf60
Merge branch 'master' of github.com:elastic/kibana into telemetry/cha…
Bamieh Nov 13, 2019
2d7a971
skip handle_old
Bamieh Nov 13, 2019
4587784
Merge branch 'master' of github.com:elastic/kibana into telemetry/cha…
Bamieh Nov 13, 2019
3993823
merge master
Bamieh Nov 13, 2019
6a8f369
fix failing tests
Bamieh Nov 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/legacy/core_plugins/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const telemetry = (kibana: any) => {
then: Joi.valid(true).default(true),
otherwise: Joi.boolean().default(true),
}),

// `config` is used internally and not intended to be set
config: Joi.string().default(Joi.ref('$defaultConfigPath')),
banner: Joi.boolean().default(true),
Expand All @@ -66,6 +65,15 @@ const telemetry = (kibana: any) => {
`https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`
),
}),
optInStatusUrl: Joi.when('$dev', {
is: true,
then: Joi.string().default(
`https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
),
otherwise: Joi.string().default(
`https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
),
}),
sendUsageFrom: Joi.string()
.allow(['server', 'browser'])
.default('browser'),
Expand Down Expand Up @@ -101,6 +109,7 @@ const telemetry = (kibana: any) => {
config.get('telemetry.allowChangingOptInStatus') !== false &&
getXpackConfigWithDeprecated(config, 'telemetry.banner'),
telemetryOptedIn: config.get('telemetry.optIn'),
telemetryOptInStatusUrl: config.get('telemetry.optInStatusUrl'),
allowChangingOptInStatus: config.get('telemetry.allowChangingOptInStatus'),
telemetrySendUsageFrom: config.get('telemetry.sendUsageFrom'),
};
Expand Down Expand Up @@ -140,7 +149,6 @@ const telemetry = (kibana: any) => {
} as any) as CoreSetup;

telemetryPlugin(initializerContext).setup(coreSetup);

// register collectors
server.usage.collectorSet.register(createTelemetryPluginUsageCollector(server));
server.usage.collectorSet.register(createLocalizationUsageCollector(server));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const getTelemetryOptInProvider = ({ simulateFailure = false, simulateError = fa
addBasePath: (url) => url
};

const provider = new TelemetryOptInProvider(injector, chrome);
const provider = new TelemetryOptInProvider(injector, chrome, false);

if (simulateError) {
provider.setOptIn = () => Promise.reject('unhandled error');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) =>
}
};

return new TelemetryOptInProvider($injector, chrome);
return new TelemetryOptInProvider($injector, chrome, false);
};

describe('handle_old_settings', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('TelemetryOptInProvider', () => {
}
};

const provider = new TelemetryOptInProvider(mockInjector, mockChrome);
const provider = new TelemetryOptInProvider(mockInjector, mockChrome, false);
return {
provider,
mockHttp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,36 @@ import { i18n } from '@kbn/i18n';
let bannerId: string | null = null;
let currentOptInStatus = false;

export function TelemetryOptInProvider($injector: any, chrome: any) {
async function sendOptInStatus($injector: any, chrome: any, enabled: boolean) {
const telemetryOptInStatusUrl = npStart.core.injectedMetadata.getInjectedVar(
'telemetryOptInStatusUrl'
) as string;
const $http = $injector.get('$http');

try {
const optInStatus = await $http.post(
chrome.addBasePath('/api/telemetry/v2/clusters/_opt_in_stats'),
{
enabled,
unencrypted: false,
}
);

if (optInStatus.data && optInStatus.data.length) {
return await fetch(telemetryOptInStatusUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(optInStatus.data),
});
}
} catch (err) {
// Sending the ping is best-effort. Telemetry tries to send the ping once and discards it immediately if sending fails.
// swallow any errors
}
}
export function TelemetryOptInProvider($injector: any, chrome: any, sendOptInStatusChange = true) {
currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean;
const allowChangingOptInStatus = npStart.core.injectedMetadata.getInjectedVar(
'allowChangingOptInStatus'
Expand All @@ -49,6 +78,9 @@ export function TelemetryOptInProvider($injector: any, chrome: any) {

try {
await $http.post(chrome.addBasePath('/api/telemetry/v2/optIn'), { enabled });
if (sendOptInStatusChange) {
await sendOptInStatus($injector, chrome, enabled);
}
currentOptInStatus = enabled;
} catch (error) {
toastNotifications.addError(error, {
Expand Down
207 changes: 155 additions & 52 deletions src/legacy/core_plugins/telemetry/server/collection_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,186 @@
*/

import { encryptTelemetry } from './collectors';
import { CallCluster } from '../../elasticsearch';

export type EncryptedStatsGetterConfig = { unencrypted: false } & {
server: any;
start: any;
end: any;
isDev: boolean;
start: string;
end: string;
};

export type UnencryptedStatsGetterConfig = { unencrypted: true } & {
req: any;
start: any;
end: any;
isDev: boolean;
start: string;
end: string;
};

export interface ClusterDetails {
clusterUuid: string;
}

export interface StatsCollectionConfig {
callCluster: any;
callCluster: CallCluster;
server: any;
start: any;
end: any;
start: string;
end: string;
}

export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig;
export type ClusterDetailsGetter = (config: StatsCollectionConfig) => Promise<ClusterDetails[]>;
export type StatsGetter = (
clustersDetails: ClusterDetails[],
config: StatsCollectionConfig
) => Promise<any[]>;

export type StatsGetter = (config: StatsGetterConfig) => Promise<any[]>;

export const getStatsCollectionConfig = (
config: StatsGetterConfig,
esClustser: string
): StatsCollectionConfig => {
const { start, end } = config;
const server = config.unencrypted ? config.req.server : config.server;
const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster(
esClustser
);
const callCluster = config.unencrypted
? (...args: any[]) => callWithRequest(config.req, ...args)
: callWithInternalUser;

return { server, callCluster, start, end };
};
interface CollectionConfig {
title: string;
priority: number;
esCluster: string;
statsGetter: StatsGetter;
clusterDetailsGetter: ClusterDetailsGetter;
}
interface Collection {
statsGetter: StatsGetter;
clusterDetailsGetter: ClusterDetailsGetter;
esCluster: string;
title: string;
}

export class TelemetryCollectionManager {
private getterMethod?: StatsGetter;
private collectionTitle?: string;
private getterMethodPriority = -1;

public setStatsGetter = (statsGetter: StatsGetter, title: string, priority = 0) => {
if (priority > this.getterMethodPriority) {
this.getterMethod = statsGetter;
this.collectionTitle = title;
this.getterMethodPriority = priority;
private usageGetterMethodPriority = -1;
private collections: Collection[] = [];

public setCollection = (collectionConfig: CollectionConfig) => {
const { title, priority, esCluster, statsGetter, clusterDetailsGetter } = collectionConfig;

if (typeof priority !== 'number') {
throw new Error('priority must be set.');
}
if (priority === this.usageGetterMethodPriority) {
throw new Error(`A Usage Getter with the same priority is already set.`);
}
};

private getStats = async (config: StatsGetterConfig) => {
if (!this.getterMethod) {
throw Error('Stats getter method not set.');
if (priority > this.usageGetterMethodPriority) {
if (!statsGetter) {
throw Error('Stats getter method not set.');
}
if (!esCluster) {
throw Error('esCluster name must be set for the getCluster method.');
}
if (!clusterDetailsGetter) {
throw Error('Cluser UUIds method is not set.');
}

this.collections.unshift({
statsGetter,
clusterDetailsGetter,
esCluster,
title,
});
this.usageGetterMethodPriority = priority;
}
const usageData = await this.getterMethod(config);
};

if (config.unencrypted) return usageData;
return encryptTelemetry(usageData, config.isDev);
private getStatsCollectionConfig = async (
collection: Collection,
config: StatsGetterConfig
): Promise<StatsCollectionConfig> => {
const { start, end } = config;
const server = config.unencrypted ? config.req.server : config.server;
const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster(
collection.esCluster
);
const callCluster = config.unencrypted
? (...args: any[]) => callWithRequest(config.req, ...args)
: callWithInternalUser;

return { server, callCluster, start, end };
};
public getCollectionTitle = () => {
return this.collectionTitle;

private getOptInStatsForCollection = async (
collection: Collection,
optInStatus: boolean,
statsCollectionConfig: StatsCollectionConfig
) => {
const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);
return clustersDetails.map(({ clusterUuid }) => ({
cluster_uuid: clusterUuid,
opt_in_status: optInStatus,
}));
};

public getStatsGetter = () => {
if (!this.getterMethod) {
throw Error('Stats getter method not set.');
private getUsageForCollection = async (
collection: Collection,
statsCollectionConfig: StatsCollectionConfig
) => {
const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);

if (clustersDetails.length === 0) {
// don't bother doing a further lookup, try next collection.
return;
}
return {
getStats: this.getStats,
priority: this.getterMethodPriority,
title: this.collectionTitle,
};

return await collection.statsGetter(clustersDetails, statsCollectionConfig);
};

public getOptInStats = async (optInStatus: boolean, config: StatsGetterConfig) => {
for (const collection of this.collections) {
const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
try {
const optInStats = await this.getOptInStatsForCollection(
collection,
optInStatus,
statsCollectionConfig
);
if (optInStats && optInStats.length) {
statsCollectionConfig.server.log(
['debug', 'telemetry', 'collection'],
`Got Opt In stats using ${collection.title} collection.`
);
if (config.unencrypted) {
return optInStats;
}
const isDev = statsCollectionConfig.server.config().get('env.dev');
return encryptTelemetry(optInStats, isDev);
}
} catch (err) {
statsCollectionConfig.server.log(
['debu', 'telemetry', 'collection'],
`Failed to collect any opt in stats with registered collections.`
);
// swallow error to try next collection;
}
}

return [];
};
public getStats = async (config: StatsGetterConfig) => {
for (const collection of this.collections) {
const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
try {
const usageData = await this.getUsageForCollection(collection, statsCollectionConfig);
if (usageData && usageData.length) {
statsCollectionConfig.server.log(
['debug', 'telemetry', 'collection'],
`Got Usage using ${collection.title} collection.`
);
if (config.unencrypted) {
return usageData;
}
const isDev = statsCollectionConfig.server.config().get('env.dev');
return encryptTelemetry(usageData, isDev);
}
} catch (err) {
statsCollectionConfig.server.log(
['debu', 'telemetry', 'collection'],
`Failed to collect any usage with registered collections.`
);
// swallow error to try next collection;
}
}

return [];
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import { TELEMETRY_STATS_TYPE } from '../../../common/constants';
import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository';
import { getTelemetryOptIn, getTelemetryUsageFetcher } from '../../telemetry_config';
import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../telemetry_config';
export interface TelemetryUsageStats {
opt_in_status?: boolean | null;
usage_fetcher?: 'browser' | 'server';
Expand Down Expand Up @@ -53,7 +53,7 @@ export function createCollectorFetch(server: any) {
configTelemetryOptIn,
}),
last_reported: telemetrySavedObject ? telemetrySavedObject.lastReported : undefined,
usage_fetcher: getTelemetryUsageFetcher({
usage_fetcher: getTelemetrySendUsageFrom({
telemetrySavedObject,
configTelemetrySendUsageFrom,
}),
Expand Down
Loading