[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
cloud: 21076
console: 46091
core: 434325
core: 435325
crossClusterReplication: 65408
dashboard: 374194
dashboardEnhanced: 65646

View file

@ -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",

View file

@ -12,7 +12,7 @@ import { ServerStatus } from './server_status';
import { FormattedStatus } from '../lib';
const getStatus = (parts: Partial<FormattedStatus['state']> = {}): 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(<ServerStatus serverState={status} name="My Computer" />);

View file

@ -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',

View file

@ -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',
});
});

View file

@ -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<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.
*/
@ -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),
};
}

View file

@ -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<string, ServiceStatus>;
}
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<StatusResponse, 'status'> {
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) => {

View file

@ -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<ServiceStatusLevelFromServer['toString']>;
export interface ServiceStatus extends Omit<ServiceStatusFromServer, 'level'> {
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;
requests: {
status_codes: Record<number, number>;
};
};
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<string, ServiceStatus>;
}
export interface StatusResponse {
name: string;
uuid: string;
version: ServerVersion;
status: {
overall: ServerStatus;
statuses: ServerStatus[];
};
status: StatusInfo;
metrics: ServerMetrics;
}