From def97bd73435efe509b55809a5c1551bf062c7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 11 Aug 2021 16:56:23 +0100 Subject: [PATCH] [Status UI] Use the new output format of API `GET /api/status` (#107937) --- packages/kbn-optimizer/limits.yml | 2 +- .../__snapshots__/status_table.test.tsx.snap | 2 +- .../status/components/server_status.test.tsx | 4 +- .../status/components/status_table.test.tsx | 2 +- .../core_app/status/lib/load_status.test.ts | 77 ++++++++++++------- .../public/core_app/status/lib/load_status.ts | 63 ++++++++++++--- src/core/server/status/routes/status.ts | 51 +----------- src/core/types/status.ts | 54 +++++++++---- 8 files changed, 149 insertions(+), 106 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 10ce4f7ab1d1..4772e00d5645 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -8,7 +8,7 @@ pageLoadAssetSize: charts: 95000 cloud: 21076 console: 46091 - core: 434325 + core: 435325 crossClusterReplication: 65408 dashboard: 374194 dashboardEnhanced: 65646 diff --git a/src/core/public/core_app/status/components/__snapshots__/status_table.test.tsx.snap b/src/core/public/core_app/status/components/__snapshots__/status_table.test.tsx.snap index f5d3b837ce71..16b67bfa0584 100644 --- a/src/core/public/core_app/status/components/__snapshots__/status_table.test.tsx.snap +++ b/src/core/public/core_app/status/components/__snapshots__/status_table.test.tsx.snap @@ -27,7 +27,7 @@ exports[`StatusTable renders when statuses is provided 1`] = ` Object { "id": "plugin:1", "state": Object { - "id": "green", + "id": "available", "message": "Ready", "title": "green", "uiColor": "secondary", diff --git a/src/core/public/core_app/status/components/server_status.test.tsx b/src/core/public/core_app/status/components/server_status.test.tsx index f0a9a9a88bc4..3aa41827ff40 100644 --- a/src/core/public/core_app/status/components/server_status.test.tsx +++ b/src/core/public/core_app/status/components/server_status.test.tsx @@ -12,7 +12,7 @@ import { ServerStatus } from './server_status'; import { FormattedStatus } from '../lib'; const getStatus = (parts: Partial = {}): FormattedStatus['state'] => ({ - id: 'green', + id: 'available', title: 'Green', uiColor: 'secondary', message: '', @@ -29,7 +29,7 @@ describe('ServerStatus', () => { it('renders correctly for red state', () => { const status = getStatus({ - id: 'red', + id: 'unavailable', title: 'Red', }); const component = mount(); diff --git a/src/core/public/core_app/status/components/status_table.test.tsx b/src/core/public/core_app/status/components/status_table.test.tsx index 6f6324bf809e..af7d33bee5ed 100644 --- a/src/core/public/core_app/status/components/status_table.test.tsx +++ b/src/core/public/core_app/status/components/status_table.test.tsx @@ -11,7 +11,7 @@ import { shallow } from 'enzyme'; import { StatusTable } from './status_table'; const state = { - id: 'green', + id: 'available' as const, uiColor: 'secondary', message: 'Ready', title: 'green', diff --git a/src/core/public/core_app/status/lib/load_status.test.ts b/src/core/public/core_app/status/lib/load_status.test.ts index 349f20a2385c..e412192ea00e 100644 --- a/src/core/public/core_app/status/lib/load_status.test.ts +++ b/src/core/public/core_app/status/lib/load_status.test.ts @@ -17,36 +17,37 @@ const mockedResponse: StatusResponse = { version: { number: '8.0.0', build_hash: '9007199254740991', - build_number: '12', - build_snapshot: 'XXXXXXXX', + build_number: 12, + build_snapshot: false, }, status: { overall: { - id: 'overall', - state: 'yellow', - title: 'Yellow', - message: 'yellow', - uiColor: 'secondary', + level: 'degraded', + summary: 'yellow', }, - statuses: [ - { - id: 'plugin:1', - state: 'green', - title: 'Green', - message: 'Ready', - uiColor: 'secondary', + core: { + elasticsearch: { + level: 'available', + summary: 'Elasticsearch is available', }, - { - id: 'plugin:2', - state: 'yellow', - title: 'Yellow', - message: 'Something is weird', - uiColor: 'warning', + savedObjects: { + level: 'available', + summary: 'SavedObjects service has completed migrations and is available', }, - ], + }, + plugins: { + '1': { + level: 'available', + summary: 'Ready', + }, + '2': { + level: 'degraded', + summary: 'Something is weird', + }, + }, }, metrics: { - collected_at: new Date('2020-01-01 01:00:00'), + last_updated: '2020-01-01 01:00:00', collection_interval_in_millis: 1000, os: { platform: 'darwin' as const, @@ -80,6 +81,7 @@ const mockedResponse: StatusResponse = { disconnects: 1, total: 400, statusCodes: {}, + status_codes: {}, }, concurrent_connections: 1, }, @@ -148,13 +150,36 @@ describe('response processing', () => { test('includes the plugin statuses', async () => { const data = await loadStatus({ http, notifications }); expect(data.statuses).toEqual([ + { + id: 'core:elasticsearch', + state: { + id: 'available', + title: 'Green', + message: 'Elasticsearch is available', + uiColor: 'secondary', + }, + }, + { + id: 'core:savedObjects', + state: { + id: 'available', + title: 'Green', + message: 'SavedObjects service has completed migrations and is available', + uiColor: 'secondary', + }, + }, { id: 'plugin:1', - state: { id: 'green', title: 'Green', message: 'Ready', uiColor: 'secondary' }, + state: { id: 'available', title: 'Green', message: 'Ready', uiColor: 'secondary' }, }, { id: 'plugin:2', - state: { id: 'yellow', title: 'Yellow', message: 'Something is weird', uiColor: 'warning' }, + state: { + id: 'degraded', + title: 'Yellow', + message: 'Something is weird', + uiColor: 'warning', + }, }, ]); }); @@ -162,10 +187,10 @@ describe('response processing', () => { test('includes the serverState', async () => { const data = await loadStatus({ http, notifications }); expect(data.serverState).toEqual({ - id: 'yellow', + id: 'degraded', title: 'Yellow', message: 'yellow', - uiColor: 'secondary', + uiColor: 'warning', }); }); diff --git a/src/core/public/core_app/status/lib/load_status.ts b/src/core/public/core_app/status/lib/load_status.ts index 0748c3dfe1de..a5cc18ffd6c1 100644 --- a/src/core/public/core_app/status/lib/load_status.ts +++ b/src/core/public/core_app/status/lib/load_status.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { UnwrapPromise } from '@kbn/utility-types'; -import type { ServerStatus, StatusResponse } from '../../../../types/status'; +import type { StatusResponse, ServiceStatus, ServiceStatusLevel } from '../../../../types/status'; import type { HttpSetup } from '../../../http'; import type { NotificationsSetup } from '../../../notifications'; import type { DataType } from '../lib'; @@ -22,13 +22,18 @@ export interface Metric { export interface FormattedStatus { id: string; state: { - id: string; + id: ServiceStatusLevel; title: string; message: string; uiColor: string; }; } +interface StatusUIAttributes { + title: string; + uiColor: string; +} + /** * Returns an object of any keys that should be included for metrics. */ @@ -86,18 +91,47 @@ function formatMetrics({ metrics }: StatusResponse): Metric[] { /** * Reformat the backend data to make the frontend views simpler. */ -function formatStatus(status: ServerStatus): FormattedStatus { +function formatStatus(id: string, status: ServiceStatus): FormattedStatus { + const { title, uiColor } = STATUS_LEVEL_UI_ATTRS[status.level]; + return { - id: status.id, + id, state: { - id: status.state, - title: status.title, - message: status.message, - uiColor: status.uiColor, + id: status.level, + message: status.summary, + title, + uiColor, }, }; } +const STATUS_LEVEL_UI_ATTRS: Record = { + critical: { + title: i18n.translate('core.status.redTitle', { + defaultMessage: 'Red', + }), + uiColor: 'danger', + }, + unavailable: { + title: i18n.translate('core.status.redTitle', { + defaultMessage: 'Red', + }), + uiColor: 'danger', + }, + degraded: { + title: i18n.translate('core.status.yellowTitle', { + defaultMessage: 'Yellow', + }), + uiColor: 'warning', + }, + available: { + title: i18n.translate('core.status.greenTitle', { + defaultMessage: 'Green', + }), + uiColor: 'secondary', + }, +}; + /** * Get the status from the server API and format it for display. */ @@ -111,7 +145,7 @@ export async function loadStatus({ let response: StatusResponse; try { - response = await http.get('/api/status'); + response = await http.get('/api/status', { query: { v8format: true } }); } catch (e) { // API returns a 503 response if not all services are available. // In this case, we want to treat this as a successful API call, so that we can @@ -144,8 +178,15 @@ export async function loadStatus({ return { name: response.name, version: response.version, - statuses: response.status.statuses.map(formatStatus), - serverState: formatStatus(response.status.overall).state, + statuses: [ + ...Object.entries(response.status.core).map(([serviceName, status]) => + formatStatus(`core:${serviceName}`, status) + ), + ...Object.entries(response.status.plugins).map(([pluginName, status]) => + formatStatus(`plugin:${pluginName}`, status) + ), + ], + serverState: formatStatus('overall', response.status.overall).state, metrics: formatMetrics(response), }; } diff --git a/src/core/server/status/routes/status.ts b/src/core/server/status/routes/status.ts index 72f639231996..43a596bd1e0e 100644 --- a/src/core/server/status/routes/status.ts +++ b/src/core/server/status/routes/status.ts @@ -16,6 +16,7 @@ import { ServiceStatus, CoreStatus, ServiceStatusLevels } from '../types'; import { PluginName } from '../../plugins'; import { calculateLegacyStatus, LegacyStatusInfo } from '../legacy_status'; import { PackageInfo } from '../../config'; +import { StatusResponse } from '../../../types/status'; const SNAPSHOT_POSTFIX = /-SNAPSHOT$/; @@ -41,55 +42,9 @@ interface StatusInfo { plugins: Record; } -interface StatusHttpBody { - name: string; - uuid: string; - version: { - number: string; - build_hash: string; - build_number: number; - build_snapshot: boolean; - }; +// The moment we remove support for the LegacyStatusInfo, we can use the StatusResponse straight away. +interface StatusHttpBody extends Omit { status: StatusInfo | LegacyStatusInfo; - metrics: { - /** ISO-8601 date string w/o timezone */ - last_updated: string; - collection_interval_in_millis: number; - process: { - memory: { - heap: { - total_in_bytes: number; - used_in_bytes: number; - size_limit: number; - }; - resident_set_size_in_bytes: number; - }; - event_loop_delay: number; - pid: number; - uptime_in_millis: number; - }; - os: { - load: Record; - memory: { - total_in_bytes: number; - used_in_bytes: number; - free_in_bytes: number; - }; - uptime_in_millis: number; - platform: string; - platformRelease: string; - }; - response_times: { - max_in_millis: number; - }; - requests: { - total: number; - disconnects: number; - statusCodes: Record; - status_codes: Record; - }; - concurrent_connections: number; - }; } export const registerStatusRoute = ({ router, config, metrics, status }: Deps) => { diff --git a/src/core/types/status.ts b/src/core/types/status.ts index 5a59e46ef7d4..58c954fb7005 100644 --- a/src/core/types/status.ts +++ b/src/core/types/status.ts @@ -6,36 +6,58 @@ * Side Public License, v 1. */ -import type { OpsMetrics } from '../server/metrics'; +import type { + CoreStatus as CoreStatusFromServer, + ServiceStatus as ServiceStatusFromServer, + ServiceStatusLevel as ServiceStatusLevelFromServer, + OpsMetrics, +} from '../server'; -export interface ServerStatus { - id: string; - title: string; - state: string; - message: string; - uiColor: string; - icon?: string; - since?: string; +/** + * We need this type to convert the object `ServiceStatusLevel` to a union of the possible strings. + * This is because of the "stringification" that occurs when serving HTTP requests. + */ +export type ServiceStatusLevel = ReturnType; + +export interface ServiceStatus extends Omit { + level: ServiceStatusLevel; } -export type ServerMetrics = OpsMetrics & { +/** + * Copy all the services listed in CoreStatus with their specific ServiceStatus declarations + * but overwriting the `level` to its stringified version. + */ +export type CoreStatus = { + [ServiceName in keyof CoreStatusFromServer]: Omit & { + level: ServiceStatusLevel; + }; +}; + +export type ServerMetrics = Omit & { + last_updated: string; collection_interval_in_millis: number; + requests: { + status_codes: Record; + }; }; export interface ServerVersion { number: string; build_hash: string; - build_number: string; - build_snapshot: string; + build_number: number; + build_snapshot: boolean; +} + +export interface StatusInfo { + overall: ServiceStatus; + core: CoreStatus; + plugins: Record; } export interface StatusResponse { name: string; uuid: string; version: ServerVersion; - status: { - overall: ServerStatus; - statuses: ServerStatus[]; - }; + status: StatusInfo; metrics: ServerMetrics; }