[Status UI] Use the new output format of API GET /api/status (#107937)

This commit is contained in:
Alejandro Fernández Haro 2021-08-11 16:56:23 +01:00 committed by GitHub
parent 6d2c1da2ba
commit def97bd734
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 149 additions and 106 deletions

View file

@ -8,7 +8,7 @@ pageLoadAssetSize:
charts: 95000 charts: 95000
cloud: 21076 cloud: 21076
console: 46091 console: 46091
core: 434325 core: 435325
crossClusterReplication: 65408 crossClusterReplication: 65408
dashboard: 374194 dashboard: 374194
dashboardEnhanced: 65646 dashboardEnhanced: 65646

View file

@ -27,7 +27,7 @@ exports[`StatusTable renders when statuses is provided 1`] = `
Object { Object {
"id": "plugin:1", "id": "plugin:1",
"state": Object { "state": Object {
"id": "green", "id": "available",
"message": "Ready", "message": "Ready",
"title": "green", "title": "green",
"uiColor": "secondary", "uiColor": "secondary",

View file

@ -12,7 +12,7 @@ import { ServerStatus } from './server_status';
import { FormattedStatus } from '../lib'; import { FormattedStatus } from '../lib';
const getStatus = (parts: Partial<FormattedStatus['state']> = {}): FormattedStatus['state'] => ({ const getStatus = (parts: Partial<FormattedStatus['state']> = {}): FormattedStatus['state'] => ({
id: 'green', id: 'available',
title: 'Green', title: 'Green',
uiColor: 'secondary', uiColor: 'secondary',
message: '', message: '',
@ -29,7 +29,7 @@ describe('ServerStatus', () => {
it('renders correctly for red state', () => { it('renders correctly for red state', () => {
const status = getStatus({ const status = getStatus({
id: 'red', id: 'unavailable',
title: 'Red', title: 'Red',
}); });
const component = mount(<ServerStatus serverState={status} name="My Computer" />); const component = mount(<ServerStatus serverState={status} name="My Computer" />);

View file

@ -11,7 +11,7 @@ import { shallow } from 'enzyme';
import { StatusTable } from './status_table'; import { StatusTable } from './status_table';
const state = { const state = {
id: 'green', id: 'available' as const,
uiColor: 'secondary', uiColor: 'secondary',
message: 'Ready', message: 'Ready',
title: 'green', title: 'green',

View file

@ -17,36 +17,37 @@ const mockedResponse: StatusResponse = {
version: { version: {
number: '8.0.0', number: '8.0.0',
build_hash: '9007199254740991', build_hash: '9007199254740991',
build_number: '12', build_number: 12,
build_snapshot: 'XXXXXXXX', build_snapshot: false,
}, },
status: { status: {
overall: { overall: {
id: 'overall', level: 'degraded',
state: 'yellow', summary: 'yellow',
title: 'Yellow',
message: 'yellow',
uiColor: 'secondary',
}, },
statuses: [ core: {
{ elasticsearch: {
id: 'plugin:1', level: 'available',
state: 'green', summary: 'Elasticsearch is available',
title: 'Green',
message: 'Ready',
uiColor: 'secondary',
}, },
{ savedObjects: {
id: 'plugin:2', level: 'available',
state: 'yellow', summary: 'SavedObjects service has completed migrations and is available',
title: 'Yellow',
message: 'Something is weird',
uiColor: 'warning',
}, },
], },
plugins: {
'1': {
level: 'available',
summary: 'Ready',
},
'2': {
level: 'degraded',
summary: 'Something is weird',
},
},
}, },
metrics: { metrics: {
collected_at: new Date('2020-01-01 01:00:00'), last_updated: '2020-01-01 01:00:00',
collection_interval_in_millis: 1000, collection_interval_in_millis: 1000,
os: { os: {
platform: 'darwin' as const, platform: 'darwin' as const,
@ -80,6 +81,7 @@ const mockedResponse: StatusResponse = {
disconnects: 1, disconnects: 1,
total: 400, total: 400,
statusCodes: {}, statusCodes: {},
status_codes: {},
}, },
concurrent_connections: 1, concurrent_connections: 1,
}, },
@ -148,13 +150,36 @@ describe('response processing', () => {
test('includes the plugin statuses', async () => { test('includes the plugin statuses', async () => {
const data = await loadStatus({ http, notifications }); const data = await loadStatus({ http, notifications });
expect(data.statuses).toEqual([ 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', id: 'plugin:1',
state: { id: 'green', title: 'Green', message: 'Ready', uiColor: 'secondary' }, state: { id: 'available', title: 'Green', message: 'Ready', uiColor: 'secondary' },
}, },
{ {
id: 'plugin:2', 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 () => { test('includes the serverState', async () => {
const data = await loadStatus({ http, notifications }); const data = await loadStatus({ http, notifications });
expect(data.serverState).toEqual({ expect(data.serverState).toEqual({
id: 'yellow', id: 'degraded',
title: 'Yellow', title: 'Yellow',
message: 'yellow', message: 'yellow',
uiColor: 'secondary', uiColor: 'warning',
}); });
}); });

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { UnwrapPromise } from '@kbn/utility-types'; 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 { HttpSetup } from '../../../http';
import type { NotificationsSetup } from '../../../notifications'; import type { NotificationsSetup } from '../../../notifications';
import type { DataType } from '../lib'; import type { DataType } from '../lib';
@ -22,13 +22,18 @@ export interface Metric {
export interface FormattedStatus { export interface FormattedStatus {
id: string; id: string;
state: { state: {
id: string; id: ServiceStatusLevel;
title: string; title: string;
message: string; message: string;
uiColor: string; uiColor: string;
}; };
} }
interface StatusUIAttributes {
title: string;
uiColor: string;
}
/** /**
* Returns an object of any keys that should be included for metrics. * 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. * 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 { return {
id: status.id, id,
state: { state: {
id: status.state, id: status.level,
title: status.title, message: status.summary,
message: status.message, title,
uiColor: status.uiColor, uiColor,
}, },
}; };
} }
const STATUS_LEVEL_UI_ATTRS: Record<ServiceStatusLevel, StatusUIAttributes> = {
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. * Get the status from the server API and format it for display.
*/ */
@ -111,7 +145,7 @@ export async function loadStatus({
let response: StatusResponse; let response: StatusResponse;
try { try {
response = await http.get('/api/status'); response = await http.get('/api/status', { query: { v8format: true } });
} catch (e) { } catch (e) {
// API returns a 503 response if not all services are available. // 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 // 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 { return {
name: response.name, name: response.name,
version: response.version, version: response.version,
statuses: response.status.statuses.map(formatStatus), statuses: [
serverState: formatStatus(response.status.overall).state, ...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), metrics: formatMetrics(response),
}; };
} }

View file

@ -16,6 +16,7 @@ import { ServiceStatus, CoreStatus, ServiceStatusLevels } from '../types';
import { PluginName } from '../../plugins'; import { PluginName } from '../../plugins';
import { calculateLegacyStatus, LegacyStatusInfo } from '../legacy_status'; import { calculateLegacyStatus, LegacyStatusInfo } from '../legacy_status';
import { PackageInfo } from '../../config'; import { PackageInfo } from '../../config';
import { StatusResponse } from '../../../types/status';
const SNAPSHOT_POSTFIX = /-SNAPSHOT$/; const SNAPSHOT_POSTFIX = /-SNAPSHOT$/;
@ -41,55 +42,9 @@ interface StatusInfo {
plugins: Record<string, ServiceStatus>; plugins: Record<string, ServiceStatus>;
} }
interface StatusHttpBody { // The moment we remove support for the LegacyStatusInfo, we can use the StatusResponse straight away.
name: string; interface StatusHttpBody extends Omit<StatusResponse, 'status'> {
uuid: string;
version: {
number: string;
build_hash: string;
build_number: number;
build_snapshot: boolean;
};
status: StatusInfo | LegacyStatusInfo; 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<string, number>;
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<number, number>;
status_codes: Record<number, number>;
};
concurrent_connections: number;
};
} }
export const registerStatusRoute = ({ router, config, metrics, status }: Deps) => { export const registerStatusRoute = ({ router, config, metrics, status }: Deps) => {

View file

@ -6,36 +6,58 @@
* Side Public License, v 1. * 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; * We need this type to convert the object `ServiceStatusLevel` to a union of the possible strings.
title: string; * This is because of the "stringification" that occurs when serving HTTP requests.
state: string; */
message: string; export type ServiceStatusLevel = ReturnType<ServiceStatusLevelFromServer['toString']>;
uiColor: string;
icon?: string; export interface ServiceStatus extends Omit<ServiceStatusFromServer, 'level'> {
since?: string; 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<CoreStatusFromServer[ServiceName], 'level'> & {
level: ServiceStatusLevel;
};
};
export type ServerMetrics = Omit<OpsMetrics, 'collected_at'> & {
last_updated: string;
collection_interval_in_millis: number; collection_interval_in_millis: number;
requests: {
status_codes: Record<number, number>;
};
}; };
export interface ServerVersion { export interface ServerVersion {
number: string; number: string;
build_hash: string; build_hash: string;
build_number: string; build_number: number;
build_snapshot: string; build_snapshot: boolean;
}
export interface StatusInfo {
overall: ServiceStatus;
core: CoreStatus;
plugins: Record<string, ServiceStatus>;
} }
export interface StatusResponse { export interface StatusResponse {
name: string; name: string;
uuid: string; uuid: string;
version: ServerVersion; version: ServerVersion;
status: { status: StatusInfo;
overall: ServerStatus;
statuses: ServerStatus[];
};
metrics: ServerMetrics; metrics: ServerMetrics;
} }