parent
41739096a4
commit
980ec8caa3
72
.github/CODEOWNERS
vendored
Normal file
72
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
# GitHub CODEOWNERS definition
|
||||
# Identify which groups will be pinged by changes to different parts of the codebase.
|
||||
# For more info, see https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# App Architecture
|
||||
/src/plugins/data/ @elastic/kibana-app-arch
|
||||
/src/plugins/kibana_utils/ @elastic/kibana-app-arch
|
||||
|
||||
# APM
|
||||
/x-pack/legacy/plugins/apm/ @elastic/apm-ui
|
||||
|
||||
# Beats
|
||||
/x-pack/legacy/plugins/beats_management/ @elastic/beats
|
||||
|
||||
# Canvas
|
||||
/x-pack/legacy/plugins/canvas/ @elastic/kibana-canvas
|
||||
|
||||
# Code
|
||||
/x-pack/legacy/plugins/code/ @teams/code
|
||||
/x-pack/test/functional/apps/code/ @teams/code
|
||||
/x-pack/test/api_integration/apis/code/ @teams/code
|
||||
|
||||
# Infrastructure and Logs UI
|
||||
/x-pack/legacy/plugins/infra/ @elastic/infra-logs-ui
|
||||
|
||||
# Machine Learning
|
||||
/x-pack/legacy/plugins/ml/ @elastic/ml-ui
|
||||
|
||||
# Operations
|
||||
/renovate.json5 @elastic/kibana-operations
|
||||
/src/dev/ @elastic/kibana-operations
|
||||
/src/setup_node_env/ @elastic/kibana-operations
|
||||
/src/optimize/ @elastic/kibana-operations
|
||||
|
||||
# Platform
|
||||
/src/core/ @elastic/kibana-platform
|
||||
/src/legacy/server/saved_objects/ @elastic/kibana-platform
|
||||
/src/legacy/ui/public/saved_objects @elastic/kibana-platform
|
||||
|
||||
# Security
|
||||
/x-pack/legacy/plugins/security/ @elastic/kibana-security
|
||||
/x-pack/legacy/plugins/spaces/ @elastic/kibana-security
|
||||
/x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security
|
||||
/src/legacy/server/csp/ @elastic/kibana-security
|
||||
/x-pack/plugins/security/ @elastic/kibana-security
|
||||
|
||||
# Kibana Stack Services
|
||||
/packages/kbn-analytics/ @elastic/kibana-stack-services
|
||||
/src/legacy/core_plugins/ui_metric/ @elastic/kibana-stack-services
|
||||
/x-pack/legacy/plugins/telemetry @elastic/kibana-stack-services
|
||||
/x-pack/legacy/plugins/alerting @elastic/kibana-stack-services
|
||||
/x-pack/legacy/plugins/actions @elastic/kibana-stack-services
|
||||
/x-pack/legacy/plugins/task_manager @elastic/kibana-stack-services
|
||||
|
||||
# Design
|
||||
**/*.scss @elastic/kibana-design
|
||||
|
||||
# Elasticsearch UI
|
||||
/src/legacy/core_plugins/console/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/console_extensions/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/index_lifecycle_management/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/index_management/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/license_management/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/remote_clusters/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/rollup/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui
|
||||
/x-pack/legacy/plugins/watcher/ @elastic/es-ui
|
||||
|
||||
# Kibana TSVB external contractors
|
||||
/src/legacy/core_plugins/metrics/ @elastic/kibana-tsvb-external
|
21
packages/kbn-analytics/package.json
Normal file
21
packages/kbn-analytics/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@kbn/analytics",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Kibana Analytics tool",
|
||||
"main": "target/index.js",
|
||||
"types": "target/index.d.ts",
|
||||
"author": "Ahmad Bamieh <ahmadbamieh@gmail.com>",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"kbn:bootstrap": "yarn build",
|
||||
"kbn:watch": "yarn build --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kbn/dev-utils": "1.0.0"
|
||||
}
|
||||
}
|
22
packages/kbn-analytics/src/index.ts
Normal file
22
packages/kbn-analytics/src/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { createReporter, ReportHTTP, Reporter, ReporterConfig } from './reporter';
|
||||
export { UiStatsMetricType, METRIC_TYPE } from './metrics';
|
||||
export { Report, ReportManager } from './report';
|
37
packages/kbn-analytics/src/metrics/index.ts
Normal file
37
packages/kbn-analytics/src/metrics/index.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { UiStatsMetric, UiStatsMetricType } from './ui_stats';
|
||||
|
||||
export {
|
||||
UiStatsMetric,
|
||||
createUiStatsMetric,
|
||||
UiStatsMetricReport,
|
||||
UiStatsMetricType,
|
||||
} from './ui_stats';
|
||||
export { Stats } from './stats';
|
||||
|
||||
export type Metric = UiStatsMetric<UiStatsMetricType>;
|
||||
export type MetricType = keyof typeof METRIC_TYPE;
|
||||
|
||||
export enum METRIC_TYPE {
|
||||
COUNT = 'count',
|
||||
LOADED = 'loaded',
|
||||
CLICK = 'click',
|
||||
}
|
|
@ -17,4 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { registerUiMetricUsageCollector } from './collector';
|
||||
export interface Stats {
|
||||
min: number;
|
||||
max: number;
|
||||
sum: number;
|
||||
avg: number;
|
||||
}
|
53
packages/kbn-analytics/src/metrics/ui_stats.ts
Normal file
53
packages/kbn-analytics/src/metrics/ui_stats.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Stats } from './stats';
|
||||
import { METRIC_TYPE } from './';
|
||||
|
||||
export type UiStatsMetricType = METRIC_TYPE.CLICK | METRIC_TYPE.LOADED | METRIC_TYPE.COUNT;
|
||||
export interface UiStatsMetricConfig<T extends UiStatsMetricType> {
|
||||
type: T;
|
||||
appName: string;
|
||||
eventName: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface UiStatsMetric<T extends UiStatsMetricType = UiStatsMetricType> {
|
||||
type: T;
|
||||
appName: string;
|
||||
eventName: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export function createUiStatsMetric<T extends UiStatsMetricType>({
|
||||
type,
|
||||
appName,
|
||||
eventName,
|
||||
count = 1,
|
||||
}: UiStatsMetricConfig<T>): UiStatsMetric<T> {
|
||||
return { type, appName, eventName, count };
|
||||
}
|
||||
|
||||
export interface UiStatsMetricReport {
|
||||
key: string;
|
||||
appName: string;
|
||||
eventName: string;
|
||||
type: UiStatsMetricType;
|
||||
stats: Stats;
|
||||
}
|
93
packages/kbn-analytics/src/report.ts
Normal file
93
packages/kbn-analytics/src/report.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { UnreachableCaseError } from './util';
|
||||
import { Metric, Stats, UiStatsMetricReport, METRIC_TYPE } from './metrics';
|
||||
|
||||
export interface Report {
|
||||
uiStatsMetrics: {
|
||||
[key: string]: UiStatsMetricReport;
|
||||
};
|
||||
}
|
||||
|
||||
export class ReportManager {
|
||||
public report: Report;
|
||||
constructor(report?: Report) {
|
||||
this.report = report || ReportManager.createReport();
|
||||
}
|
||||
static createReport() {
|
||||
return { uiStatsMetrics: {} };
|
||||
}
|
||||
public clearReport() {
|
||||
this.report = ReportManager.createReport();
|
||||
}
|
||||
public isReportEmpty(): boolean {
|
||||
return Object.keys(this.report.uiStatsMetrics).length === 0;
|
||||
}
|
||||
private incrementStats(count: number, stats?: Stats): Stats {
|
||||
const { min = 0, max = 0, sum = 0 } = stats || {};
|
||||
const newMin = Math.min(min, count);
|
||||
const newMax = Math.max(max, count);
|
||||
const newAvg = newMin + newMax / 2;
|
||||
const newSum = sum + count;
|
||||
|
||||
return {
|
||||
min: newMin,
|
||||
max: newMax,
|
||||
avg: newAvg,
|
||||
sum: newSum,
|
||||
};
|
||||
}
|
||||
assignReports(newMetrics: Metric[]) {
|
||||
newMetrics.forEach(newMetric => this.assignReport(this.report, newMetric));
|
||||
}
|
||||
static createMetricKey(metric: Metric): string {
|
||||
switch (metric.type) {
|
||||
case METRIC_TYPE.CLICK:
|
||||
case METRIC_TYPE.LOADED:
|
||||
case METRIC_TYPE.COUNT: {
|
||||
const { appName, type, eventName } = metric;
|
||||
return `${appName}-${type}-${eventName}`;
|
||||
}
|
||||
default:
|
||||
throw new UnreachableCaseError(metric.type);
|
||||
}
|
||||
}
|
||||
private assignReport(report: Report, metric: Metric) {
|
||||
switch (metric.type) {
|
||||
case METRIC_TYPE.CLICK:
|
||||
case METRIC_TYPE.LOADED:
|
||||
case METRIC_TYPE.COUNT: {
|
||||
const { appName, type, eventName, count } = metric;
|
||||
const key = ReportManager.createMetricKey(metric);
|
||||
const existingStats = (report.uiStatsMetrics[key] || {}).stats;
|
||||
this.report.uiStatsMetrics[key] = {
|
||||
key,
|
||||
appName,
|
||||
eventName,
|
||||
type,
|
||||
stats: this.incrementStats(count, existingStats),
|
||||
};
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new UnreachableCaseError(metric.type);
|
||||
}
|
||||
}
|
||||
}
|
114
packages/kbn-analytics/src/reporter.ts
Normal file
114
packages/kbn-analytics/src/reporter.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { wrapArray } from './util';
|
||||
import { Metric, UiStatsMetric, createUiStatsMetric } from './metrics';
|
||||
|
||||
import { Storage, ReportStorageManager } from './storage';
|
||||
import { Report, ReportManager } from './report';
|
||||
|
||||
export interface ReporterConfig {
|
||||
http: ReportHTTP;
|
||||
storage?: Storage;
|
||||
checkInterval?: number;
|
||||
debug?: boolean;
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
export type ReportHTTP = (report: Report) => Promise<void>;
|
||||
|
||||
export class Reporter {
|
||||
checkInterval: number;
|
||||
private interval: any;
|
||||
private http: ReportHTTP;
|
||||
private reportManager: ReportManager;
|
||||
private storageManager: ReportStorageManager;
|
||||
private debug: boolean;
|
||||
|
||||
constructor(config: ReporterConfig) {
|
||||
const { http, storage, debug, checkInterval = 10000, storageKey = 'analytics' } = config;
|
||||
|
||||
this.http = http;
|
||||
this.checkInterval = checkInterval;
|
||||
this.interval = null;
|
||||
this.storageManager = new ReportStorageManager(storageKey, storage);
|
||||
const storedReport = this.storageManager.get();
|
||||
this.reportManager = new ReportManager(storedReport);
|
||||
this.debug = !!debug;
|
||||
}
|
||||
|
||||
private saveToReport(newMetrics: Metric[]) {
|
||||
this.reportManager.assignReports(newMetrics);
|
||||
this.storageManager.store(this.reportManager.report);
|
||||
}
|
||||
|
||||
private flushReport() {
|
||||
this.reportManager.clearReport();
|
||||
this.storageManager.store(this.reportManager.report);
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (!this.interval) {
|
||||
this.interval = setTimeout(() => {
|
||||
this.interval = null;
|
||||
this.sendReports();
|
||||
}, this.checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private log(message: any) {
|
||||
if (this.debug) {
|
||||
// eslint-disable-next-line
|
||||
console.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
public reportUiStats(
|
||||
appName: string,
|
||||
type: UiStatsMetric['type'],
|
||||
eventNames: string | string[],
|
||||
count?: number
|
||||
) {
|
||||
const metrics = wrapArray(eventNames).map(eventName => {
|
||||
if (this) this.log(`${type} Metric -> (${appName}:${eventName}):`);
|
||||
const report = createUiStatsMetric({ type, appName, eventName, count });
|
||||
this.log(report);
|
||||
return report;
|
||||
});
|
||||
this.saveToReport(metrics);
|
||||
}
|
||||
|
||||
public async sendReports() {
|
||||
if (!this.reportManager.isReportEmpty()) {
|
||||
try {
|
||||
await this.http(this.reportManager.report);
|
||||
this.flushReport();
|
||||
} catch (err) {
|
||||
this.log(`Error Sending Metrics Report ${err}`);
|
||||
}
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
export function createReporter(reportedConf: ReporterConfig) {
|
||||
const reporter = new Reporter(reportedConf);
|
||||
reporter.start();
|
||||
return reporter;
|
||||
}
|
38
packages/kbn-analytics/src/storage.ts
Normal file
38
packages/kbn-analytics/src/storage.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Report } from './report';
|
||||
|
||||
export type Storage = Map<string, any>;
|
||||
export class ReportStorageManager {
|
||||
storageKey: string;
|
||||
private storage?: Storage;
|
||||
constructor(storageKey: string, storage?: Storage) {
|
||||
this.storageKey = storageKey;
|
||||
this.storage = storage;
|
||||
}
|
||||
public get(): Report | undefined {
|
||||
if (!this.storage) return;
|
||||
return this.storage.get(this.storageKey);
|
||||
}
|
||||
public store(report: Report) {
|
||||
if (!this.storage) return;
|
||||
this.storage.set(this.storageKey, report);
|
||||
}
|
||||
}
|
28
packages/kbn-analytics/src/util.ts
Normal file
28
packages/kbn-analytics/src/util.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function wrapArray<T extends any>(subj: T | T[]): T[] {
|
||||
return Array.isArray(subj) ? subj : [subj];
|
||||
}
|
||||
|
||||
export class UnreachableCaseError extends Error {
|
||||
constructor(val: never) {
|
||||
super(`Unreachable case: ${val}`);
|
||||
}
|
||||
}
|
20
packages/kbn-analytics/tsconfig.json
Normal file
20
packages/kbn-analytics/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationDir": "./target",
|
||||
"outDir": "./target",
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"target"
|
||||
]
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
import semver from 'semver';
|
||||
import chrome from 'ui/chrome';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { trackUiMetric } from '../../../../ui_metric/public';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public';
|
||||
import {
|
||||
DashboardAppState,
|
||||
SavedDashboardPanelTo60,
|
||||
|
@ -59,7 +59,7 @@ export function migrateAppState(appState: { [key: string]: unknown } | Dashboard
|
|||
const version = (panel as SavedDashboardPanel730ToLatest).version;
|
||||
|
||||
// This will help us figure out when to remove support for older style URLs.
|
||||
trackUiMetric('DashboardPanelVersionInUrl', `${version}`);
|
||||
createUiStatsReporter('DashboardPanelVersionInUrl')(METRIC_TYPE.LOADED, `${version}`);
|
||||
|
||||
return semver.satisfies(version, '<7.3');
|
||||
});
|
||||
|
|
|
@ -16,29 +16,32 @@ the name of a dashboard they've viewed, or the timestamp of the interaction.
|
|||
|
||||
## How to use it
|
||||
|
||||
To track a user interaction, import the `trackUiMetric` helper function from UI Metric app:
|
||||
To track a user interaction, import the `createUiStatsReporter` helper function from UI Metric app:
|
||||
|
||||
```js
|
||||
import { trackUiMetric } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public';
|
||||
const trackMetric = createUiStatsReporter(`<AppName>`);
|
||||
trackMetric(METRIC_TYPE.CLICK, `<EventName>`);
|
||||
trackMetric('click', `<EventName>`);
|
||||
```
|
||||
|
||||
Metric Types:
|
||||
- `METRIC_TYPE.CLICK` for tracking clicks `trackMetric(METRIC_TYPE.CLICK, 'my_button_clicked');`
|
||||
- `METRIC_TYPE.LOADED` for a component load or page load `trackMetric(METRIC_TYPE.LOADED', 'my_component_loaded');`
|
||||
- `METRIC_TYPE.COUNT` for a tracking a misc count `trackMetric(METRIC_TYPE.COUNT', 'my_counter', <count> });`
|
||||
|
||||
Call this function whenever you would like to track a user interaction within your app. The function
|
||||
accepts two arguments, `appName` and `metricType`. These should be underscore-delimited strings.
|
||||
For example, to track the `my_metric` metric in the app `my_app` call `trackUiMetric('my_app', 'my_metric)`.
|
||||
accepts two arguments, `metricType` and `eventNames`. These should be underscore-delimited strings.
|
||||
For example, to track the `my_event` metric in the app `my_app` call `trackUiMetric(METRIC_TYPE.*, 'my_event)`.
|
||||
|
||||
That's all you need to do!
|
||||
|
||||
To track multiple metrics within a single request, provide an array of metric types, e.g. `trackUiMetric('my_app', ['my_metric1', 'my_metric2', 'my_metric3'])`.
|
||||
|
||||
**NOTE:** When called, this function sends a `POST` request to `/api/ui_metric/{appName}/{metricType}`.
|
||||
It's important that this request is sent via the `trackUiMetric` function, because it contains special
|
||||
logic for blocking the request if the user hasn't opted in to telemetry.
|
||||
To track multiple metrics within a single request, provide an array of events, e.g. `trackMetric(METRIC_TYPE.*, ['my_event1', 'my_event2', 'my_event3'])`.
|
||||
|
||||
### Disallowed characters
|
||||
|
||||
The colon and comma characters (`,`, `:`) should not be used in app name or metric types. Colons play
|
||||
a sepcial role in how metrics are stored as saved objects, and the API endpoint uses commas to delimit
|
||||
multiple metric types in a single API request.
|
||||
The colon character (`:`) should not be used in app name or event names. Colons play
|
||||
a special role in how metrics are stored as saved objects.
|
||||
|
||||
### Tracking timed interactions
|
||||
|
||||
|
@ -47,7 +50,7 @@ logic yourself. You'll also need to predefine some buckets into which the UI met
|
|||
For example, if you're timing how long it takes to create a visualization, you may decide to
|
||||
measure interactions that take less than 1 minute, 1-5 minutes, 5-20 minutes, and longer than 20 minutes.
|
||||
To track these interactions, you'd use the timed length of the interaction to determine whether to
|
||||
use a `metricType` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`.
|
||||
use a `eventName` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`.
|
||||
|
||||
## How it works
|
||||
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import JoiNamespace from 'joi';
|
||||
import { Server } from 'hapi';
|
||||
import { Legacy } from '../../../../kibana';
|
||||
import { registerUserActionRoute } from './server/routes/api/ui_metric';
|
||||
import { registerUiMetricUsageCollector } from './server/usage/index';
|
||||
import { registerUiMetricRoute } from './server/routes/api/ui_metric';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(kibana: any) {
|
||||
|
@ -28,15 +29,25 @@ export default function(kibana: any) {
|
|||
id: 'ui_metric',
|
||||
require: ['kibana', 'elasticsearch'],
|
||||
publicDir: resolve(__dirname, 'public'),
|
||||
|
||||
config(Joi: typeof JoiNamespace) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
debug: Joi.boolean().default(Joi.ref('$dev')),
|
||||
}).default();
|
||||
},
|
||||
uiExports: {
|
||||
injectDefaultVars(server: Server) {
|
||||
const config = server.config();
|
||||
return {
|
||||
debugUiMetric: config.get('ui_metric.debug'),
|
||||
};
|
||||
},
|
||||
mappings: require('./mappings.json'),
|
||||
hacks: ['plugins/ui_metric'],
|
||||
hacks: ['plugins/ui_metric/hacks/ui_metric_init'],
|
||||
},
|
||||
|
||||
init(server: Legacy.Server) {
|
||||
registerUserActionRoute(server);
|
||||
registerUiMetricUsageCollector(server);
|
||||
registerUiMetricRoute(server);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
import { createAnalyticsReporter, setTelemetryReporter } from '../services/telemetry_analytics';
|
||||
|
||||
function telemetryInit($injector: any) {
|
||||
const localStorage = $injector.get('localStorage');
|
||||
const debug = chrome.getInjected('debugUiMetric');
|
||||
const $http = $injector.get('$http');
|
||||
const basePath = chrome.getBasePath();
|
||||
const uiReporter = createAnalyticsReporter({ localStorage, $http, basePath, debug });
|
||||
setTelemetryReporter(uiReporter);
|
||||
}
|
||||
|
||||
uiModules.get('kibana').run(telemetryInit);
|
|
@ -17,39 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { getCanTrackUiMetrics } from 'ui/ui_metric';
|
||||
import { API_BASE_PATH } from '../common';
|
||||
|
||||
let _http: any;
|
||||
|
||||
uiModules.get('kibana').run(($http: any) => {
|
||||
_http = $http;
|
||||
});
|
||||
|
||||
function createErrorMessage(subject: string): any {
|
||||
const message =
|
||||
`trackUiMetric was called with ${subject}, which is not allowed to contain a colon. ` +
|
||||
`Colons play a special role in how metrics are saved as stored objects`;
|
||||
return new Error(message);
|
||||
}
|
||||
|
||||
export function trackUiMetric(appName: string, metricType: string | string[]) {
|
||||
if (!getCanTrackUiMetrics()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (appName.includes(':')) {
|
||||
throw createErrorMessage(`app name '${appName}'`);
|
||||
}
|
||||
|
||||
if (metricType.includes(':')) {
|
||||
throw createErrorMessage(`metric type ${metricType}`);
|
||||
}
|
||||
|
||||
const metricTypes = Array.isArray(metricType) ? metricType.join(',') : metricType;
|
||||
const uri = chrome.addBasePath(`${API_BASE_PATH}/${appName}/${metricTypes}`);
|
||||
_http.post(uri);
|
||||
}
|
||||
export { createUiStatsReporter } from './services/telemetry_analytics';
|
||||
export { METRIC_TYPE } from '@kbn/analytics';
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { createReporter, Reporter, UiStatsMetricType } from '@kbn/analytics';
|
||||
|
||||
let telemetryReporter: Reporter;
|
||||
|
||||
export const setTelemetryReporter = (aTelemetryReporter: Reporter): void => {
|
||||
telemetryReporter = aTelemetryReporter;
|
||||
};
|
||||
|
||||
export const getTelemetryReporter = () => {
|
||||
return telemetryReporter;
|
||||
};
|
||||
|
||||
export const createUiStatsReporter = (appName: string) => (
|
||||
type: UiStatsMetricType,
|
||||
eventNames: string | string[],
|
||||
count?: number
|
||||
): void => {
|
||||
if (telemetryReporter) {
|
||||
return telemetryReporter.reportUiStats(appName, type, eventNames, count);
|
||||
}
|
||||
};
|
||||
|
||||
interface AnalyicsReporterConfig {
|
||||
localStorage: any;
|
||||
basePath: string;
|
||||
debug: boolean;
|
||||
$http: ng.IHttpService;
|
||||
}
|
||||
|
||||
export function createAnalyticsReporter(config: AnalyicsReporterConfig) {
|
||||
const { localStorage, basePath, $http, debug } = config;
|
||||
|
||||
return createReporter({
|
||||
debug,
|
||||
storage: localStorage,
|
||||
async http(report) {
|
||||
const url = `${basePath}/api/telemetry/report`;
|
||||
await $http.post(url, { report });
|
||||
},
|
||||
});
|
||||
}
|
|
@ -17,36 +17,65 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Joi from 'joi';
|
||||
import Boom from 'boom';
|
||||
import { Report } from '@kbn/analytics';
|
||||
import { Server } from 'hapi';
|
||||
import { API_BASE_PATH } from '../../../common';
|
||||
|
||||
export const registerUserActionRoute = (server: Server) => {
|
||||
/*
|
||||
* Increment a count on an object representing a specific interaction with the UI.
|
||||
*/
|
||||
export async function storeReport(server: any, report: Report) {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
||||
const metricKeys = Object.keys(report.uiStatsMetrics);
|
||||
return Promise.all(
|
||||
metricKeys.map(async key => {
|
||||
const metric = report.uiStatsMetrics[key];
|
||||
const { appName, eventName } = metric;
|
||||
const savedObjectId = `${appName}:${eventName}`;
|
||||
return internalRepository.incrementCounter('ui-metric', savedObjectId, 'count');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function registerUiMetricRoute(server: Server) {
|
||||
server.route({
|
||||
path: `${API_BASE_PATH}/{appName}/{metricTypes}`,
|
||||
method: 'POST',
|
||||
handler: async (request: any) => {
|
||||
const { appName, metricTypes } = request.params;
|
||||
path: '/api/telemetry/report',
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
report: Joi.object({
|
||||
uiStatsMetrics: Joi.object()
|
||||
.pattern(
|
||||
/.*/,
|
||||
Joi.object({
|
||||
key: Joi.string().required(),
|
||||
type: Joi.string().required(),
|
||||
appName: Joi.string().required(),
|
||||
eventName: Joi.string().required(),
|
||||
stats: Joi.object({
|
||||
min: Joi.number(),
|
||||
sum: Joi.number(),
|
||||
max: Joi.number(),
|
||||
avg: Joi.number(),
|
||||
}).allow(null),
|
||||
})
|
||||
)
|
||||
.allow(null),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (req: any, h: any) => {
|
||||
const { report } = req.payload;
|
||||
|
||||
try {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
||||
const incrementRequests = metricTypes.split(',').map((metricType: string) => {
|
||||
const savedObjectId = `${appName}:${metricType}`;
|
||||
// This object is created if it doesn't already exist.
|
||||
return internalRepository.incrementCounter('ui-metric', savedObjectId, 'count');
|
||||
});
|
||||
|
||||
await Promise.all(incrementRequests);
|
||||
await storeReport(server, report);
|
||||
return {};
|
||||
} catch (error) {
|
||||
return new Boom('Something went wrong', { statusCode: error.status });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const UI_METRIC_USAGE_TYPE = 'ui_metric';
|
||||
|
||||
export function registerUiMetricUsageCollector(server: any) {
|
||||
const collector = server.usage.collectorSet.makeUsageCollector({
|
||||
type: UI_METRIC_USAGE_TYPE,
|
||||
fetch: async (callCluster: any) => {
|
||||
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
const savedObjectsClient = new SavedObjectsClient(internalRepository);
|
||||
|
||||
const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({
|
||||
type: 'ui-metric',
|
||||
fields: ['count'],
|
||||
});
|
||||
|
||||
const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => {
|
||||
const {
|
||||
id,
|
||||
attributes: { count },
|
||||
} = rawUiMetric;
|
||||
|
||||
const [appName, metricType] = id.split(':');
|
||||
|
||||
if (!accum[appName]) {
|
||||
accum[appName] = [];
|
||||
}
|
||||
|
||||
const pair = { key: metricType, value: count };
|
||||
accum[appName].push(pair);
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
return uiMetricsByAppName;
|
||||
},
|
||||
isReady: () => true,
|
||||
});
|
||||
|
||||
server.usage.collectorSet.register(collector);
|
||||
}
|
|
@ -18,16 +18,33 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { ReportManager } from '@kbn/analytics';
|
||||
|
||||
export default function ({ getService }) {
|
||||
const supertest = getService('supertest');
|
||||
const es = getService('es');
|
||||
|
||||
const createMetric = (eventName) => ({
|
||||
key: ReportManager.createMetricKey({ appName: 'myApp', type: 'click', eventName }),
|
||||
eventName,
|
||||
appName: 'myApp',
|
||||
type: 'click',
|
||||
stats: { sum: 1, avg: 1, min: 1, max: 1 },
|
||||
});
|
||||
|
||||
describe('ui_metric API', () => {
|
||||
const uiStatsMetric = createMetric('myEvent');
|
||||
const report = {
|
||||
uiStatsMetrics: {
|
||||
[uiStatsMetric.key]: uiStatsMetric,
|
||||
}
|
||||
};
|
||||
it('increments the count field in the document defined by the {app}/{action_type} path', async () => {
|
||||
await supertest
|
||||
.post('/api/ui_metric/myApp/myAction')
|
||||
.post('/api/telemetry/report')
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.set('content-type', 'application/json')
|
||||
.send({ report })
|
||||
.expect(200);
|
||||
|
||||
return es.search({
|
||||
|
@ -35,14 +52,24 @@ export default function ({ getService }) {
|
|||
q: 'type:user-action',
|
||||
}).then(response => {
|
||||
const ids = response.hits.hits.map(({ _id }) => _id);
|
||||
expect(ids.includes('user-action:myApp:myAction'));
|
||||
expect(ids.includes('user-action:myApp:myEvent'));
|
||||
});
|
||||
});
|
||||
|
||||
it('supports comma-delimited action types', async () => {
|
||||
it('supports multiple events', async () => {
|
||||
const uiStatsMetric1 = createMetric('myEvent1');
|
||||
const uiStatsMetric2 = createMetric('myEvent2');
|
||||
const report = {
|
||||
uiStatsMetrics: {
|
||||
[uiStatsMetric1.key]: uiStatsMetric1,
|
||||
[uiStatsMetric2.key]: uiStatsMetric2,
|
||||
}
|
||||
};
|
||||
await supertest
|
||||
.post('/api/ui_metric/myApp/myAction1,myAction2')
|
||||
.post('/api/telemetry/report')
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.set('content-type', 'application/json')
|
||||
.send({ report })
|
||||
.expect(200);
|
||||
|
||||
return es.search({
|
||||
|
@ -50,8 +77,8 @@ export default function ({ getService }) {
|
|||
q: 'type:user-action',
|
||||
}).then(response => {
|
||||
const ids = response.hits.hits.map(({ _id }) => _id);
|
||||
expect(ids.includes('user-action:myApp:myAction1'));
|
||||
expect(ids.includes('user-action:myApp:myAction2'));
|
||||
expect(ids.includes('user-action:myApp:myEvent1'));
|
||||
expect(ids.includes('user-action:myApp:myEvent2'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
WorkpadLoadedMetric,
|
||||
WorkpadLoadedWithErrorsMetric,
|
||||
} from '../workpad_telemetry';
|
||||
import { METRIC_TYPE } from '../../../../lib/ui_metric';
|
||||
|
||||
const trackMetric = jest.fn();
|
||||
const Component = withUnconnectedElementsLoadedTelemetry(() => <div />, trackMetric);
|
||||
|
@ -83,7 +84,7 @@ describe('Elements Loaded Telemetry', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
expect(trackMetric).toBeCalledWith(WorkpadLoadedMetric);
|
||||
expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric);
|
||||
});
|
||||
|
||||
it('only tracks loaded once', () => {
|
||||
|
@ -154,7 +155,10 @@ describe('Elements Loaded Telemetry', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
expect(trackMetric).toBeCalledWith([WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]);
|
||||
expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, [
|
||||
WorkpadLoadedMetric,
|
||||
WorkpadLoadedWithErrorsMetric,
|
||||
]);
|
||||
});
|
||||
|
||||
it('tracks when the workpad changes and is loaded', () => {
|
||||
|
@ -198,7 +202,7 @@ describe('Elements Loaded Telemetry', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
expect(trackMetric).toBeCalledWith(WorkpadLoadedMetric);
|
||||
expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric);
|
||||
});
|
||||
|
||||
it('does not track if workpad has no elements', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
// @ts-ignore: Local Untyped
|
||||
import { trackCanvasUiMetric } from '../../../lib/ui_metric';
|
||||
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
|
||||
// @ts-ignore: Local Untyped
|
||||
import { getElementCounts } from '../../../state/selectors/workpad';
|
||||
// @ts-ignore: Local Untyped
|
||||
|
@ -79,7 +79,7 @@ function areAllElementsInResolvedArgs(workpad: Workpad, resolvedArgs: ResolvedAr
|
|||
|
||||
export const withUnconnectedElementsLoadedTelemetry = function<P extends object>(
|
||||
Component: React.ComponentType<P>,
|
||||
trackMetric: (metric: string | string[]) => void = trackCanvasUiMetric
|
||||
trackMetric = trackCanvasUiMetric
|
||||
): React.SFC<P & ElementsLoadedTelemetryProps> {
|
||||
return function ElementsLoadedTelemetry(
|
||||
props: P & ElementsLoadedTelemetryProps
|
||||
|
@ -117,11 +117,10 @@ export const withUnconnectedElementsLoadedTelemetry = function<P extends object>
|
|||
resolvedArgsAreForWorkpad
|
||||
) {
|
||||
if (telemetryElementCounts.error > 0) {
|
||||
trackMetric([WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]);
|
||||
trackMetric(METRIC_TYPE.LOADED, [WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]);
|
||||
} else {
|
||||
trackMetric(WorkpadLoadedMetric);
|
||||
trackMetric(METRIC_TYPE.LOADED, WorkpadLoadedMetric);
|
||||
}
|
||||
|
||||
setHasReported(true);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ import { notify } from '../../lib/notify';
|
|||
import { selectToplevelNodes } from '../../state/actions/transient';
|
||||
import { insertNodes, addElement } from '../../state/actions/elements';
|
||||
import { getSelectedPage } from '../../state/selectors/workpad';
|
||||
import { trackCanvasUiMetric } from '../../lib/ui_metric';
|
||||
import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric';
|
||||
import { ElementTypes as Component } from './element_types';
|
||||
|
||||
const customElementAdded = 'elements-custom-added';
|
||||
|
@ -51,7 +51,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
selectToplevelNodes(clonedNodes); // then select the cloned node(s)
|
||||
}
|
||||
onClose();
|
||||
trackCanvasUiMetric(customElementAdded);
|
||||
trackCanvasUiMetric(METRIC_TYPE.LOADED, customElementAdded);
|
||||
},
|
||||
// custom element search
|
||||
findCustomElements: async text => {
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
getPages,
|
||||
getWorkpad,
|
||||
} from '../../../state/selectors/workpad';
|
||||
import { trackCanvasUiMetric } from '../../../lib/ui_metric';
|
||||
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
|
||||
import {
|
||||
LAUNCHED_FULLSCREEN,
|
||||
LAUNCHED_FULLSCREEN_AUTOPLAY,
|
||||
|
@ -54,6 +54,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
|
||||
if (value === true) {
|
||||
trackCanvasUiMetric(
|
||||
METRIC_TYPE.COUNT,
|
||||
stateProps.autoplayEnabled
|
||||
? [LAUNCHED_FULLSCREEN, LAUNCHED_FULLSCREEN_AUTOPLAY]
|
||||
: LAUNCHED_FULLSCREEN
|
||||
|
|
13
x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts
Normal file
13
x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
export const trackCanvasUiMetric = createUiStatsReporter('canvas');
|
||||
export { METRIC_TYPE };
|
|
@ -19,10 +19,6 @@ jest.mock('ui/index_patterns', () => {
|
|||
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({
|
||||
trackUiMetric: jest.fn(),
|
||||
}));
|
||||
|
||||
const { setup } = pageHelpers.autoFollowPatternList;
|
||||
|
||||
describe('<AutoFollowPatternList />', () => {
|
||||
|
|
|
@ -19,10 +19,6 @@ jest.mock('ui/index_patterns', () => {
|
|||
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({
|
||||
trackUiMetric: jest.fn(),
|
||||
}));
|
||||
|
||||
const { setup } = pageHelpers.followerIndexList;
|
||||
|
||||
describe('<FollowerIndicesList />', () => {
|
||||
|
|
|
@ -28,10 +28,6 @@ jest.mock('ui/index_patterns', () => {
|
|||
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({
|
||||
trackUiMetric: jest.fn(),
|
||||
}));
|
||||
|
||||
const { setup } = pageHelpers.home;
|
||||
|
||||
describe('<CrossClusterReplicationHome />', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
|
||||
import routing from '../../../services/routing';
|
||||
import { extractQueryParams } from '../../../services/query_params';
|
||||
import { trackUiMetric } from '../../../services/track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric';
|
||||
import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants';
|
||||
import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components';
|
||||
import { AutoFollowPatternTable, DetailPanel } from './components';
|
||||
|
@ -60,7 +60,7 @@ export class AutoFollowPatternList extends PureComponent {
|
|||
componentDidMount() {
|
||||
const { loadAutoFollowPatterns, loadAutoFollowStats, selectAutoFollowPattern, history } = this.props;
|
||||
|
||||
trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD);
|
||||
loadAutoFollowPatterns();
|
||||
loadAutoFollowStats();
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants';
|
||||
import { AutoFollowPatternDeleteProvider } from '../../../../../components';
|
||||
import routing from '../../../../../services/routing';
|
||||
import { trackUiMetric } from '../../../../../services/track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../../../services/track_ui_metric';
|
||||
|
||||
export class AutoFollowPatternTable extends PureComponent {
|
||||
static propTypes = {
|
||||
|
@ -77,7 +77,7 @@ export class AutoFollowPatternTable extends PureComponent {
|
|||
return (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK);
|
||||
trackUiMetric(METRIC_TYPE.CLICK, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK);
|
||||
selectAutoFollowPattern(name);
|
||||
}}
|
||||
data-test-subj="autoFollowPatternLink"
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
FollowerIndexUnfollowProvider
|
||||
} from '../../../../../components';
|
||||
import routing from '../../../../../services/routing';
|
||||
import { trackUiMetric } from '../../../../../services/track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../../../services/track_ui_metric';
|
||||
import { ContextMenu } from '../context_menu';
|
||||
|
||||
export class FollowerIndicesTable extends PureComponent {
|
||||
|
@ -191,7 +191,7 @@ export class FollowerIndicesTable extends PureComponent {
|
|||
return (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
trackUiMetric(UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK);
|
||||
trackUiMetric(METRIC_TYPE.CLICK, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK);
|
||||
selectFollowerIndex(name);
|
||||
}}
|
||||
data-test-subj="followerIndexLink"
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
|
||||
import routing from '../../../services/routing';
|
||||
import { extractQueryParams } from '../../../services/query_params';
|
||||
import { trackUiMetric } from '../../../services/track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric';
|
||||
import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants';
|
||||
import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components';
|
||||
import { FollowerIndicesTable, DetailPanel } from './components';
|
||||
|
@ -58,7 +58,7 @@ export class FollowerIndicesList extends PureComponent {
|
|||
componentDidMount() {
|
||||
const { loadFollowerIndices, selectFollowerIndex, history } = this.props;
|
||||
|
||||
trackUiMetric(UIM_FOLLOWER_INDEX_LIST_LOAD);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, UIM_FOLLOWER_INDEX_LIST_LOAD);
|
||||
loadFollowerIndices();
|
||||
|
||||
// Select the pattern in the URL query params
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { UIM_APP_NAME } from '../constants';
|
||||
|
||||
export function trackUiMetric(actionType) {
|
||||
track(UIM_APP_NAME, actionType);
|
||||
}
|
||||
|
||||
export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME);
|
||||
export { METRIC_TYPE };
|
||||
/**
|
||||
* Transparently return provided request Promise, while allowing us to track
|
||||
* a successful completion of the request.
|
||||
|
@ -18,7 +16,7 @@ export function trackUiMetric(actionType) {
|
|||
export function trackUserRequest(request, actionType) {
|
||||
// Only track successful actions.
|
||||
return request.then(response => {
|
||||
trackUiMetric(actionType);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, actionType);
|
||||
// We return the response immediately without waiting for the tracking request to resolve,
|
||||
// to avoid adding additional latency.
|
||||
return response;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { PolicyTable } from './sections/policy_table';
|
|||
import { trackUiMetric } from './services';
|
||||
|
||||
export const App = () => {
|
||||
useEffect(() => trackUiMetric(UIM_APP_LOAD), []);
|
||||
useEffect(() => trackUiMetric('loaded', UIM_APP_LOAD), []);
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
|
|
|
@ -178,7 +178,7 @@ export class PolicyTable extends Component {
|
|||
className="policyTable__link"
|
||||
data-test-subj="policyTablePolicyNameLink"
|
||||
href={getPolicyPath(value)}
|
||||
onClick={() => trackUiMetric(UIM_EDIT_CLICK)}
|
||||
onClick={() => trackUiMetric('click', UIM_EDIT_CLICK)}
|
||||
>
|
||||
{value}
|
||||
</EuiLink>
|
||||
|
|
|
@ -58,34 +58,34 @@ export async function savePolicy(policy, httpClient = getHttpClient()) {
|
|||
export async function deletePolicy(policyName, httpClient = getHttpClient()) {
|
||||
const response = await httpClient.delete(`${apiPrefix}/policies/${encodeURIComponent(policyName)}`);
|
||||
// Only track successful actions.
|
||||
trackUiMetric(UIM_POLICY_DELETE);
|
||||
trackUiMetric('count', UIM_POLICY_DELETE);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const retryLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => {
|
||||
const response = await httpClient.post(`${apiPrefix}/index/retry`, { indexNames });
|
||||
// Only track successful actions.
|
||||
trackUiMetric(UIM_INDEX_RETRY_STEP);
|
||||
trackUiMetric('count', UIM_INDEX_RETRY_STEP);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => {
|
||||
const response = await httpClient.post(`${apiPrefix}/index/remove`, { indexNames });
|
||||
// Only track successful actions.
|
||||
trackUiMetric(UIM_POLICY_DETACH_INDEX);
|
||||
trackUiMetric('count', UIM_POLICY_DETACH_INDEX);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const addLifecyclePolicyToIndex = async (body, httpClient = getHttpClient()) => {
|
||||
const response = await httpClient.post(`${apiPrefix}/index/add`, body);
|
||||
// Only track successful actions.
|
||||
trackUiMetric(UIM_POLICY_ATTACH_INDEX);
|
||||
trackUiMetric('count', UIM_POLICY_ATTACH_INDEX);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const addLifecyclePolicyToTemplate = async (body, httpClient = getHttpClient()) => {
|
||||
const response = await httpClient.post(`${apiPrefix}/template`, body);
|
||||
// Only track successful actions.
|
||||
trackUiMetric(UIM_POLICY_ATTACH_INDEX_TEMPLATE);
|
||||
trackUiMetric('count', UIM_POLICY_ATTACH_INDEX_TEMPLATE);
|
||||
return response.data;
|
||||
};
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter } from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
import {
|
||||
UIM_APP_NAME,
|
||||
|
@ -29,9 +28,7 @@ import {
|
|||
defaultHotPhase,
|
||||
} from '../store/defaults';
|
||||
|
||||
export function trackUiMetric(metricType) {
|
||||
track(UIM_APP_NAME, metricType);
|
||||
}
|
||||
export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME);
|
||||
|
||||
export function getUiMetricsForPhases(phases) {
|
||||
const phaseUiMetrics = [{
|
||||
|
|
|
@ -32,7 +32,7 @@ export const saveLifecyclePolicy = (lifecycle, isNew) => async () => {
|
|||
|
||||
const uiMetrics = getUiMetricsForPhases(lifecycle.phases);
|
||||
uiMetrics.push(isNew ? UIM_POLICY_CREATE : UIM_POLICY_UPDATE);
|
||||
trackUiMetric(uiMetrics);
|
||||
trackUiMetric('count', uiMetrics);
|
||||
|
||||
const message = i18n.translate('xpack.indexLifecycleMgmt.editPolicy.successfulSaveMessage',
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IndexManagementHome } from './sections/home';
|
|||
import { trackUiMetric } from './services';
|
||||
|
||||
export const App = () => {
|
||||
useEffect(() => trackUiMetric(UIM_APP_LOAD), []);
|
||||
useEffect(() => trackUiMetric('loaded', UIM_APP_LOAD), []);
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
|
|
|
@ -220,7 +220,7 @@ export class IndexTable extends Component {
|
|||
className="indTable__link"
|
||||
data-test-subj="indexTableIndexNameLink"
|
||||
onClick={() => {
|
||||
trackUiMetric(UIM_SHOW_DETAILS_CLICK);
|
||||
trackUiMetric('click', UIM_SHOW_DETAILS_CLICK);
|
||||
openDetailPanel(value);
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { SectionError, SectionLoading } from '../../../components';
|
|||
import { TemplatesTable } from './templates_table';
|
||||
import { loadIndexTemplates } from '../../../services/api';
|
||||
import { Template } from '../../../../common/types';
|
||||
import { trackUiMetric } from '../../../services/track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric';
|
||||
import { UIM_TEMPLATE_LIST_LOAD } from '../../../../common/constants';
|
||||
|
||||
export const TemplatesList: React.FunctionComponent = () => {
|
||||
|
@ -38,7 +38,7 @@ export const TemplatesList: React.FunctionComponent = () => {
|
|||
|
||||
// Track component loaded
|
||||
useEffect(() => {
|
||||
trackUiMetric(UIM_TEMPLATE_LIST_LOAD);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, UIM_TEMPLATE_LIST_LOAD);
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
|
||||
import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants';
|
||||
|
||||
import { trackUiMetric } from './track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from './track_ui_metric';
|
||||
import { useRequest, sendRequest } from './use_request';
|
||||
import { Template } from '../../common/types';
|
||||
|
||||
|
@ -67,8 +67,8 @@ export async function closeIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/close`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_CLOSE_MANY : UIM_INDEX_CLOSE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_CLOSE_MANY : UIM_INDEX_CLOSE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,8 @@ export async function deleteIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/delete`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_DELETE_MANY : UIM_INDEX_DELETE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_DELETE_MANY : UIM_INDEX_DELETE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -89,8 +89,8 @@ export async function openIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/open`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_OPEN_MANY : UIM_INDEX_OPEN;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_OPEN_MANY : UIM_INDEX_OPEN;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -100,8 +100,8 @@ export async function refreshIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/refresh`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_REFRESH_MANY : UIM_INDEX_REFRESH;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_REFRESH_MANY : UIM_INDEX_REFRESH;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -111,8 +111,8 @@ export async function flushIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/flush`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_FLUSH_MANY : UIM_INDEX_FLUSH;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_FLUSH_MANY : UIM_INDEX_FLUSH;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -123,8 +123,8 @@ export async function forcemergeIndices(indices: string[], maxNumSegments: strin
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/forcemerge`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_FORCE_MERGE_MANY : UIM_INDEX_FORCE_MERGE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_FORCE_MERGE_MANY : UIM_INDEX_FORCE_MERGE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -134,8 +134,8 @@ export async function clearCacheIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/clear_cache`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_CLEAR_CACHE_MANY : UIM_INDEX_CLEAR_CACHE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_CLEAR_CACHE_MANY : UIM_INDEX_CLEAR_CACHE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
export async function freezeIndices(indices: string[]) {
|
||||
|
@ -144,8 +144,8 @@ export async function freezeIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/freeze`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_FREEZE_MANY : UIM_INDEX_FREEZE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_FREEZE_MANY : UIM_INDEX_FREEZE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
export async function unfreezeIndices(indices: string[]) {
|
||||
|
@ -154,8 +154,8 @@ export async function unfreezeIndices(indices: string[]) {
|
|||
};
|
||||
const response = await httpClient.post(`${apiPrefix}/indices/unfreeze`, body);
|
||||
// Only track successful requests.
|
||||
const actionType = indices.length > 1 ? UIM_INDEX_UNFREEZE_MANY : UIM_INDEX_UNFREEZE;
|
||||
trackUiMetric(actionType);
|
||||
const eventName = indices.length > 1 ? UIM_INDEX_UNFREEZE_MANY : UIM_INDEX_UNFREEZE;
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,7 @@ export async function loadIndexSettings(indexName: string) {
|
|||
export async function updateIndexSettings(indexName: string, settings: object) {
|
||||
const response = await httpClient.put(`${apiPrefix}/settings/${indexName}`, settings);
|
||||
// Only track successful requests.
|
||||
trackUiMetric(UIM_UPDATE_SETTINGS);
|
||||
trackUiMetric(METRIC_TYPE.COUNT, UIM_UPDATE_SETTINGS);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { UIM_APP_NAME } from '../../common/constants';
|
||||
|
||||
export function trackUiMetric(metricType: string) {
|
||||
track(UIM_APP_NAME, metricType);
|
||||
}
|
||||
export { METRIC_TYPE };
|
||||
export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getHttpClient } from './api';
|
||||
import { trackUiMetric } from './track_ui_metric';
|
||||
import { trackUiMetric, METRIC_TYPE } from './track_ui_metric';
|
||||
|
||||
interface SendRequest {
|
||||
path?: string;
|
||||
|
@ -35,7 +35,7 @@ export const sendRequest = async ({
|
|||
|
||||
// Track successful request
|
||||
if (uimActionType) {
|
||||
trackUiMetric(uimActionType);
|
||||
trackUiMetric(METRIC_TYPE.COUNT, uimActionType);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -54,7 +54,7 @@ export const detailPanel = handleActions(
|
|||
};
|
||||
|
||||
if (panelTypeToUiMetricMap[panelType]) {
|
||||
trackUiMetric(panelTypeToUiMetricMap[panelType]);
|
||||
trackUiMetric('count', panelTypeToUiMetricMap[panelType]);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
/**
|
||||
* Note: The UI Metric plugin will take care of sending this data to the telemetry server.
|
||||
* You can find these metrics stored at:
|
||||
* stack_stats.kibana.plugins.ui_metric.{app}.{metric}(__delayed_{n}ms)?
|
||||
* which will be an array of objects each containing a key, representing the metric, and
|
||||
* a value, which will be a counter
|
||||
*/
|
||||
|
||||
type ObservabilityApp = 'infra_metrics' | 'infra_logs' | 'apm' | 'uptime';
|
||||
|
||||
const trackerCache = new Map<string, ReturnType<typeof createUiStatsReporter>>();
|
||||
|
||||
function getTrackerForApp(app: string) {
|
||||
const cached = trackerCache.get(app);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const tracker = createUiStatsReporter(app);
|
||||
trackerCache.set(app, tracker);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
interface TrackOptions {
|
||||
app: ObservabilityApp;
|
||||
metricType?: METRIC_TYPE;
|
||||
delay?: number; // in ms
|
||||
}
|
||||
type EffectDeps = unknown[];
|
||||
|
||||
type TrackMetricOptions = TrackOptions & { metric: string };
|
||||
|
||||
export { METRIC_TYPE };
|
||||
export function useTrackMetric(
|
||||
{ app, metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions,
|
||||
effectDependencies: EffectDeps = []
|
||||
) {
|
||||
useEffect(() => {
|
||||
let decoratedMetric = metric;
|
||||
if (delay > 0) {
|
||||
decoratedMetric += `__delayed_${delay}ms`;
|
||||
}
|
||||
const trackUiMetric = getTrackerForApp(app);
|
||||
const id = setTimeout(() => trackUiMetric(metricType, decoratedMetric), Math.max(delay, 0));
|
||||
return () => clearTimeout(id);
|
||||
}, effectDependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* useTrackPageview is a convenience wrapper for tracking a pageview
|
||||
* Its metrics will be found at:
|
||||
* stack_stats.kibana.plugins.ui_metric.{app}.pageview__{path}(__delayed_{n}ms)?
|
||||
*/
|
||||
type TrackPageviewProps = TrackOptions & { path: string };
|
||||
|
||||
export function useTrackPageview(
|
||||
{ path, ...rest }: TrackPageviewProps,
|
||||
effectDependencies: EffectDeps = []
|
||||
) {
|
||||
useTrackMetric({ ...rest, metric: `pageview__${path}` }, effectDependencies);
|
||||
}
|
|
@ -13,7 +13,9 @@ import { fatalError, toastNotifications } from 'ui/notify'; // eslint-disable-li
|
|||
import { init as initBreadcrumb } from '../../../public/app/services/breadcrumb';
|
||||
import { init as initHttp } from '../../../public/app/services/http';
|
||||
import { init as initNotification } from '../../../public/app/services/notification';
|
||||
import { init as initUiMetric } from '../../../public/app/services/ui_metric';
|
||||
import { init as initHttpRequests } from './http_requests';
|
||||
import { createUiStatsReporter } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
export const setupEnvironment = () => {
|
||||
chrome.breadcrumbs = {
|
||||
|
@ -23,6 +25,7 @@ export const setupEnvironment = () => {
|
|||
initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path);
|
||||
initBreadcrumb(() => {}, MANAGEMENT_BREADCRUMB);
|
||||
initNotification(toastNotifications, fatalError);
|
||||
initUiMetric(createUiStatsReporter);
|
||||
|
||||
const { server, httpRequestsMockHelpers } = initHttpRequests();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
|||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
|
||||
import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants';
|
||||
import { registerRouter, setUserHasLeftApp, trackUiMetric } from './services';
|
||||
import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services';
|
||||
import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections';
|
||||
|
||||
export class App extends Component {
|
||||
|
@ -34,7 +34,7 @@ export class App extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
trackUiMetric(UIM_APP_LOAD);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { CRUD_APP_BASE_PATH, UIM_SHOW_DETAILS_CLICK } from '../../../constants';
|
||||
import { getRouterLinkProps, trackUiMetric } from '../../../services';
|
||||
import { getRouterLinkProps, trackUiMetric, METRIC_TYPE } from '../../../services';
|
||||
import { ConnectionStatus, RemoveClusterButtonProvider } from '../components';
|
||||
|
||||
export class RemoteClusterTable extends Component {
|
||||
|
@ -91,7 +91,7 @@ export class RemoteClusterTable extends Component {
|
|||
<EuiLink
|
||||
data-test-subj="remoteClustersTableListClusterLink"
|
||||
onClick={() => {
|
||||
trackUiMetric(UIM_SHOW_DETAILS_CLICK);
|
||||
trackUiMetric(METRIC_TYPE.CLICK, UIM_SHOW_DETAILS_CLICK);
|
||||
openDetailPanel(name);
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { UIM_CLUSTER_ADD, UIM_CLUSTER_UPDATE } from '../constants';
|
||||
import { trackUserRequest } from './track_ui_metric';
|
||||
import { trackUserRequest } from './ui_metric';
|
||||
import { sendGet, sendPost, sendPut, sendDelete } from './http';
|
||||
|
||||
export async function loadClusters() {
|
||||
|
|
|
@ -40,4 +40,5 @@ export {
|
|||
|
||||
export {
|
||||
trackUiMetric,
|
||||
} from './track_ui_metric';
|
||||
METRIC_TYPE,
|
||||
} from './ui_metric';
|
||||
|
|
|
@ -1,26 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { UIM_APP_NAME } from '../constants';
|
||||
|
||||
export function trackUiMetric(actionType) {
|
||||
track(UIM_APP_NAME, actionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transparently return provided request Promise, while allowing us to track
|
||||
* a successful completion of the request.
|
||||
*/
|
||||
export function trackUserRequest(request, actionType) {
|
||||
// Only track successful actions.
|
||||
return request.then(response => {
|
||||
trackUiMetric(actionType);
|
||||
// We return the response immediately without waiting for the tracking request to resolve,
|
||||
// to avoid adding additional latency.
|
||||
return response;
|
||||
});
|
||||
}
|
|
@ -5,13 +5,28 @@
|
|||
*/
|
||||
|
||||
import { UIM_APP_NAME } from '../constants';
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
export let track: any;
|
||||
export let trackUiMetric: ReturnType<typeof createUiStatsReporter>;
|
||||
export { METRIC_TYPE };
|
||||
|
||||
export function init(_track: any): void {
|
||||
track = _track;
|
||||
export function init(getReporter: typeof createUiStatsReporter): void {
|
||||
trackUiMetric = getReporter(UIM_APP_NAME);
|
||||
}
|
||||
|
||||
export function trackUiMetric(actionType: string): any {
|
||||
return track(UIM_APP_NAME, actionType);
|
||||
/**
|
||||
* Transparently return provided request Promise, while allowing us to track
|
||||
* a successful completion of the request.
|
||||
*/
|
||||
export function trackUserRequest(request: Promise<any>, eventName: string) {
|
||||
// Only track successful actions.
|
||||
return request.then((response: any) => {
|
||||
trackUiMetric(METRIC_TYPE.COUNT, eventName);
|
||||
// We return the response immediately without waiting for the tracking request to resolve,
|
||||
// to avoid adding additional latency.
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { UIM_CLUSTER_REMOVE, UIM_CLUSTER_REMOVE_MANY } from '../../constants';
|
|||
import {
|
||||
removeClusterRequest as sendRemoveClusterRequest,
|
||||
trackUiMetric,
|
||||
METRIC_TYPE,
|
||||
} from '../../services';
|
||||
|
||||
import {
|
||||
|
@ -83,7 +84,7 @@ export const removeClusters = (names) => async (dispatch, getState) => {
|
|||
|
||||
if (itemsDeleted.length > 0) {
|
||||
// Only track successful requests.
|
||||
trackUiMetric(names.length > 1 ? UIM_CLUSTER_REMOVE_MANY : UIM_CLUSTER_REMOVE);
|
||||
trackUiMetric(METRIC_TYPE.COUNT, names.length > 1 ? UIM_CLUSTER_REMOVE_MANY : UIM_CLUSTER_REMOVE);
|
||||
|
||||
if (itemsDeleted.length === 1) {
|
||||
toasts.addSuccess(i18n.translate('xpack.remoteClusters.removeAction.successSingleNotificationTitle', {
|
||||
|
|
|
@ -35,7 +35,7 @@ export class Plugin {
|
|||
if (getInjectedVar('remoteClustersUiEnabled')) {
|
||||
const {
|
||||
management: { getSection, breadcrumb: managementBreadcrumb },
|
||||
uiMetric: { track },
|
||||
uiMetric: { createUiStatsReporter },
|
||||
} = pluginsStart;
|
||||
|
||||
const esSection = getSection('elasticsearch');
|
||||
|
@ -49,7 +49,7 @@ export class Plugin {
|
|||
// Initialize services
|
||||
initBreadcrumbs(setBreadcrumbs, managementBreadcrumb);
|
||||
initDocumentation(`${elasticWebsiteUrl}guide/en/elasticsearch/reference/${docLinkVersion}/`);
|
||||
initUiMetric(track);
|
||||
initUiMetric(createUiStatsReporter);
|
||||
initNotification(toasts, fatalError);
|
||||
|
||||
const unmountReactApp = () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { management, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
|||
import { fatalError } from 'ui/notify';
|
||||
import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links';
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
export function createShim() {
|
||||
const {
|
||||
|
@ -35,7 +35,7 @@ export function createShim() {
|
|||
breadcrumb: MANAGEMENT_BREADCRUMB,
|
||||
},
|
||||
uiMetric: {
|
||||
track,
|
||||
createUiStatsReporter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,10 +19,6 @@ jest.mock('ui/chrome', () => ({
|
|||
|
||||
jest.mock('lodash/function/debounce', () => fn => fn);
|
||||
|
||||
jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({
|
||||
trackUiMetric: jest.fn(),
|
||||
}));
|
||||
|
||||
const { setup } = pageHelpers.jobCreate;
|
||||
|
||||
describe('Create Rollup Job, step 5: Metrics', () => {
|
||||
|
|
|
@ -28,10 +28,6 @@ jest.mock('ui/chrome', () => ({
|
|||
}
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({
|
||||
trackUiMetric: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../public/crud_app/services', () => {
|
||||
const services = require.requireActual('../../public/crud_app/services');
|
||||
return {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { HashRouter, Switch, Route, Redirect } from 'react-router-dom';
|
|||
|
||||
import { UIM_APP_LOAD } from '../../common';
|
||||
import { CRUD_APP_BASE_PATH } from './constants';
|
||||
import { registerRouter, setUserHasLeftApp, trackUiMetric } from './services';
|
||||
import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services';
|
||||
import { JobList, JobCreate } from './sections';
|
||||
|
||||
class ShareRouter extends Component {
|
||||
|
@ -41,7 +41,7 @@ class ShareRouter extends Component {
|
|||
|
||||
export class App extends Component { // eslint-disable-line react/no-multi-comp
|
||||
componentDidMount() {
|
||||
trackUiMetric(UIM_APP_LOAD);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
UIM_DETAIL_PANEL_METRICS_TAB_CLICK,
|
||||
UIM_DETAIL_PANEL_JSON_TAB_CLICK,
|
||||
} from '../../../../../common';
|
||||
import { trackUiMetric } from '../../../services';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../services';
|
||||
|
||||
import {
|
||||
JobActionMenu,
|
||||
|
@ -114,7 +114,7 @@ export class DetailPanelUi extends Component {
|
|||
renderedTabs.push(
|
||||
<EuiTab
|
||||
onClick={() => {
|
||||
trackUiMetric(tabToUiMetricMap[tab]);
|
||||
trackUiMetric(METRIC_TYPE.CLICK, tabToUiMetricMap[tab]);
|
||||
openDetailPanel({ panelType: tab, jobId: id });
|
||||
}}
|
||||
isSelected={isSelected}
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { UIM_SHOW_DETAILS_CLICK } from '../../../../../common';
|
||||
import { trackUiMetric } from '../../../services';
|
||||
import { trackUiMetric, METRIC_TYPE } from '../../../services';
|
||||
import { JobActionMenu, JobStatus } from '../../components';
|
||||
|
||||
const COLUMNS = [{
|
||||
|
@ -259,7 +259,7 @@ export class JobTableUi extends Component {
|
|||
content = (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
trackUiMetric(UIM_SHOW_DETAILS_CLICK);
|
||||
trackUiMetric(METRIC_TYPE.CLICK, UIM_SHOW_DETAILS_CLICK);
|
||||
openDetailPanel(job.id);
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -95,4 +95,5 @@ export {
|
|||
|
||||
export {
|
||||
trackUiMetric,
|
||||
METRIC_TYPE,
|
||||
} from './track_ui_metric';
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { UIM_APP_NAME } from '../../../common';
|
||||
|
||||
export function trackUiMetric(actionType) {
|
||||
track(UIM_APP_NAME, actionType);
|
||||
}
|
||||
export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME);
|
||||
export { METRIC_TYPE };
|
||||
|
||||
/**
|
||||
* Transparently return provided request Promise, while allowing us to track
|
||||
|
@ -18,7 +17,7 @@ export function trackUiMetric(actionType) {
|
|||
export function trackUserRequest(request, actionType) {
|
||||
// Only track successful actions.
|
||||
return request.then(response => {
|
||||
trackUiMetric(actionType);
|
||||
trackUiMetric(METRIC_TYPE.LOADED, actionType);
|
||||
// We return the response immediately without waiting for the tracking request to resolve,
|
||||
// to avoid adding additional latency.
|
||||
return response;
|
||||
|
|
|
@ -14,12 +14,11 @@ import { ActionCreator } from 'typescript-fsa';
|
|||
|
||||
import { State, timelineSelectors } from '../../store';
|
||||
import { DataProvider } from '../timeline/data_providers/data_provider';
|
||||
|
||||
import { FlyoutButton } from './button';
|
||||
import { Pane } from './pane';
|
||||
import { timelineActions } from '../../store/actions';
|
||||
import { DEFAULT_TIMELINE_WIDTH } from '../timeline/body/helpers';
|
||||
import { trackUiAction as track } from '../../lib/track_usage';
|
||||
import { trackUiAction as track, METRIC_TYPE } from '../../lib/track_usage';
|
||||
|
||||
/** The height in pixels of the flyout header, exported for use in height calculations */
|
||||
export const flyoutHeaderHeight: number = 60;
|
||||
|
@ -100,7 +99,7 @@ export const FlyoutComponent = pure<Props>(
|
|||
show={!show}
|
||||
timelineId={timelineId}
|
||||
onOpen={() => {
|
||||
track('open_timeline');
|
||||
track(METRIC_TYPE.LOADED, 'open_timeline');
|
||||
showTimeline!({ id: timelineId, show: true });
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as React from 'react';
|
|||
import styled from 'styled-components';
|
||||
|
||||
import { getHostsUrl, getNetworkUrl, getOverviewUrl, getTimelinesUrl } from '../../link_to';
|
||||
import { trackUiAction as track } from '../../../lib/track_usage';
|
||||
import { trackUiAction as track, METRIC_TYPE } from '../../../lib/track_usage';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
|
@ -101,7 +101,7 @@ export class TabNavigation extends React.PureComponent<TabNavigationProps, TabNa
|
|||
disabled={tab.disabled}
|
||||
isSelected={this.state.selectedTabId === tab.id}
|
||||
onClick={() => {
|
||||
track(`tab_${tab.id}`);
|
||||
track(METRIC_TYPE.CLICK, `tab_${tab.id}`);
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { trackUiMetric } from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { APP_ID } from '../../../common/constants';
|
||||
|
||||
export const trackUiAction = (metricType: string) => trackUiMetric(APP_ID, metricType);
|
||||
export const trackUiAction = createUiStatsReporter(APP_ID);
|
||||
export { METRIC_TYPE };
|
||||
|
|
|
@ -4,16 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { UIM_APP_NAME } from '../../constants';
|
||||
import {
|
||||
createUiStatsReporter,
|
||||
METRIC_TYPE,
|
||||
} from '../../../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
class UiMetricService {
|
||||
public track: any = () => {};
|
||||
track?: ReturnType<typeof createUiStatsReporter>;
|
||||
|
||||
public init = (track: any): void => {
|
||||
this.track = track;
|
||||
public init = (getReporter: typeof createUiStatsReporter): void => {
|
||||
this.track = getReporter(UIM_APP_NAME);
|
||||
};
|
||||
|
||||
public trackUiMetric = (actionType: string): any => {
|
||||
return this.track(UIM_APP_NAME, actionType);
|
||||
public trackUiMetric = (eventName: string): void => {
|
||||
if (!this.track) throw Error('UiMetricService not initialized.');
|
||||
return this.track(METRIC_TYPE.COUNT, eventName);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ export class Plugin {
|
|||
textService.init(i18n);
|
||||
breadcrumbService.init(chrome, management.constants.BREADCRUMB);
|
||||
documentationLinksService.init(documentation.esDocBasePath, documentation.esPluginDocBasePath);
|
||||
uiMetricService.init(uiMetric.track);
|
||||
uiMetricService.init(uiMetric.createUiStatsReporter);
|
||||
|
||||
const unmountReactApp = (): void => {
|
||||
const elem = document.getElementById(REACT_ROOT_ID);
|
||||
|
|
|
@ -16,7 +16,7 @@ import routes from 'ui/routes';
|
|||
import { HashRouter } from 'react-router-dom';
|
||||
|
||||
// @ts-ignore: allow traversal to fail on x-pack build
|
||||
import { trackUiMetric as track } from '../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
export interface AppCore {
|
||||
i18n: {
|
||||
|
@ -63,7 +63,7 @@ export interface Plugins extends AppPlugins {
|
|||
};
|
||||
};
|
||||
uiMetric: {
|
||||
track: typeof track;
|
||||
createUiStatsReporter: typeof createUiStatsReporter;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ export function createShim(): { core: Core; plugins: Plugins } {
|
|||
},
|
||||
},
|
||||
uiMetric: {
|
||||
track,
|
||||
createUiStatsReporter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -81,3 +81,9 @@ export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization';
|
|||
* @type {string}
|
||||
*/
|
||||
export const TELEMETRY_QUERY_SOURCE = 'TELEMETRY';
|
||||
|
||||
/**
|
||||
* UI metric usage type
|
||||
* @type {string}
|
||||
*/
|
||||
export const UI_METRIC_USAGE_TYPE = 'ui_metric';
|
||||
|
|
|
@ -17,6 +17,7 @@ import { telemetryPlugin } from './server';
|
|||
import {
|
||||
createLocalizationUsageCollector,
|
||||
createTelemetryUsageCollector,
|
||||
createUiMetricUsageCollector,
|
||||
} from './server/collectors';
|
||||
|
||||
const ENDPOINT_VERSION = 'v2';
|
||||
|
@ -72,10 +73,7 @@ export const telemetry = (kibana: any) => {
|
|||
activeSpace: null,
|
||||
};
|
||||
},
|
||||
hacks: [
|
||||
'plugins/telemetry/hacks/telemetry_opt_in',
|
||||
'plugins/telemetry/hacks/telemetry_trigger',
|
||||
],
|
||||
hacks: ['plugins/telemetry/hacks/telemetry_init', 'plugins/telemetry/hacks/telemetry_opt_in'],
|
||||
mappings,
|
||||
},
|
||||
init(server: Server) {
|
||||
|
@ -89,6 +87,7 @@ export const telemetry = (kibana: any) => {
|
|||
// register collectors
|
||||
server.usage.collectorSet.register(createLocalizationUsageCollector(server));
|
||||
server.usage.collectorSet.register(createTelemetryUsageCollector(server));
|
||||
server.usage.collectorSet.register(createUiMetricUsageCollector(server));
|
||||
|
||||
// expose
|
||||
server.expose('telemetryCollectionInterval', REPORT_INTERVAL_MS);
|
||||
|
|
|
@ -4,24 +4,31 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
// @ts-ignore
|
||||
import { Path } from 'plugins/xpack_main/services/path';
|
||||
import { Telemetry } from './telemetry';
|
||||
import { fetchTelemetry } from './fetch_telemetry';
|
||||
// @ts-ignore
|
||||
import { npStart } from 'ui/new_platform';
|
||||
// @ts-ignore
|
||||
import { Telemetry } from './telemetry';
|
||||
// @ts-ignore
|
||||
import { fetchTelemetry } from './fetch_telemetry';
|
||||
|
||||
function telemetryInit($injector: any) {
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
function telemetryStart($injector) {
|
||||
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
|
||||
|
||||
if (telemetryEnabled) {
|
||||
// no telemetry for non-logged in users
|
||||
if (Path.isUnauthenticated()) { return; }
|
||||
if (Path.isUnauthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $http = $injector.get('$http');
|
||||
const sender = new Telemetry($injector, () => fetchTelemetry($http));
|
||||
|
||||
sender.start();
|
||||
}
|
||||
}
|
||||
|
||||
uiModules.get('telemetry/hacks').run(telemetryStart);
|
||||
uiModules.get('telemetry/hacks').run(telemetryInit);
|
|
@ -17,7 +17,6 @@ export function TelemetryOptInProvider($injector, chrome) {
|
|||
getOptIn: () => currentOptInStatus,
|
||||
setOptIn: async (enabled) => {
|
||||
setCanTrackUiMetrics(enabled);
|
||||
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
try {
|
||||
|
|
|
@ -11,4 +11,5 @@ export { getLocalStats } from './local';
|
|||
export { getStats } from './get_stats';
|
||||
export { encryptTelemetry } from './encryption';
|
||||
export { createTelemetryUsageCollector } from './usage';
|
||||
export { createUiMetricUsageCollector } from './ui_metric';
|
||||
export { createLocalizationUsageCollector } from './localization';
|
||||
|
|
|
@ -4,9 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { trackUiMetric } from '../../../../../../src/legacy/core_plugins/ui_metric/public';
|
||||
|
||||
const APP = 'canvas';
|
||||
export const trackCanvasUiMetric = uiMetrics => {
|
||||
trackUiMetric(APP, uiMetrics);
|
||||
};
|
||||
export { createUiMetricUsageCollector } from './telemetry_ui_metric_collector';
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { UI_METRIC_USAGE_TYPE } from '../../../common/constants';
|
||||
|
||||
export function createUiMetricUsageCollector(server: any) {
|
||||
const { collectorSet } = server.usage;
|
||||
return collectorSet.makeUsageCollector({
|
||||
type: UI_METRIC_USAGE_TYPE,
|
||||
fetch: async () => {
|
||||
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
const savedObjectsClient = new SavedObjectsClient(internalRepository);
|
||||
|
||||
const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({
|
||||
type: 'ui-metric',
|
||||
fields: ['count'],
|
||||
});
|
||||
|
||||
const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => {
|
||||
const {
|
||||
id,
|
||||
attributes: { count },
|
||||
} = rawUiMetric;
|
||||
|
||||
const [appName, metricType] = id.split(':');
|
||||
|
||||
if (!accum[appName]) {
|
||||
accum[appName] = [];
|
||||
}
|
||||
|
||||
const pair = { key: metricType, value: count };
|
||||
accum[appName].push(pair);
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
return uiMetricsByAppName;
|
||||
},
|
||||
isReady: () => true,
|
||||
});
|
||||
}
|
|
@ -178,6 +178,7 @@
|
|||
"@elastic/nodegit": "0.25.0-alpha.22",
|
||||
"@elastic/numeral": "2.3.3",
|
||||
"@elastic/request-crypto": "^1.0.2",
|
||||
"@kbn/analytics": "1.0.0",
|
||||
"@kbn/babel-preset": "1.0.0",
|
||||
"@kbn/config-schema": "1.0.0",
|
||||
"@kbn/elastic-idx": "1.0.0",
|
||||
|
|
|
@ -27796,7 +27796,7 @@ typescript-fsa@^2.0.0, typescript-fsa@^2.5.0:
|
|||
resolved "https://registry.yarnpkg.com/typescript-fsa/-/typescript-fsa-2.5.0.tgz#1baec01b5e8f5f34c322679d1327016e9e294faf"
|
||||
integrity sha1-G67AG16PXzTDImedEycBbp4pT68=
|
||||
|
||||
typescript@3.5.3, typescript@^3.0.3, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.0.3, typescript@~3.3.3333, typescript@~3.4.3:
|
||||
typescript@3.5.1, typescript@3.5.3, typescript@^3.0.3, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.0.3, typescript@~3.3.3333, typescript@~3.4.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
|
||||
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
|
||||
|
|
Loading…
Reference in a new issue