Upgrade assistant telemetry (#28878)
* feat(NA): very first version with telemetry working for the upgrade assistant. * refact(NA): import all types from a file and rewrite first version code. * chore(NA): missing types for register and makeCollector functions. * test(NA): tests for upgrade assistant telemetry routes. * refact(NA): extra spaces on imports and imports order. * test(NA): for ui_open saved objects on upgrade assistant telemetry. * test(NA): for usage_collector functions. * chore(NA): change the return object into the upgrade assistant telemetry. * chore(NA): fix type imports. * fix(NA): importing saved objects client types. * refact(NA): simplify saved objects schema for upgrade assistant telemetry. * feat(NA): only send upgrade assistant metrics after we have data. Never send telemetry info about a clicked tab if it is the current selected one. * fix(NA): typo on word * feat(NA): add telemetry schema support for for ui_reindex. * test(NA): update integration test fixture to include new telemetry stats. * chore(NA): fix mappings for ui_reindex. * refact(NA): invest telemetry local expect data order. * chore(NA): includeDefaults for callCluster. * chore(NA): remove ignore 404 from callCluster. * refact(NA): wrap get logging status from cluster settings into a function. * feat(NA): add reindex ui telemetry. * fix(NA): typo on mappings. fix(NA): non wait for send telemetry to complete will cause es doc errors when increment counters * test(NA): wait for telemetry requests to end before esArchiver.unload on functional tests. * refact(NA): remove bad added async to tabs onclick function.
This commit is contained in:
parent
4dab26ab58
commit
64334ca26e
20 changed files with 908 additions and 17 deletions
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BaseOptions, SavedObject } from '../saved_objects_client';
|
||||
|
||||
export interface SavedObjectsRepositoryOptions {
|
||||
index: string | string[];
|
||||
mappings: unknown;
|
||||
|
@ -30,5 +32,13 @@ export interface SavedObjectsRepositoryOptions {
|
|||
export declare class SavedObjectsRepository {
|
||||
// ATTENTION: this interface is incomplete
|
||||
|
||||
public get: (type: string, id: string, options?: BaseOptions) => Promise<SavedObject>;
|
||||
public incrementCounter: (
|
||||
type: string,
|
||||
id: string,
|
||||
counterFieldName: string,
|
||||
options?: BaseOptions
|
||||
) => Promise<SavedObject>;
|
||||
|
||||
constructor(options: SavedObjectsRepositoryOptions);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Legacy } from 'kibana';
|
||||
import {
|
||||
SavedObject,
|
||||
SavedObjectAttributes,
|
||||
|
@ -57,3 +58,68 @@ export enum IndexGroup {
|
|||
ml = '___ML_REINDEX_LOCK___',
|
||||
watcher = '___WATCHER_REINDEX_LOCK___',
|
||||
}
|
||||
|
||||
// Telemetry types
|
||||
export const UPGRADE_ASSISTANT_TYPE = 'upgrade-assistant-telemetry';
|
||||
export const UPGRADE_ASSISTANT_DOC_ID = 'upgrade-assistant-telemetry';
|
||||
export type UIOpenOption = 'overview' | 'cluster' | 'indices';
|
||||
export type UIReindexOption = 'close' | 'open' | 'start' | 'stop';
|
||||
|
||||
export interface UIOpen {
|
||||
overview: boolean;
|
||||
cluster: boolean;
|
||||
indices: boolean;
|
||||
}
|
||||
|
||||
export interface UIReindex {
|
||||
close: boolean;
|
||||
open: boolean;
|
||||
start: boolean;
|
||||
stop: boolean;
|
||||
}
|
||||
|
||||
export interface UpgradeAssistantTelemetryServer extends Legacy.Server {
|
||||
usage: {
|
||||
collectorSet: {
|
||||
makeUsageCollector: any;
|
||||
register: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpgradeAssistantTelemetrySavedObject {
|
||||
ui_open: {
|
||||
overview: number;
|
||||
cluster: number;
|
||||
indices: number;
|
||||
};
|
||||
ui_reindex: {
|
||||
close: number;
|
||||
open: number;
|
||||
start: number;
|
||||
stop: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpgradeAssistantTelemetry {
|
||||
ui_open: {
|
||||
overview: number;
|
||||
cluster: number;
|
||||
indices: number;
|
||||
};
|
||||
ui_reindex: {
|
||||
close: number;
|
||||
open: number;
|
||||
start: number;
|
||||
stop: number;
|
||||
};
|
||||
features: {
|
||||
deprecation_logging: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpgradeAssistantTelemetrySavedObjectAttributes {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { Server } from 'hapi';
|
||||
import Joi from 'joi';
|
||||
import { resolve } from 'path';
|
||||
import mappings from './mappings.json';
|
||||
import { initServer } from './server';
|
||||
|
||||
export function upgradeAssistant(kibana: any) {
|
||||
|
@ -14,13 +15,16 @@ export function upgradeAssistant(kibana: any) {
|
|||
require: ['elasticsearch'],
|
||||
uiExports: {
|
||||
managementSections: ['plugins/upgrade_assistant'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
mappings: require('./mappings.json'),
|
||||
savedObjectSchemas: {
|
||||
'upgrade-assistant-reindex-operation': {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
'upgrade-assistant-telemetry': {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
},
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
mappings,
|
||||
},
|
||||
publicDir: resolve(__dirname, 'public'),
|
||||
|
||||
|
|
|
@ -9,5 +9,57 @@
|
|||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"upgrade-assistant-telemetry": {
|
||||
"properties": {
|
||||
"ui_open": {
|
||||
"properties": {
|
||||
"overview": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
},
|
||||
"cluster": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
},
|
||||
"indices": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ui_reindex": {
|
||||
"properties": {
|
||||
"close": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
},
|
||||
"open": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
},
|
||||
"start": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
},
|
||||
"stop": {
|
||||
"type": "long",
|
||||
"null_value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"properties": {
|
||||
"deprecation_logging": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"null_value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,25 +5,27 @@
|
|||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { findIndex } from 'lodash';
|
||||
import { findIndex, set } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
|
||||
import { UpgradeAssistantStatus } from '../../server/lib/es_migration_apis';
|
||||
import { LatestMinorBanner } from './latest_minor_banner';
|
||||
import { CheckupTab } from './tabs/checkup';
|
||||
import { OverviewTab } from './tabs/overview';
|
||||
import { LoadingState, UpgradeAssistantTabProps } from './types';
|
||||
import { LoadingState, TelemetryState, UpgradeAssistantTabProps } from './types';
|
||||
|
||||
interface TabsState {
|
||||
loadingState: LoadingState;
|
||||
loadingError?: Error;
|
||||
checkupData?: UpgradeAssistantStatus;
|
||||
selectedTabIndex: number;
|
||||
telemetryState: TelemetryState;
|
||||
}
|
||||
|
||||
export class UpgradeAssistantTabsUI extends React.Component<
|
||||
|
@ -36,19 +38,26 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
this.state = {
|
||||
loadingState: LoadingState.Loading,
|
||||
selectedTabIndex: 0,
|
||||
telemetryState: TelemetryState.Complete,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.loadData();
|
||||
public async componentDidMount() {
|
||||
await this.loadData();
|
||||
|
||||
// Send telemetry info about the default selected tab
|
||||
this.sendTelemetryInfo(this.tabs[this.state.selectedTabIndex].id);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { selectedTabIndex } = this.state;
|
||||
const { selectedTabIndex, telemetryState } = this.state;
|
||||
const tabs = this.tabs;
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
data-test-subj={
|
||||
telemetryState === TelemetryState.Running ? 'upgradeAssistantTelemetryRunning' : undefined
|
||||
}
|
||||
tabs={tabs}
|
||||
onTabClick={this.onTabClick}
|
||||
selectedTab={tabs[selectedTabIndex]}
|
||||
|
@ -62,6 +71,13 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
throw new Error(`Clicked tab did not exist in tabs array`);
|
||||
}
|
||||
|
||||
// Send telemetry info about the current selected tab
|
||||
// only in case the clicked tab id it's different from the
|
||||
// current selected tab id
|
||||
if (this.tabs[this.state.selectedTabIndex].id !== selectedTab.id) {
|
||||
this.sendTelemetryInfo(selectedTab.id);
|
||||
}
|
||||
|
||||
this.setSelectedTabIndex(selectedTabIndex);
|
||||
};
|
||||
|
||||
|
@ -142,6 +158,24 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
private async sendTelemetryInfo(tabName: string) {
|
||||
// In case we don't have any data yet, we wanna to ignore the
|
||||
// telemetry info update
|
||||
if (this.state.loadingState !== LoadingState.Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ telemetryState: TelemetryState.Running });
|
||||
|
||||
await kfetch({
|
||||
pathname: '/api/upgrade_assistant/telemetry/ui_open',
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(set({}, tabName, true)),
|
||||
});
|
||||
|
||||
this.setState({ telemetryState: TelemetryState.Complete });
|
||||
}
|
||||
}
|
||||
|
||||
export const UpgradeAssistantTabs = injectI18n(UpgradeAssistantTabsUI);
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { set } from 'lodash';
|
||||
import React, { Fragment, ReactNode } from 'react';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { ReindexStatus } from '../../../../../../common/types';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { ReindexStatus, UIReindexOption } from '../../../../../../common/types';
|
||||
import { LoadingState } from '../../../../types';
|
||||
import { ReindexFlyout } from './flyout';
|
||||
import { ReindexPollingService, ReindexState } from './polling_service';
|
||||
|
@ -104,7 +106,7 @@ export class ReindexButton extends React.Component<ReindexButtonProps, ReindexBu
|
|||
indexName={indexName}
|
||||
closeFlyout={this.closeFlyout}
|
||||
reindexState={reindexState}
|
||||
startReindex={this.service.startReindex}
|
||||
startReindex={this.startReindex}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
@ -133,11 +135,30 @@ export class ReindexButton extends React.Component<ReindexButtonProps, ReindexBu
|
|||
}
|
||||
}
|
||||
|
||||
private startReindex = async () => {
|
||||
if (!this.state.reindexState.status) {
|
||||
// if status didn't exist we are starting a reindex action
|
||||
this.sendUIReindexTelemetryInfo('start');
|
||||
}
|
||||
|
||||
await this.service.startReindex();
|
||||
};
|
||||
|
||||
private showFlyout = () => {
|
||||
this.sendUIReindexTelemetryInfo('open');
|
||||
this.setState({ flyoutVisible: true });
|
||||
};
|
||||
|
||||
private closeFlyout = () => {
|
||||
this.sendUIReindexTelemetryInfo('close');
|
||||
this.setState({ flyoutVisible: false });
|
||||
};
|
||||
|
||||
private async sendUIReindexTelemetryInfo(uiReindexAction: UIReindexOption) {
|
||||
await kfetch({
|
||||
pathname: '/api/upgrade_assistant/telemetry/ui_reindex',
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(set({}, uiReindexAction, true)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,3 +42,8 @@ export enum GroupByOption {
|
|||
index = 'index',
|
||||
node = 'node',
|
||||
}
|
||||
|
||||
export enum TelemetryState {
|
||||
Running,
|
||||
Complete,
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
*/
|
||||
|
||||
import { Legacy } from 'kibana';
|
||||
import { UpgradeAssistantTelemetryServer } from '../common/types';
|
||||
import { credentialStoreFactory } from './lib/reindexing/credential_store';
|
||||
import { makeUpgradeAssistantUsageCollector } from './lib/telemetry';
|
||||
import { registerClusterCheckupRoutes } from './routes/cluster_checkup';
|
||||
import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging';
|
||||
import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices';
|
||||
import { registerTelemetryRoutes } from './routes/telemetry';
|
||||
|
||||
export function initServer(server: Legacy.Server) {
|
||||
registerClusterCheckupRoutes(server);
|
||||
|
@ -24,4 +27,8 @@ export function initServer(server: Legacy.Server) {
|
|||
|
||||
const worker = registerReindexWorker(server, credentialStore);
|
||||
registerReindexIndicesRoutes(server, worker, credentialStore);
|
||||
|
||||
// Bootstrap the needed routes and the collector for the telemetry
|
||||
registerTelemetryRoutes(server as UpgradeAssistantTelemetryServer);
|
||||
makeUpgradeAssistantUsageCollector(server as UpgradeAssistantTelemetryServer);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types';
|
||||
import { upsertUIOpenOption } from './es_ui_open_apis';
|
||||
|
||||
/**
|
||||
* Since these route callbacks are so thin, these serve simply as integration tests
|
||||
* to ensure they're wired up to the lib functions correctly. Business logic is tested
|
||||
* more thoroughly in the lib/telemetry tests.
|
||||
*/
|
||||
describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => {
|
||||
const mockIncrementCounter = jest.fn();
|
||||
const server = jest.fn().mockReturnValue({
|
||||
usage: {
|
||||
collectorSet: {
|
||||
makeUsageCollector: {},
|
||||
register: {},
|
||||
},
|
||||
},
|
||||
savedObjects: {
|
||||
getSavedObjectsRepository: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
incrementCounter: mockIncrementCounter,
|
||||
};
|
||||
}),
|
||||
},
|
||||
plugins: {
|
||||
elasticsearch: {
|
||||
getCluster: () => {
|
||||
return {
|
||||
callWithInternalUser: {},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const request = jest.fn().mockReturnValue({
|
||||
payload: {
|
||||
overview: true,
|
||||
cluster: true,
|
||||
indices: true,
|
||||
},
|
||||
});
|
||||
|
||||
describe('Upsert UIOpen Option', () => {
|
||||
it('call saved objects internal repository with the correct info', async () => {
|
||||
const serverMock = server();
|
||||
const incCounterSORepoFunc = serverMock.savedObjects.getSavedObjectsRepository()
|
||||
.incrementCounter;
|
||||
|
||||
await upsertUIOpenOption(serverMock, request());
|
||||
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledTimes(3);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_open.overview`
|
||||
);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_open.cluster`
|
||||
);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_open.indices`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { Legacy } from 'kibana';
|
||||
import {
|
||||
UIOpen,
|
||||
UIOpenOption,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UpgradeAssistantTelemetryServer,
|
||||
} from '../../../common/types';
|
||||
|
||||
async function incrementUIOpenOptionCounter(
|
||||
server: UpgradeAssistantTelemetryServer,
|
||||
uiOpenOptionCounter: UIOpenOption
|
||||
) {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
||||
await internalRepository.incrementCounter(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_open.${uiOpenOptionCounter}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function upsertUIOpenOption(
|
||||
server: UpgradeAssistantTelemetryServer,
|
||||
req: Legacy.Request
|
||||
): Promise<UIOpen> {
|
||||
const { overview, cluster, indices } = req.payload as UIOpen;
|
||||
|
||||
if (overview) {
|
||||
await incrementUIOpenOptionCounter(server, 'overview');
|
||||
}
|
||||
|
||||
if (cluster) {
|
||||
await incrementUIOpenOptionCounter(server, 'cluster');
|
||||
}
|
||||
|
||||
if (indices) {
|
||||
await incrementUIOpenOptionCounter(server, 'indices');
|
||||
}
|
||||
|
||||
return {
|
||||
overview,
|
||||
cluster,
|
||||
indices,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types';
|
||||
import { upsertUIReindexOption } from './es_ui_reindex_apis';
|
||||
|
||||
/**
|
||||
* Since these route callbacks are so thin, these serve simply as integration tests
|
||||
* to ensure they're wired up to the lib functions correctly. Business logic is tested
|
||||
* more thoroughly in the lib/telemetry tests.
|
||||
*/
|
||||
describe('Upgrade Assistant Telemetry SavedObject UIReindex', () => {
|
||||
const mockIncrementCounter = jest.fn();
|
||||
const server = jest.fn().mockReturnValue({
|
||||
usage: {
|
||||
collectorSet: {
|
||||
makeUsageCollector: {},
|
||||
register: {},
|
||||
},
|
||||
},
|
||||
savedObjects: {
|
||||
getSavedObjectsRepository: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
incrementCounter: mockIncrementCounter,
|
||||
};
|
||||
}),
|
||||
},
|
||||
plugins: {
|
||||
elasticsearch: {
|
||||
getCluster: () => {
|
||||
return {
|
||||
callWithInternalUser: {},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const request = jest.fn().mockReturnValue({
|
||||
payload: {
|
||||
close: true,
|
||||
open: true,
|
||||
start: true,
|
||||
stop: true,
|
||||
},
|
||||
});
|
||||
|
||||
describe('Upsert UIReindex Option', () => {
|
||||
it('call saved objects internal repository with the correct info', async () => {
|
||||
const serverMock = server();
|
||||
const incCounterSORepoFunc = serverMock.savedObjects.getSavedObjectsRepository()
|
||||
.incrementCounter;
|
||||
|
||||
await upsertUIReindexOption(serverMock, request());
|
||||
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledTimes(4);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_reindex.close`
|
||||
);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_reindex.open`
|
||||
);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_reindex.start`
|
||||
);
|
||||
expect(incCounterSORepoFunc).toHaveBeenCalledWith(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_reindex.stop`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { Legacy } from 'kibana';
|
||||
import {
|
||||
UIReindex,
|
||||
UIReindexOption,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UpgradeAssistantTelemetryServer,
|
||||
} from '../../../common/types';
|
||||
|
||||
async function incrementUIReindexOptionCounter(
|
||||
server: UpgradeAssistantTelemetryServer,
|
||||
uiOpenOptionCounter: UIReindexOption
|
||||
) {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
||||
await internalRepository.incrementCounter(
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
`ui_reindex.${uiOpenOptionCounter}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function upsertUIReindexOption(
|
||||
server: UpgradeAssistantTelemetryServer,
|
||||
req: Legacy.Request
|
||||
): Promise<UIReindex> {
|
||||
const { close, open, start, stop } = req.payload as UIReindex;
|
||||
|
||||
if (close) {
|
||||
await incrementUIReindexOptionCounter(server, 'close');
|
||||
}
|
||||
|
||||
if (open) {
|
||||
await incrementUIReindexOptionCounter(server, 'open');
|
||||
}
|
||||
|
||||
if (start) {
|
||||
await incrementUIReindexOptionCounter(server, 'start');
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
await incrementUIReindexOptionCounter(server, 'stop');
|
||||
}
|
||||
|
||||
return {
|
||||
close,
|
||||
open,
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { makeUpgradeAssistantUsageCollector } from './usage_collector';
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 * as usageCollector from './usage_collector';
|
||||
|
||||
/**
|
||||
* Since these route callbacks are so thin, these serve simply as integration tests
|
||||
* to ensure they're wired up to the lib functions correctly. Business logic is tested
|
||||
* more thoroughly in the lib/telemetry tests.
|
||||
*/
|
||||
describe('Upgrade Assistant Usage Collector', () => {
|
||||
let makeUsageCollectorStub: any;
|
||||
let registerStub: any;
|
||||
let server: any;
|
||||
let callClusterStub: any;
|
||||
|
||||
beforeEach(() => {
|
||||
makeUsageCollectorStub = jest.fn();
|
||||
registerStub = jest.fn();
|
||||
server = jest.fn().mockReturnValue({
|
||||
usage: {
|
||||
collectorSet: { makeUsageCollector: makeUsageCollectorStub, register: registerStub },
|
||||
register: {},
|
||||
},
|
||||
savedObjects: {
|
||||
getSavedObjectsRepository: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
get: () => {
|
||||
return {
|
||||
attributes: {
|
||||
'ui_open.overview': 10,
|
||||
'ui_open.cluster': 20,
|
||||
'ui_open.indices': 30,
|
||||
'ui_reindex.close': 1,
|
||||
'ui_reindex.open': 4,
|
||||
'ui_reindex.start': 2,
|
||||
'ui_reindex.stop': 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
callClusterStub = jest.fn().mockResolvedValue({
|
||||
persistent: {},
|
||||
transient: {
|
||||
logger: {
|
||||
deprecation: 'WARN',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeUpgradeAssistantUsageCollector', () => {
|
||||
it('should call collectorSet.register', () => {
|
||||
usageCollector.makeUpgradeAssistantUsageCollector(server());
|
||||
expect(registerStub).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call makeUsageCollector with type = upgrade-assistant', () => {
|
||||
usageCollector.makeUpgradeAssistantUsageCollector(server());
|
||||
expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1);
|
||||
expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('upgrade-assistant-telemetry');
|
||||
});
|
||||
|
||||
it('fetchUpgradeAssistantMetrics should return correct info', async () => {
|
||||
usageCollector.makeUpgradeAssistantUsageCollector(server());
|
||||
const upgradeAssistantStats = await makeUsageCollectorStub.mock.calls[0][0].fetch(
|
||||
callClusterStub
|
||||
);
|
||||
expect(upgradeAssistantStats).toEqual({
|
||||
ui_open: {
|
||||
overview: 10,
|
||||
cluster: 20,
|
||||
indices: 30,
|
||||
},
|
||||
ui_reindex: {
|
||||
close: 1,
|
||||
open: 4,
|
||||
start: 2,
|
||||
stop: 1,
|
||||
},
|
||||
features: {
|
||||
deprecation_logging: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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 { set } from 'lodash';
|
||||
import { SavedObjectsRepository } from 'src/server/saved_objects/service/lib/repository';
|
||||
import {
|
||||
UPGRADE_ASSISTANT_DOC_ID,
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UpgradeAssistantTelemetry,
|
||||
UpgradeAssistantTelemetrySavedObject,
|
||||
UpgradeAssistantTelemetrySavedObjectAttributes,
|
||||
UpgradeAssistantTelemetryServer,
|
||||
} from '../../../common/types';
|
||||
import { isDeprecationLoggingEnabled } from '../es_deprecation_logging_apis';
|
||||
|
||||
async function getSavedObjectAttributesFromRepo(
|
||||
savedObjectsRepository: SavedObjectsRepository,
|
||||
docType: string,
|
||||
docID: string
|
||||
) {
|
||||
try {
|
||||
return (await savedObjectsRepository.get(docType, docID)).attributes;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getDeprecationLoggingStatusValue(callCluster: any): Promise<boolean> {
|
||||
try {
|
||||
const loggerDeprecationCallResult = await callCluster('cluster.getSettings', {
|
||||
includeDefaults: true,
|
||||
});
|
||||
|
||||
return isDeprecationLoggingEnabled(loggerDeprecationCallResult);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchUpgradeAssistantMetrics(
|
||||
callCluster: any,
|
||||
server: UpgradeAssistantTelemetryServer
|
||||
): Promise<UpgradeAssistantTelemetry> {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const savedObjectsRepository = getSavedObjectsRepository(callCluster);
|
||||
const upgradeAssistantSOAttributes = await getSavedObjectAttributesFromRepo(
|
||||
savedObjectsRepository,
|
||||
UPGRADE_ASSISTANT_TYPE,
|
||||
UPGRADE_ASSISTANT_DOC_ID
|
||||
);
|
||||
const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callCluster);
|
||||
|
||||
const getTelemetrySavedObject = (
|
||||
upgradeAssistantTelemetrySavedObjectAttrs: UpgradeAssistantTelemetrySavedObjectAttributes | null
|
||||
): UpgradeAssistantTelemetrySavedObject => {
|
||||
const defaultTelemetrySavedObject = {
|
||||
ui_open: {
|
||||
overview: 0,
|
||||
cluster: 0,
|
||||
indices: 0,
|
||||
},
|
||||
ui_reindex: {
|
||||
close: 0,
|
||||
open: 0,
|
||||
start: 0,
|
||||
stop: 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (!upgradeAssistantTelemetrySavedObjectAttrs) {
|
||||
return defaultTelemetrySavedObject;
|
||||
}
|
||||
|
||||
const upgradeAssistantTelemetrySOAttrsKeys = Object.keys(
|
||||
upgradeAssistantTelemetrySavedObjectAttrs
|
||||
);
|
||||
const telemetryObj = defaultTelemetrySavedObject;
|
||||
|
||||
upgradeAssistantTelemetrySOAttrsKeys.forEach((key: string) => {
|
||||
set(telemetryObj, key, upgradeAssistantTelemetrySavedObjectAttrs[key]);
|
||||
});
|
||||
|
||||
return telemetryObj as UpgradeAssistantTelemetrySavedObject;
|
||||
};
|
||||
|
||||
return {
|
||||
...getTelemetrySavedObject(upgradeAssistantSOAttributes),
|
||||
features: {
|
||||
deprecation_logging: {
|
||||
enabled: deprecationLoggingStatusValue,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function makeUpgradeAssistantUsageCollector(server: UpgradeAssistantTelemetryServer) {
|
||||
const kbnServer = server as UpgradeAssistantTelemetryServer;
|
||||
const upgradeAssistantUsageCollector = kbnServer.usage.collectorSet.makeUsageCollector({
|
||||
type: UPGRADE_ASSISTANT_TYPE,
|
||||
fetch: async (callCluster: any) => fetchUpgradeAssistantMetrics(callCluster, server),
|
||||
});
|
||||
|
||||
kbnServer.usage.collectorSet.register(upgradeAssistantUsageCollector);
|
||||
}
|
146
x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.js
Normal file
146
x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
jest.mock('../lib/telemetry/es_ui_open_apis', () => ({
|
||||
upsertUIOpenOption: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../lib/telemetry/es_ui_reindex_apis', () => ({
|
||||
upsertUIReindexOption: jest.fn(),
|
||||
}));
|
||||
|
||||
import { Server } from 'hapi';
|
||||
import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis';
|
||||
import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis';
|
||||
import { registerTelemetryRoutes } from './telemetry';
|
||||
|
||||
/**
|
||||
* Since these route callbacks are so thin, these serve simply as integration tests
|
||||
* to ensure they're wired up to the lib functions correctly. Business logic is tested
|
||||
* more thoroughly in the lib/telemetry tests.
|
||||
*/
|
||||
describe('Upgrade Assistant Telemetry API', () => {
|
||||
const server = new Server();
|
||||
|
||||
registerTelemetryRoutes(server);
|
||||
|
||||
describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => {
|
||||
it('returns correct payload with single option', async () => {
|
||||
const returnPayload = {
|
||||
overview: true,
|
||||
cluster: false,
|
||||
indices: false,
|
||||
};
|
||||
|
||||
upsertUIOpenOption.mockResolvedValue(returnPayload);
|
||||
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_open',
|
||||
payload: {
|
||||
overview: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(resp.payload)).toEqual(returnPayload);
|
||||
});
|
||||
|
||||
it('returns correct payload with multiple option', async () => {
|
||||
const returnPayload = {
|
||||
overview: true,
|
||||
cluster: true,
|
||||
indices: true,
|
||||
};
|
||||
|
||||
upsertUIOpenOption.mockResolvedValue(returnPayload);
|
||||
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_open',
|
||||
payload: {
|
||||
overview: true,
|
||||
cluster: true,
|
||||
indices: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(resp.payload)).toEqual(returnPayload);
|
||||
});
|
||||
|
||||
it('returns an error if it throws', async () => {
|
||||
upsertUIOpenOption.mockRejectedValue(new Error(`scary error!`));
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_open',
|
||||
payload: {
|
||||
overview: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).toEqual(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => {
|
||||
it('returns correct payload with single option', async () => {
|
||||
const returnPayload = {
|
||||
close: false,
|
||||
open: false,
|
||||
start: true,
|
||||
stop: false,
|
||||
};
|
||||
|
||||
upsertUIReindexOption.mockResolvedValue(returnPayload);
|
||||
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_reindex',
|
||||
payload: {
|
||||
start: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(resp.payload)).toEqual(returnPayload);
|
||||
});
|
||||
|
||||
it('returns correct payload with multiple option', async () => {
|
||||
const returnPayload = {
|
||||
close: true,
|
||||
open: true,
|
||||
start: true,
|
||||
stop: true,
|
||||
};
|
||||
|
||||
upsertUIReindexOption.mockResolvedValue(returnPayload);
|
||||
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_reindex',
|
||||
payload: {
|
||||
close: true,
|
||||
open: true,
|
||||
start: true,
|
||||
stop: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(resp.payload)).toEqual(returnPayload);
|
||||
});
|
||||
|
||||
it('returns an error if it throws', async () => {
|
||||
upsertUIReindexOption.mockRejectedValue(new Error(`scary error!`));
|
||||
const resp = await server.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/upgrade_assistant/telemetry/ui_reindex',
|
||||
payload: {
|
||||
start: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).toEqual(500);
|
||||
});
|
||||
});
|
||||
});
|
56
x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts
Normal file
56
x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 Boom from 'boom';
|
||||
import Joi from 'joi';
|
||||
import { UpgradeAssistantTelemetryServer } from '../../common/types';
|
||||
import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis';
|
||||
import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis';
|
||||
|
||||
export function registerTelemetryRoutes(server: UpgradeAssistantTelemetryServer) {
|
||||
server.route({
|
||||
path: '/api/upgrade_assistant/telemetry/ui_open',
|
||||
method: 'PUT',
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
overview: Joi.boolean().default(false),
|
||||
cluster: Joi.boolean().default(false),
|
||||
indices: Joi.boolean().default(false),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request) {
|
||||
try {
|
||||
return await upsertUIOpenOption(server, request);
|
||||
} catch (e) {
|
||||
return Boom.boomify(e, { statusCode: 500 });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/upgrade_assistant/telemetry/ui_reindex',
|
||||
method: 'PUT',
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
close: Joi.boolean().default(false),
|
||||
open: Joi.boolean().default(false),
|
||||
start: Joi.boolean().default(false),
|
||||
stop: Joi.boolean().default(false),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request) {
|
||||
try {
|
||||
return await upsertUIReindexOption(server, request);
|
||||
} catch (e) {
|
||||
return Boom.boomify(e, { statusCode: 500 });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
|
@ -24,15 +24,15 @@ function flatKeys(source) {
|
|||
|
||||
const disableCollection = {
|
||||
'persistent':
|
||||
{
|
||||
xpack: {
|
||||
monitoring: {
|
||||
collection: {
|
||||
enabled: false
|
||||
{
|
||||
xpack: {
|
||||
monitoring: {
|
||||
collection: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function ({ getService }) {
|
||||
|
@ -174,4 +174,3 @@ export default function ({ getService }) {
|
|||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,10 @@ export default function upgradeAssistantFunctionalTests({
|
|||
|
||||
describe('Upgrade Checkup', () => {
|
||||
before(async () => await esArchiver.load('empty_kibana'));
|
||||
after(async () => await esArchiver.unload('empty_kibana'));
|
||||
after(async () => {
|
||||
await PageObjects.upgradeAssistant.expectTelemetryHasFinish();
|
||||
await esArchiver.unload('empty_kibana');
|
||||
});
|
||||
|
||||
it('allows user to navigate to upgrade checkup', async () => {
|
||||
await PageObjects.upgradeAssistant.navigateToPage();
|
||||
|
|
|
@ -67,6 +67,14 @@ export function UpgradeAssistantProvider({ getService, getPageObjects }) {
|
|||
expect(summaryElText).to.eql(summary);
|
||||
});
|
||||
}
|
||||
|
||||
async expectTelemetryHasFinish() {
|
||||
return await retry.try(async () => {
|
||||
log.debug('expectTelemetryHasFinish');
|
||||
const isTelemetryFinished = !(await testSubjects.exists('upgradeAssistantTelemetryRunning'));
|
||||
expect(isTelemetryFinished).to.equal(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new UpgradeAssistant();
|
||||
|
|
Loading…
Reference in a new issue