[Upgrade Assistant] Move out of legacy folder (#58034)

* Create x-pack/plugins skeleton for upgrade assistant

* Move public folder contents

Move the public folder of Upgrade Assistant and migrate public to use HttpSetup (remove axios)

* Include stylesheets in public

* Move server side out of legacy

Begin migration of Reindex worker to new platform

Move imports around so that it satsifies new platform constraints like not importing
server side code (even types) in client.

* Updated the routes with new dependencies and removed server shim

* Fix router unit tests

* Fix server lib tests

After changing function signatures for the reindex server factory (and others) all
of the tests needed to be revisited and brought in line with the new APIs.

Also used core/server mocks where appropriate

* Clean up types issues

* Fix setting credentials on request header

* Removed the security plugin from Upgrade Assistant

The security plugin is a potential future consumer of the Upgrade Assistant's deprecation feature
and we would therefore not want to create a circular depedency here. We pull in the licensing plugin
rather (as it is less likely that we will depend on that) and use it to determine whether security
is available and enabled.

* Migrate to config to new platform config

xpack.upgrade_assistant.enabled

* Remove unused types

* Fix import issue

* Move upgrade assistant back to Elasticsearch management section

* Update dotfiles

Added elasticsearch ui team as upgrade assistant code owner
Updated i18nrc.json path

* Alphabetical ordering in xpack/i18nrc.json

* Implemented PR feedback

Renamed callCluster -> callAsUser to be more consistent
with platform naming.

Added comment about why we are not using security plugin
directly inside of Upgrade Assistant.

Fixed long path imports and use 'src/core/..' throughout.

Fixed import ordering.

Renamed variables inside of telemetry lib.

* Revert to longer import path

In plugin.ts importing from 'kibana/server' or 'src/core/server'
results in a module not found error.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jean-Louis Leysens 2020-02-25 15:31:04 +01:00 committed by GitHub
parent d339740c2d
commit 899270a108
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
129 changed files with 1233 additions and 1342 deletions

2
.github/CODEOWNERS vendored
View file

@ -179,6 +179,8 @@
/x-pack/legacy/plugins/rollup/ @elastic/es-ui
/x-pack/plugins/searchprofiler/ @elastic/es-ui
/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui
/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui
/x-pack/plugins/upgrade_assistant/ @elastic/es-ui
/x-pack/plugins/watcher/ @elastic/es-ui
# Endpoint

View file

@ -40,7 +40,7 @@
"xpack.taskManager": "legacy/plugins/task_manager",
"xpack.transform": "legacy/plugins/transform",
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "legacy/plugins/upgrade_assistant",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
"xpack.uptime": "legacy/plugins/uptime",
"xpack.watcher": "plugins/watcher"
},

View file

@ -3,25 +3,14 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import Joi from 'joi';
import { Legacy } from 'kibana';
import { resolve } from 'path';
import mappings from './mappings.json';
import { plugin } from './server/np_ready';
import { CloudSetup } from '../../../plugins/cloud/server';
export function upgradeAssistant(kibana: any) {
const publicSrc = resolve(__dirname, 'public');
const npSrc = resolve(publicSrc, 'np_ready');
const config: Legacy.PluginSpecOptions = {
id: 'upgrade_assistant',
configPrefix: 'xpack.upgrade_assistant',
require: ['elasticsearch'],
uiExports: {
// @ts-ignore
managementSections: ['plugins/upgrade_assistant'],
savedObjectSchemas: {
'upgrade-assistant-reindex-operation': {
isNamespaceAgnostic: true,
@ -30,41 +19,10 @@ export function upgradeAssistant(kibana: any) {
isNamespaceAgnostic: true,
},
},
styleSheetPaths: resolve(npSrc, 'application/index.scss'),
mappings,
},
publicDir: publicSrc,
config() {
return Joi.object({
enabled: Joi.boolean().default(true),
}).default();
},
init(server: Legacy.Server) {
// Add server routes and initialize the plugin here
const instance = plugin({} as any);
const { usageCollection, cloud } = server.newPlatform.setup.plugins;
instance.setup(server.newPlatform.setup.core, {
usageCollection,
cloud: cloud as CloudSetup,
__LEGACY: {
// Legacy objects
events: server.events,
savedObjects: server.savedObjects,
// Legacy functions
log: server.log.bind(server),
// Legacy plugins
plugins: {
elasticsearch: server.plugins.elasticsearch,
xpack_main: server.plugins.xpack_main,
},
},
});
},
init() {},
};
return new kibana.Plugin(config);
}

View file

@ -1,7 +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.
*/
export * from './legacy';

View file

@ -1,106 +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 { ComponentType } from 'react';
import { i18n } from '@kbn/i18n';
/* LEGACY IMPORTS */
import { npSetup } from 'ui/new_platform';
import { wrapInI18nContext } from 'ui/i18n';
import { management } from 'ui/management';
// @ts-ignore
import { uiModules } from 'ui/modules';
import routes from 'ui/routes';
import chrome from 'ui/chrome';
/* LEGACY IMPORTS */
import { NEXT_MAJOR_VERSION } from '../common/version';
import { plugin } from './np_ready';
import { CloudSetup } from '../../../../plugins/cloud/public';
const BASE_PATH = `/management/elasticsearch/upgrade_assistant`;
export interface LegacyAppMountParameters {
__LEGACY: { renderToElement: (RootComponent: ComponentType<any>) => void };
}
export interface LegacyApp {
mount(ctx: any, params: LegacyAppMountParameters): void;
}
export interface LegacyManagementPlugin {
sections: {
get(
name: string
): {
registerApp(app: LegacyApp): void;
};
};
}
// Based on /rfcs/text/0006_management_section_service.md
export interface LegacyPlugins {
cloud?: CloudSetup;
management: LegacyManagementPlugin;
__LEGACY: {
XSRF: string;
};
}
function startApp() {
routes.when(`${BASE_PATH}/:view?`, {
template:
'<kbn-management-app section="elasticsearch/upgrade_assistant"><upgrade-assistant /></kbn-management-app>',
});
const { cloud } = npSetup.plugins as any;
const legacyPluginsShim: LegacyPlugins = {
cloud: cloud as CloudSetup,
__LEGACY: {
XSRF: chrome.getXsrfToken(),
},
management: {
sections: {
get(_: string) {
return {
registerApp(app) {
management.getSection('elasticsearch').register('upgrade_assistant', {
visible: true,
display: i18n.translate('xpack.upgradeAssistant.appTitle', {
defaultMessage: '{version} Upgrade Assistant',
values: { version: `${NEXT_MAJOR_VERSION}.0` },
}),
order: 100,
url: `#${BASE_PATH}`,
});
app.mount(
{},
{
__LEGACY: {
// While there is not an NP API for registering management section apps yet
renderToElement: RootComponent => {
uiModules
.get('kibana')
.directive('upgradeAssistant', (reactDirective: any) => {
return reactDirective(wrapInI18nContext(RootComponent));
});
},
},
}
);
},
};
},
},
},
};
const pluginInstance = plugin();
pluginInstance.setup(npSetup.core, legacyPluginsShim);
}
startApp();

View file

@ -1,40 +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 React from 'react';
import { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { NEXT_MAJOR_VERSION } from '../../../common/version';
import { UpgradeAssistantTabs } from './components/tabs';
import { AppContextProvider, ContextValue, AppContext } from './app_context';
type AppDependencies = ContextValue;
export const RootComponent = (deps: AppDependencies) => {
return (
<AppContextProvider value={deps}>
<div data-test-subj="upgradeAssistantRoot">
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="xpack.upgradeAssistant.appTitle"
defaultMessage="{version} Upgrade Assistant"
values={{ version: `${NEXT_MAJOR_VERSION}.0` }}
/>
</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<AppContext.Consumer>
{({ http }) => <UpgradeAssistantTabs http={http} />}
</AppContext.Consumer>
</div>
</AppContextProvider>
);
};

View file

@ -1,24 +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 { ReindexStatus, ReindexStep } from '../../../../../../../../common/types';
export const mockClient = {
post: jest.fn().mockResolvedValue({
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.inProgress,
}),
get: jest.fn().mockResolvedValue({
status: 200,
data: {
warnings: [],
reindexOp: null,
},
}),
};
jest.mock('axios', () => ({
create: jest.fn().mockReturnValue(mockClient),
}));

View file

@ -1,24 +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 { Plugin, CoreSetup } from 'src/core/public';
import { RootComponent } from './application/app';
import { LegacyPlugins } from '../legacy';
export class UpgradeAssistantUIPlugin implements Plugin {
async setup({ http }: CoreSetup, { cloud, management, __LEGACY: { XSRF } }: LegacyPlugins) {
const appRegistrar = management.sections.get('kibana');
const isCloudEnabled = !!(cloud && cloud.isCloudEnabled);
return appRegistrar.registerApp({
mount(__, { __LEGACY: { renderToElement } }) {
return renderToElement(() => RootComponent({ http, XSRF, isCloudEnabled }));
},
});
}
async start() {}
async stop() {}
}

View file

@ -1,7 +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.
*/
export { plugin } from './np_ready';

View file

@ -1,47 +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 {
UIOpen,
UIOpenOption,
UPGRADE_ASSISTANT_DOC_ID,
UPGRADE_ASSISTANT_TYPE,
} from '../../../../common/types';
import { RequestShim, ServerShim } from '../../types';
async function incrementUIOpenOptionCounter(server: ServerShim, 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: ServerShim, req: RequestShim): 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,
};
}

View file

@ -1,58 +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 {
UIReindex,
UIReindexOption,
UPGRADE_ASSISTANT_DOC_ID,
UPGRADE_ASSISTANT_TYPE,
} from '../../../../common/types';
import { RequestShim, ServerShim } from '../../types';
async function incrementUIReindexOptionCounter(
server: ServerShim,
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: ServerShim,
req: RequestShim
): 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,
};
}

View file

@ -1,49 +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 { Plugin, CoreSetup, CoreStart } from 'src/core/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { ServerShim, ServerShimWithRouter } from './types';
import { credentialStoreFactory } from './lib/reindexing/credential_store';
import { registerUpgradeAssistantUsageCollector } from './lib/telemetry';
import { registerClusterCheckupRoutes } from './routes/cluster_checkup';
import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging';
import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices';
import { CloudSetup } from '../../../../../plugins/cloud/server';
import { registerTelemetryRoutes } from './routes/telemetry';
interface PluginsSetup {
__LEGACY: ServerShim;
usageCollection: UsageCollectionSetup;
cloud?: CloudSetup;
}
export class UpgradeAssistantServerPlugin implements Plugin {
setup({ http }: CoreSetup, { __LEGACY, usageCollection, cloud }: PluginsSetup) {
const router = http.createRouter();
const shimWithRouter: ServerShimWithRouter = { ...__LEGACY, router };
registerClusterCheckupRoutes(shimWithRouter, { cloud });
registerDeprecationLoggingRoutes(shimWithRouter);
// The ReindexWorker uses a map of request headers that contain the authentication credentials
// for a given reindex. We cannot currently store these in an the .kibana index b/c we do not
// want to expose these credentials to any unauthenticated users. We also want to avoid any need
// to add a user for a special index just for upgrading. This in-memory cache allows us to
// process jobs without the browser staying on the page, but will require that jobs go into
// a paused state if no Kibana nodes have the required credentials.
const credentialStore = credentialStoreFactory();
const worker = registerReindexWorker(__LEGACY, credentialStore);
registerReindexIndicesRoutes(shimWithRouter, worker, credentialStore);
// Bootstrap the needed routes and the collector for the telemetry
registerTelemetryRoutes(shimWithRouter);
registerUpgradeAssistantUsageCollector(usageCollection, __LEGACY);
}
start(core: CoreStart, plugins: any) {}
stop(): void {}
}

View file

@ -1,46 +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 _ from 'lodash';
import { ServerShimWithRouter } from '../types';
import { getUpgradeAssistantStatus } from '../lib/es_migration_apis';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { CloudSetup } from '../../../../../../plugins/cloud/server';
import { createRequestShim } from './create_request_shim';
interface PluginsSetup {
cloud?: CloudSetup;
}
export function registerClusterCheckupRoutes(
server: ServerShimWithRouter,
pluginsSetup: PluginsSetup
) {
const { cloud } = pluginsSetup;
const isCloudEnabled = !!(cloud && cloud.isCloudEnabled);
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
server.router.get(
{
path: '/api/upgrade_assistant/status',
validate: false,
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
try {
return response.ok({
body: await getUpgradeAssistantStatus(callWithRequest, reqShim, isCloudEnabled),
});
} catch (e) {
if (e.status === 403) {
return response.forbidden(e.message);
}
return response.internalError({ body: e });
}
})
);
}

View file

@ -1,16 +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 { KibanaRequest } from 'kibana/server';
import { RequestShim } from '../types';
export const createRequestShim = (req: KibanaRequest): RequestShim => {
return {
headers: req.headers as Record<string, string>,
payload: req.body || (req as any).payload,
params: req.params,
};
};

View file

@ -1,57 +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 { schema } from '@kbn/config-schema';
import {
getDeprecationLoggingStatus,
setDeprecationLogging,
} from '../lib/es_deprecation_logging_apis';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { ServerShimWithRouter } from '../types';
import { createRequestShim } from './create_request_shim';
export function registerDeprecationLoggingRoutes(server: ServerShimWithRouter) {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
server.router.get(
{
path: '/api/upgrade_assistant/deprecation_logging',
validate: false,
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
try {
const result = await getDeprecationLoggingStatus(callWithRequest, reqShim);
return response.ok({ body: result });
} catch (e) {
return response.internalError({ body: e });
}
})
);
server.router.put(
{
path: '/api/upgrade_assistant/deprecation_logging',
validate: {
body: schema.object({
isEnabled: schema.boolean(),
}),
},
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
try {
const { isEnabled } = reqShim.payload as { isEnabled: boolean };
return response.ok({
body: await setDeprecationLogging(callWithRequest, reqShim, isEnabled),
});
} catch (e) {
return response.internalError({ body: e });
}
})
);
}

View file

@ -1,207 +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 { schema } from '@kbn/config-schema';
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
import { SavedObjectsClientContract } from 'kibana/server';
import { ReindexStatus } from '../../../common/types';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing';
import { CredentialStore } from '../lib/reindexing/credential_store';
import { reindexActionsFactory } from '../lib/reindexing/reindex_actions';
import { ServerShim, ServerShimWithRouter } from '../types';
import { createRequestShim } from './create_request_shim';
export function registerReindexWorker(server: ServerShim, credentialStore: CredentialStore) {
const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster(
'admin'
);
const xpackInfo = server.plugins.xpack_main.info;
const savedObjectsRepository = server.savedObjects.getSavedObjectsRepository(
callWithInternalUser
);
const savedObjectsClient = new server.savedObjects.SavedObjectsClient(
savedObjectsRepository
) as SavedObjectsClientContract;
// Cannot pass server.log directly because it's value changes during startup (?).
// Use this function to proxy through.
const log = (tags: string | string[], data?: string | object | (() => any), timestamp?: number) =>
server.log(tags, data, timestamp);
const worker = new ReindexWorker(
savedObjectsClient,
credentialStore,
callWithRequest,
callWithInternalUser,
xpackInfo,
log
);
// Wait for ES connection before starting the polling loop.
server.plugins.elasticsearch.waitUntilReady().then(() => {
worker.start();
server.events.on('stop', () => worker.stop());
});
return worker;
}
export function registerReindexIndicesRoutes(
server: ServerShimWithRouter,
worker: ReindexWorker,
credentialStore: CredentialStore
) {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
const xpackInfo = server.plugins.xpack_main.info;
const BASE_PATH = '/api/upgrade_assistant/reindex';
// Start reindex for an index
server.router.post(
{
path: `${BASE_PATH}/{indexName}`,
validate: {
params: schema.object({
indexName: schema.string(),
}),
},
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
const { indexName } = reqShim.params;
const { client } = ctx.core.savedObjects;
const callCluster = callWithRequest.bind(null, reqShim) as CallCluster;
const reindexActions = reindexActionsFactory(client, callCluster);
const reindexService = reindexServiceFactory(
callCluster,
xpackInfo,
reindexActions,
server.log
);
try {
if (!(await reindexService.hasRequiredPrivileges(indexName))) {
return response.forbidden({
body: `You do not have adequate privileges to reindex this index.`,
});
}
const existingOp = await reindexService.findReindexOperation(indexName);
// If the reindexOp already exists and it's paused, resume it. Otherwise create a new one.
const reindexOp =
existingOp && existingOp.attributes.status === ReindexStatus.paused
? await reindexService.resumeReindexOperation(indexName)
: await reindexService.createReindexOperation(indexName);
// Add users credentials for the worker to use
credentialStore.set(reindexOp, reqShim.headers);
// Kick the worker on this node to immediately pickup the new reindex operation.
worker.forceRefresh();
return response.ok({ body: reindexOp.attributes });
} catch (e) {
return response.internalError({ body: e });
}
})
);
// Get status
server.router.get(
{
path: `${BASE_PATH}/{indexName}`,
validate: {
params: schema.object({
indexName: schema.string(),
}),
},
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
const { client } = ctx.core.savedObjects;
const { indexName } = reqShim.params;
const callCluster = callWithRequest.bind(null, reqShim) as CallCluster;
const reindexActions = reindexActionsFactory(client, callCluster);
const reindexService = reindexServiceFactory(
callCluster,
xpackInfo,
reindexActions,
server.log
);
try {
const hasRequiredPrivileges = await reindexService.hasRequiredPrivileges(indexName);
const reindexOp = await reindexService.findReindexOperation(indexName);
// If the user doesn't have privileges than querying for warnings is going to fail.
const warnings = hasRequiredPrivileges
? await reindexService.detectReindexWarnings(indexName)
: [];
const indexGroup = reindexService.getIndexGroup(indexName);
return response.ok({
body: {
reindexOp: reindexOp ? reindexOp.attributes : null,
warnings,
indexGroup,
hasRequiredPrivileges,
},
});
} catch (e) {
if (!e.isBoom) {
return response.internalError({ body: e });
}
return response.customError({
body: {
message: e.message,
},
statusCode: e.statusCode,
});
}
})
);
// Cancel reindex
server.router.post(
{
path: `${BASE_PATH}/{indexName}/cancel`,
validate: {
params: schema.object({
indexName: schema.string(),
}),
},
},
versionCheckHandlerWrapper(async (ctx, request, response) => {
const reqShim = createRequestShim(request);
const { indexName } = reqShim.params;
const { client } = ctx.core.savedObjects;
const callCluster = callWithRequest.bind(null, reqShim) as CallCluster;
const reindexActions = reindexActionsFactory(client, callCluster);
const reindexService = reindexServiceFactory(
callCluster,
xpackInfo,
reindexActions,
server.log
);
try {
await reindexService.cancelReindexing(indexName);
return response.ok({ body: { acknowledged: true } });
} catch (e) {
if (!e.isBoom) {
return response.internalError({ body: e });
}
return response.customError({
body: {
message: e.message,
},
statusCode: e.statusCode,
});
}
})
);
}

View file

@ -1,29 +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 { Legacy } from 'kibana';
import { IRouter } from 'src/core/server';
import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch';
import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
export interface ServerShim {
plugins: {
elasticsearch: ElasticsearchPlugin;
xpack_main: XPackMainPlugin;
};
log: any;
events: any;
savedObjects: Legacy.SavedObjectsService;
}
export interface ServerShimWithRouter extends ServerShim {
router: IRouter;
}
export interface RequestShim {
headers: Record<string, string>;
payload: any;
params: any;
}

View file

@ -4,8 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { UpgradeAssistantUIPlugin } from './plugin';
import { schema, TypeOf } from '@kbn/config-schema';
export const plugin = () => {
return new UpgradeAssistantUIPlugin();
};
export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
});
export type Config = TypeOf<typeof configSchema>;

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch';
import { SavedObject, SavedObjectAttributes } from 'src/core/public';
export enum ReindexStep {
@ -114,3 +115,15 @@ export interface UpgradeAssistantTelemetry {
export interface UpgradeAssistantTelemetrySavedObjectAttributes {
[key: string]: any;
}
export interface EnrichedDeprecationInfo extends DeprecationInfo {
index?: string;
node?: string;
reindex?: boolean;
}
export interface UpgradeAssistantStatus {
readyForUpgrade: boolean;
cluster: EnrichedDeprecationInfo[];
indices: EnrichedDeprecationInfo[];
}

View file

@ -0,0 +1,9 @@
{
"id": "upgradeAssistant",
"version": "kibana",
"server": true,
"ui": true,
"configPath": ["xpack", "upgrade_assistant"],
"requiredPlugins": ["management", "licensing"],
"optionalPlugins": ["cloud", "usageCollection"]
}

View file

@ -0,0 +1,44 @@
/*
* 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 React from 'react';
import { I18nStart } from 'src/core/public';
import { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { NEXT_MAJOR_VERSION } from '../../common/version';
import { UpgradeAssistantTabs } from './components/tabs';
import { AppContextProvider, ContextValue, AppContext } from './app_context';
export interface AppDependencies extends ContextValue {
i18n: I18nStart;
}
export const RootComponent = ({ i18n, ...contexValue }: AppDependencies) => {
return (
<i18n.Context>
<AppContextProvider value={contexValue}>
<div data-test-subj="upgradeAssistantRoot">
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="xpack.upgradeAssistant.appTitle"
defaultMessage="{version} Upgrade Assistant"
values={{ version: `${NEXT_MAJOR_VERSION}.0` }}
/>
</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<AppContext.Consumer>
{({ http }) => <UpgradeAssistantTabs http={http} />}
</AppContext.Consumer>
</div>
</AppContextProvider>
</i18n.Context>
);
};

View file

@ -9,7 +9,6 @@ import React, { createContext, useContext } from 'react';
export interface ContextValue {
http: HttpSetup;
isCloudEnabled: boolean;
XSRF: string;
}
export const AppContext = createContext<ContextValue>({} as any);

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../common/version';
import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../common/version';
export const LatestMinorBanner: React.FunctionComponent = () => (
<EuiCallOut

View file

@ -6,51 +6,39 @@
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
jest.mock('axios', () => ({
get: jest.fn(),
create: jest.fn(),
}));
import { httpServiceMock } from 'src/core/public/mocks';
import { UpgradeAssistantTabs } from './tabs';
import { LoadingState } from './types';
import axios from 'axios';
import { OverviewTab } from './tabs/overview';
// Used to wait for promises to resolve and renders to finish before reading updates
const promisesToResolve = () => new Promise(resolve => setTimeout(resolve, 0));
const mockHttp = {
basePath: {
prepend: () => 'test',
},
fetch() {},
};
const mockHttp = httpServiceMock.createSetupContract();
describe('UpgradeAssistantTabs', () => {
test('renders loading state', async () => {
// @ts-ignore
axios.get.mockReturnValue(
mockHttp.get.mockReturnValue(
new Promise(resolve => {
/* never resolve */
})
);
const wrapper = mountWithIntl(<UpgradeAssistantTabs http={mockHttp as any} />);
const wrapper = mountWithIntl(<UpgradeAssistantTabs http={mockHttp} />);
// Should pass down loading status to child component
expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Loading);
});
test('successful data fetch', async () => {
// @ts-ignore
axios.get.mockResolvedValue({
mockHttp.get.mockResolvedValue({
data: {
cluster: [],
indices: [],
},
});
const wrapper = mountWithIntl(<UpgradeAssistantTabs http={mockHttp as any} />);
expect(axios.get).toHaveBeenCalled();
expect(mockHttp.get).toHaveBeenCalled();
await promisesToResolve();
wrapper.update();
// Should pass down success status to child component
@ -59,7 +47,7 @@ describe('UpgradeAssistantTabs', () => {
test('network failure', async () => {
// @ts-ignore
axios.get.mockRejectedValue(new Error(`oh no!`));
mockHttp.get.mockRejectedValue(new Error(`oh no!`));
const wrapper = mountWithIntl(<UpgradeAssistantTabs http={mockHttp as any} />);
await promisesToResolve();
wrapper.update();
@ -69,7 +57,7 @@ describe('UpgradeAssistantTabs', () => {
it('upgrade error', async () => {
// @ts-ignore
axios.get.mockRejectedValue({ response: { status: 426 } });
mockHttp.get.mockRejectedValue({ response: { status: 426 } });
const wrapper = mountWithIntl(<UpgradeAssistantTabs http={mockHttp as any} />);
await promisesToResolve();
wrapper.update();

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import axios from 'axios';
import { findIndex, get, set } from 'lodash';
import React from 'react';
@ -18,7 +17,7 @@ import {
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { HttpSetup } from 'src/core/public';
import { UpgradeAssistantStatus } from '../../../../server/np_ready/lib/es_migration_apis';
import { UpgradeAssistantStatus } from '../../../common/types';
import { LatestMinorBanner } from './latest_minor_banner';
import { CheckupTab } from './tabs/checkup';
import { OverviewTab } from './tabs/overview';
@ -153,12 +152,10 @@ export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
private loadData = async () => {
try {
this.setState({ loadingState: LoadingState.Loading });
const resp = await axios.get(
this.props.http.basePath.prepend('/api/upgrade_assistant/status')
);
const resp = await this.props.http.get('/api/upgrade_assistant/status');
this.setState({
loadingState: LoadingState.Success,
checkupData: resp.data,
checkupData: resp,
});
} catch (e) {
if (get(e, 'response.status') === 426) {

View file

@ -18,7 +18,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { NEXT_MAJOR_VERSION } from '../../../../../../common/version';
import { NEXT_MAJOR_VERSION } from '../../../../../common/version';
import { LoadingErrorBanner } from '../../error_banner';
import {
GroupByOption,

View file

@ -79,9 +79,7 @@ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({
{reindexIndexName && (
<EuiFlexItem grow={false}>
<AppContext.Consumer>
{({ http, XSRF }) => (
<ReindexButton indexName={reindexIndexName} http={http} xsrf={XSRF} />
)}
{({ http }) => <ReindexButton indexName={reindexIndexName} http={http} />}
</AppContext.Consumer>
</EuiFlexItem>
)}

View file

@ -8,7 +8,7 @@ import React, { Fragment, FunctionComponent } from 'react';
import { EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
export const DeprecationCountSummary: FunctionComponent<{
deprecations: EnrichedDeprecationInfo[];

View file

@ -11,7 +11,7 @@ import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { EuiBadge, EuiPagination } from '@elastic/eui';
import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch';
import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GroupByOption, LevelFilterOption } from '../../../types';
import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped';

View file

@ -19,7 +19,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch';
import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GroupByOption, LevelFilterOption } from '../../../types';
import { DeprecationCountSummary } from './count_summary';

View file

@ -148,9 +148,7 @@ export class IndexDeprecationTableUI extends React.Component<
render(indexDep: IndexDeprecationDetails) {
return (
<AppContext.Consumer>
{({ XSRF, http }) => (
<ReindexButton indexName={indexDep.index!} http={http} xsrf={XSRF} />
)}
{({ http }) => <ReindexButton indexName={indexDep.index!} http={http} />}
</AppContext.Consumer>
);
},

View file

@ -7,7 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GroupByOption } from '../../../types';
import { DeprecationList } from './list';

View file

@ -7,7 +7,7 @@
import React, { FunctionComponent } from 'react';
import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch';
import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GroupByOption } from '../../../types';
import { COLOR_MAP, LEVEL_MAP } from '../constants';

View file

@ -11,14 +11,13 @@ import { Subscription } from 'rxjs';
import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { HttpSetup } from 'src/core/public';
import { ReindexStatus, UIReindexOption } from '../../../../../../../../common/types';
import { ReindexStatus, UIReindexOption } from '../../../../../../../common/types';
import { LoadingState } from '../../../../types';
import { ReindexFlyout } from './flyout';
import { ReindexPollingService, ReindexState } from './polling_service';
interface ReindexButtonProps {
indexName: string;
xsrf: string;
http: HttpSetup;
}
@ -154,8 +153,8 @@ export class ReindexButton extends React.Component<ReindexButtonProps, ReindexBu
}
private newService() {
const { indexName, xsrf, http } = this.props;
return new ReindexPollingService(indexName, xsrf, http);
const { indexName, http } = this.props;
return new ReindexPollingService(indexName, http);
}
private subscribeToUpdates() {

View file

@ -8,7 +8,7 @@ import { shallow } from 'enzyme';
import { cloneDeep } from 'lodash';
import React from 'react';
import { ReindexStatus, ReindexWarning } from '../../../../../../../../../common/types';
import { ReindexStatus, ReindexWarning } from '../../../../../../../../common/types';
import { LoadingState } from '../../../../../types';
import { ReindexState } from '../polling_service';
import { ChecklistFlyoutStep } from './checklist_step';

View file

@ -19,7 +19,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ReindexStatus } from '../../../../../../../../../common/types';
import { ReindexStatus } from '../../../../../../../../common/types';
import { LoadingState } from '../../../../../types';
import { ReindexState } from '../polling_service';
import { ReindexProgress } from './progress';

View file

@ -7,7 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../../common/types';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../common/types';
import { ReindexState } from '../polling_service';
import { ReindexProgress } from './progress';

View file

@ -16,7 +16,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../../common/types';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../common/types';
import { LoadingState } from '../../../../../types';
import { ReindexState } from '../polling_service';
import { StepProgress, StepProgressStep } from './step_progress';

View file

@ -8,7 +8,7 @@ import { I18nProvider } from '@kbn/i18n/react';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { ReindexWarning } from '../../../../../../../../../common/types';
import { ReindexWarning } from '../../../../../../../../common/types';
import { idForWarning, WarningsFlyoutStep } from './warnings_step';
describe('WarningsFlyoutStep', () => {

View file

@ -21,7 +21,7 @@ import {
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ReindexWarning } from '../../../../../../../../../common/types';
import { ReindexWarning } from '../../../../../../../../common/types';
interface CheckedIds {
[id: string]: boolean;

View file

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mockClient } from './polling_service.test.mocks';
import { ReindexStatus, ReindexStep } from '../../../../../../../../common/types';
import { ReindexStatus, ReindexStep } from '../../../../../../../common/types';
import { ReindexPollingService } from './polling_service';
import { httpServiceMock } from 'src/core/public/http/http_service.mock';
const mockClient = httpServiceMock.createSetupContract();
describe('ReindexPollingService', () => {
beforeEach(() => {
mockClient.post.mockReset();
@ -18,18 +18,11 @@ describe('ReindexPollingService', () => {
it('does not poll when reindexOp is null', async () => {
mockClient.get.mockResolvedValueOnce({
status: 200,
data: {
warnings: [],
reindexOp: null,
},
warnings: [],
reindexOp: null,
});
const service = new ReindexPollingService(
'myIndex',
'myXsrf',
httpServiceMock.createSetupContract()
);
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval
@ -39,22 +32,15 @@ describe('ReindexPollingService', () => {
it('does not poll when first check is a 200 and status is failed', async () => {
mockClient.get.mockResolvedValue({
status: 200,
data: {
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.failed,
errorMessage: `Oh no!`,
},
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.failed,
errorMessage: `Oh no!`,
},
});
const service = new ReindexPollingService(
'myIndex',
'myXsrf',
httpServiceMock.createSetupContract()
);
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval
@ -65,21 +51,14 @@ describe('ReindexPollingService', () => {
it('begins to poll when first check is a 200 and status is inProgress', async () => {
mockClient.get.mockResolvedValue({
status: 200,
data: {
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.inProgress,
},
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.inProgress,
},
});
const service = new ReindexPollingService(
'myIndex',
'myXsrf',
httpServiceMock.createSetupContract()
);
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval
@ -89,11 +68,7 @@ describe('ReindexPollingService', () => {
describe('startReindex', () => {
it('posts to endpoint', async () => {
const service = new ReindexPollingService(
'myIndex',
'myXsrf',
httpServiceMock.createSetupContract()
);
const service = new ReindexPollingService('myIndex', mockClient);
await service.startReindex();
expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex');
@ -102,11 +77,7 @@ describe('ReindexPollingService', () => {
describe('cancelReindex', () => {
it('posts to cancel endpoint', async () => {
const service = new ReindexPollingService(
'myIndex',
'myXsrf',
httpServiceMock.createSetupContract()
);
const service = new ReindexPollingService('myIndex', mockClient);
await service.cancelReindex();
expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex/cancel');

View file

@ -3,8 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import axios, { AxiosInstance } from 'axios';
import { BehaviorSubject } from 'rxjs';
import { HttpSetup } from 'src/core/public';
@ -14,7 +12,7 @@ import {
ReindexStatus,
ReindexStep,
ReindexWarning,
} from '../../../../../../../../common/types';
} from '../../../../../../../common/types';
import { LoadingState } from '../../../../types';
const POLL_INTERVAL = 1000;
@ -45,24 +43,13 @@ interface StatusResponse {
export class ReindexPollingService {
public status$: BehaviorSubject<ReindexState>;
private pollTimeout?: NodeJS.Timeout;
private APIClient: AxiosInstance;
constructor(private indexName: string, private xsrf: string, private http: HttpSetup) {
constructor(private indexName: string, private http: HttpSetup) {
this.status$ = new BehaviorSubject<ReindexState>({
loadingState: LoadingState.Loading,
errorMessage: null,
reindexTaskPercComplete: null,
});
this.APIClient = axios.create({
headers: {
Accept: 'application/json',
credentials: 'same-origin',
'Content-Type': 'application/json',
'kbn-version': this.xsrf,
'kbn-xsrf': this.xsrf,
},
});
}
public updateStatus = async () => {
@ -70,8 +57,8 @@ export class ReindexPollingService {
this.stopPolling();
try {
const { data } = await this.APIClient.get<StatusResponse>(
this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`)
const data = await this.http.get<StatusResponse>(
`/api/upgrade_assistant/reindex/${this.indexName}`
);
this.updateWithResponse(data);
@ -107,8 +94,8 @@ export class ReindexPollingService {
errorMessage: null,
cancelLoadingState: undefined,
});
const { data } = await this.APIClient.post<ReindexOperation>(
this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`)
const data = await this.http.post<ReindexOperation>(
`/api/upgrade_assistant/reindex/${this.indexName}`
);
this.updateWithResponse({ reindexOp: data });
@ -125,9 +112,7 @@ export class ReindexPollingService {
cancelLoadingState: LoadingState.Loading,
});
await this.APIClient.post(
this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`)
);
await this.http.post(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`);
} catch (e) {
this.status$.next({
...this.status$.value,

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import axios from 'axios';
import React from 'react';
import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui';
@ -15,7 +14,6 @@ import { HttpSetup } from 'src/core/public';
import { LoadingState } from '../../types';
interface DeprecationLoggingTabProps extends ReactIntl.InjectedIntlProps {
xsrf: string;
http: HttpSetup;
}
@ -88,12 +86,10 @@ export class DeprecationLoggingToggleUI extends React.Component<
private loadData = async () => {
try {
this.setState({ loadingState: LoadingState.Loading });
const resp = await axios.get(
this.props.http.basePath.prepend('/api/upgrade_assistant/deprecation_logging')
);
const resp = await this.props.http.get('/api/upgrade_assistant/deprecation_logging');
this.setState({
loadingState: LoadingState.Success,
loggingEnabled: resp.data.isEnabled,
loggingEnabled: resp.isEnabled,
});
} catch (e) {
this.setState({ loadingState: LoadingState.Error });
@ -102,26 +98,19 @@ export class DeprecationLoggingToggleUI extends React.Component<
private toggleLogging = async () => {
try {
const { http, xsrf } = this.props;
// Optimistically toggle the UI
const newEnabled = !this.state.loggingEnabled;
this.setState({ loadingState: LoadingState.Loading, loggingEnabled: newEnabled });
const resp = await axios.put(
http.basePath.prepend('/api/upgrade_assistant/deprecation_logging'),
{
const resp = await this.props.http.put('/api/upgrade_assistant/deprecation_logging', {
body: JSON.stringify({
isEnabled: newEnabled,
},
{
headers: {
'kbn-xsrf': xsrf,
},
}
);
}),
});
this.setState({
loadingState: LoadingState.Success,
loggingEnabled: resp.data.isEnabled,
loggingEnabled: resp.isEnabled,
});
} catch (e) {
this.setState({ loadingState: LoadingState.Error });

View file

@ -17,7 +17,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { NEXT_MAJOR_VERSION } from '../../../../../../common/version';
import { NEXT_MAJOR_VERSION } from '../../../../../common/version';
import { LoadingErrorBanner } from '../../error_banner';
import { LoadingState, UpgradeAssistantTabProps } from '../../types';
import { Steps } from './steps';

View file

@ -19,7 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../../../common/version';
import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../../common/version';
import { UpgradeAssistantTabProps } from '../../types';
import { DeprecationLoggingToggle } from './deprecation_logging_toggle';
import { useAppContext } from '../../../app_context';
@ -104,7 +104,7 @@ export const StepsUI: FunctionComponent<UpgradeAssistantTabProps & ReactIntl.Inj
}, {} as { [checkupType: string]: number });
// Uncomment when START_UPGRADE_STEP is in use!
const { http, XSRF /* , isCloudEnabled */ } = useAppContext();
const { http /* , isCloudEnabled */ } = useAppContext();
return (
<EuiSteps
@ -262,7 +262,7 @@ export const StepsUI: FunctionComponent<UpgradeAssistantTabProps & ReactIntl.Inj
})}
describedByIds={['deprecation-logging']}
>
<DeprecationLoggingToggle http={http} xsrf={XSRF} />
<DeprecationLoggingToggle http={http} />
</EuiFormRow>
</Fragment>
),

View file

@ -6,10 +6,7 @@
import React from 'react';
import {
EnrichedDeprecationInfo,
UpgradeAssistantStatus,
} from '../../../../server/np_ready/lib/es_migration_apis';
import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../../common/types';
export interface UpgradeAssistantTabProps {
alertBanner?: React.ReactNode;

View file

@ -0,0 +1,20 @@
/*
* 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 React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { AppDependencies, RootComponent } from './app';
interface BootDependencies extends AppDependencies {
element: HTMLElement;
}
export const renderApp = (deps: BootDependencies) => {
const { element, ...appDependencies } = deps;
render(<RootComponent {...appDependencies} />, element);
return () => {
unmountComponentAtNode(element);
};
};

View file

@ -0,0 +1 @@
@import './application/index';

View file

@ -3,9 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { PluginInitializerContext } from 'src/core/server';
import { UpgradeAssistantServerPlugin } from './plugin';
import './index.scss';
import { PluginInitializerContext } from 'src/core/public';
import { UpgradeAssistantUIPlugin } from './plugin';
export const plugin = (ctx: PluginInitializerContext) => {
return new UpgradeAssistantServerPlugin();
return new UpgradeAssistantUIPlugin(ctx);
};

View file

@ -0,0 +1,48 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public';
import { CloudSetup } from '../../cloud/public';
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { NEXT_MAJOR_VERSION } from '../common/version';
import { Config } from '../common/config';
import { renderApp } from './application/render_app';
interface Dependencies {
cloud: CloudSetup;
management: ManagementSetup;
}
export class UpgradeAssistantUIPlugin implements Plugin {
constructor(private ctx: PluginInitializerContext) {}
setup({ http, getStartServices }: CoreSetup, { cloud, management }: Dependencies) {
const { enabled } = this.ctx.config.get<Config>();
if (!enabled) {
return;
}
const appRegistrar = management.sections.getSection('elasticsearch')!;
const isCloudEnabled = Boolean(cloud?.isCloudEnabled);
appRegistrar.registerApp({
id: 'upgrade_assistant',
title: i18n.translate('xpack.upgradeAssistant.appTitle', {
defaultMessage: '{version} Upgrade Assistant',
values: { version: `${NEXT_MAJOR_VERSION}.0` },
}),
order: 1000,
async mount({ element }) {
const [{ i18n: i18nDep }] = await getStartServices();
return renderApp({ element, isCloudEnabled, http, i18n: i18nDep });
},
});
}
start() {}
stop() {}
}

View file

@ -0,0 +1,19 @@
/*
* 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 { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server';
import { UpgradeAssistantServerPlugin } from './plugin';
import { configSchema } from '../common/config';
export const plugin = (ctx: PluginInitializerContext) => {
return new UpgradeAssistantServerPlugin(ctx);
};
export const config: PluginConfigDescriptor = {
schema: configSchema,
exposeToBrowser: {
enabled: true,
},
};

View file

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import {
getDeprecationLoggingStatus,
isDeprecationLoggingEnabled,
@ -12,9 +12,9 @@ import {
describe('getDeprecationLoggingStatus', () => {
it('calls cluster.getSettings', async () => {
const callWithRequest = jest.fn();
await getDeprecationLoggingStatus(callWithRequest, {} as any);
expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.getSettings', {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
await getDeprecationLoggingStatus(dataClient);
expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.getSettings', {
includeDefaults: true,
});
});
@ -23,9 +23,9 @@ describe('getDeprecationLoggingStatus', () => {
describe('setDeprecationLogging', () => {
describe('isEnabled = true', () => {
it('calls cluster.putSettings with logger.deprecation = WARN', async () => {
const callWithRequest = jest.fn();
await setDeprecationLogging(callWithRequest, {} as any, true);
expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
await setDeprecationLogging(dataClient, true);
expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', {
body: { transient: { 'logger.deprecation': 'WARN' } },
});
});
@ -33,9 +33,9 @@ describe('setDeprecationLogging', () => {
describe('isEnabled = false', () => {
it('calls cluster.putSettings with logger.deprecation = ERROR', async () => {
const callWithRequest = jest.fn();
await setDeprecationLogging(callWithRequest, {} as any, false);
expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
await setDeprecationLogging(dataClient, false);
expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', {
body: { transient: { 'logger.deprecation': 'ERROR' } },
});
});

View file

@ -4,19 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import { CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch';
import { RequestShim } from '../types';
import { IScopedClusterClient } from 'src/core/server';
interface DeprecationLoggingStatus {
isEnabled: boolean;
}
export async function getDeprecationLoggingStatus(
callWithRequest: CallClusterWithRequest,
req: RequestShim
dataClient: IScopedClusterClient
): Promise<DeprecationLoggingStatus> {
const response = await callWithRequest(req, 'cluster.getSettings', {
const response = await dataClient.callAsCurrentUser('cluster.getSettings', {
includeDefaults: true,
});
@ -26,11 +23,10 @@ export async function getDeprecationLoggingStatus(
}
export async function setDeprecationLogging(
callWithRequest: CallClusterWithRequest,
req: RequestShim,
dataClient: IScopedClusterClient,
isEnabled: boolean
): Promise<DeprecationLoggingStatus> {
const response = await callWithRequest(req, 'cluster.putSettings', {
const response = await dataClient.callAsCurrentUser('cluster.putSettings', {
body: {
transient: {
'logger.deprecation': isEnabled ? 'WARN' : 'ERROR',

View file

@ -5,6 +5,7 @@
*/
import _ from 'lodash';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { getUpgradeAssistantStatus } from './es_migration_apis';
import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch';
@ -13,7 +14,8 @@ import fakeDeprecations from './__fixtures__/fake_deprecations.json';
describe('getUpgradeAssistantStatus', () => {
let deprecationsResponse: DeprecationAPIResponse;
const callWithRequest = jest.fn().mockImplementation(async (req, api, { path }) => {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
(dataClient.callAsCurrentUser as jest.Mock).mockImplementation(async (api, { path }) => {
if (path === '/_migration/deprecations') {
return deprecationsResponse;
} else if (api === 'indices.getMapping') {
@ -28,15 +30,15 @@ describe('getUpgradeAssistantStatus', () => {
});
it('calls /_migration/deprecations', async () => {
await getUpgradeAssistantStatus(callWithRequest, {} as any, false);
expect(callWithRequest).toHaveBeenCalledWith({}, 'transport.request', {
await getUpgradeAssistantStatus(dataClient, false);
expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('transport.request', {
path: '/_migration/deprecations',
method: 'GET',
});
});
it('returns the correct shape of data', async () => {
const resp = await getUpgradeAssistantStatus(callWithRequest, {} as any, false);
const resp = await getUpgradeAssistantStatus(dataClient, false);
expect(resp).toMatchSnapshot();
});
@ -48,9 +50,10 @@ describe('getUpgradeAssistantStatus', () => {
index_settings: {},
};
await expect(
getUpgradeAssistantStatus(callWithRequest, {} as any, false)
).resolves.toHaveProperty('readyForUpgrade', false);
await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty(
'readyForUpgrade',
false
);
});
it('returns readyForUpgrade === true when no critical issues found', async () => {
@ -61,9 +64,10 @@ describe('getUpgradeAssistantStatus', () => {
index_settings: {},
};
await expect(
getUpgradeAssistantStatus(callWithRequest, {} as any, false)
).resolves.toHaveProperty('readyForUpgrade', true);
await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty(
'readyForUpgrade',
true
);
});
it('filters out security realm deprecation on Cloud', async () => {
@ -80,7 +84,7 @@ describe('getUpgradeAssistantStatus', () => {
index_settings: {},
};
const result = await getUpgradeAssistantStatus(callWithRequest, {} as any, true);
const result = await getUpgradeAssistantStatus(dataClient, true);
expect(result).toHaveProperty('readyForUpgrade', true);
expect(result).toHaveProperty('cluster', []);

View file

@ -4,32 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
CallClusterWithRequest,
DeprecationAPIResponse,
DeprecationInfo,
} from 'src/legacy/core_plugins/elasticsearch';
import { RequestShim } from '../types';
export interface EnrichedDeprecationInfo extends DeprecationInfo {
index?: string;
node?: string;
reindex?: boolean;
}
export interface UpgradeAssistantStatus {
readyForUpgrade: boolean;
cluster: EnrichedDeprecationInfo[];
indices: EnrichedDeprecationInfo[];
}
import { IScopedClusterClient } from 'src/core/server';
import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch';
import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../common/types';
export async function getUpgradeAssistantStatus(
callWithRequest: CallClusterWithRequest,
req: RequestShim,
dataClient: IScopedClusterClient,
isCloudEnabled: boolean
): Promise<UpgradeAssistantStatus> {
const deprecations = await callWithRequest(req, 'transport.request', {
const deprecations = await dataClient.callAsCurrentUser('transport.request', {
path: '/_migration/deprecations',
method: 'GET',
});

View file

@ -6,7 +6,7 @@
import { SemVer } from 'semver';
import { IScopedClusterClient, kibanaResponseFactory } from 'src/core/server';
import { CURRENT_VERSION } from '../../../common/version';
import { CURRENT_VERSION } from '../../common/version';
import {
esVersionCheck,
getAllNodeVersions,

View file

@ -13,7 +13,7 @@ import {
RequestHandler,
RequestHandlerContext,
} from 'src/core/server';
import { CURRENT_VERSION } from '../../../common/version';
import { CURRENT_VERSION } from '../../common/version';
/**
* Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster.

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ReindexSavedObject } from '../../../../common/types';
import { ReindexSavedObject } from '../../../common/types';
import { Credential, credentialStoreFactory } from './credential_store';
describe('credentialStore', () => {

View file

@ -5,12 +5,11 @@
*/
import { createHash } from 'crypto';
import { Request } from 'hapi';
import stringify from 'json-stable-stringify';
import { ReindexSavedObject } from '../../../../common/types';
import { ReindexSavedObject } from '../../../common/types';
export type Credential = Request['headers'];
export type Credential = Record<string, any>;
/**
* An in-memory cache for user credentials to be used for reindexing operations. When looking up

Some files were not shown because too many files have changed in this diff Show more