Skip to content
2 changes: 1 addition & 1 deletion packages/grafana-ui/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Icon } from '../Icon/Icon';
import { HorizontalGroup } from '../Layout/Layout';
import { Tooltip } from '../Tooltip';

export type BadgeColor = 'blue' | 'red' | 'green' | 'orange' | 'purple';
export type BadgeColor = 'blue' | 'red' | 'green' | 'orange' | 'purple' | 'gray';

export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
text: React.ReactNode;
Expand Down
5 changes: 4 additions & 1 deletion public/app/percona/inventory/Inventory.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export enum AgentType {
mysql = 'mysql',
mysqldExporter = 'mysqldExporter',
nodeExporter = 'nodeExporter',
pmmAgent = 'pmm_agent',
pmmAgent = 'pmm-agent',
postgresExporter = 'postgresExporter',
postgresql = 'postgresql',
proxysql = 'proxysql',
Expand All @@ -54,14 +54,17 @@ export enum ServiceAgentStatus {
STARTING = 'STARTING',
RUNNING = 'RUNNING',
WAITING = 'WAITING',
INITIALIZATION_ERROR = 'INITIALIZATION_ERROR',
STOPPING = 'STOPPING',
DONE = 'DONE',
UNKNOWN = 'UNKNOWN',
INVALID = '',
}

export enum MonitoringStatus {
OK = 'OK',
FAILED = 'Failed',
NA = 'N/A',
}

export interface ServiceAgentPayload {
Expand Down
9 changes: 6 additions & 3 deletions public/app/percona/inventory/Tabs/Agents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { fetchNodesAction } from 'app/percona/shared/core/reducers/nodes/nodes';
import { fetchServicesAction } from 'app/percona/shared/core/reducers/services';
import { getNodes, getServices } from 'app/percona/shared/core/selectors';
import { isApiCancelError } from 'app/percona/shared/helpers/api';
import { capitalizeText } from 'app/percona/shared/helpers/capitalizeText';
import { getExpandAndActionsCol } from 'app/percona/shared/helpers/getExpandAndActionsCol';
import { logger } from 'app/percona/shared/helpers/logger';
import { filterFulfilled, processPromiseResults } from 'app/percona/shared/helpers/promises';
Expand All @@ -33,7 +32,7 @@ import { GET_AGENTS_CANCEL_TOKEN, GET_NODES_CANCEL_TOKEN, GET_SERVICES_CANCEL_TO
import { Messages } from '../Inventory.messages';
import { InventoryService } from '../Inventory.service';

import { beautifyAgentType, getAgentStatusColor, toAgentModel } from './Agents.utils';
import { beautifyAgentType, getAgentStatusColor, getBadgeTextForAgentStatus, toAgentModel } from './Agents.utils';
import { formatNodeId } from './Nodes.utils';
import { getStyles } from './Tabs.styles';

Expand Down Expand Up @@ -64,7 +63,7 @@ export const Agents: FC<GrafanaRouteComponentProps<{ serviceId: string; nodeId:
Header: Messages.agents.columns.status,
accessor: 'status',
Cell: ({ value }: { value: ServiceAgentStatus }) => (
<Badge text={capitalizeText(value)} color={getAgentStatusColor(value)} />
<Badge text={getBadgeTextForAgentStatus(value)} color={getAgentStatusColor(value)} />
),
type: FilterFieldTypes.DROPDOWN,
options: [
Expand Down Expand Up @@ -92,6 +91,10 @@ export const Agents: FC<GrafanaRouteComponentProps<{ serviceId: string; nodeId:
label: 'Waiting',
value: ServiceAgentStatus.WAITING,
},
{
label: 'Initialization error',
value: ServiceAgentStatus.INITIALIZATION_ERROR,
},
],
},
{
Expand Down
11 changes: 10 additions & 1 deletion public/app/percona/inventory/Tabs/Agents.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BadgeColor } from '@grafana/ui';
import { capitalizeText } from 'app/percona/shared/helpers/capitalizeText';
import { payloadToCamelCase } from 'app/percona/shared/helpers/payloadToCamelCase';

import { Agent, AgentType, ServiceAgentPayload, ServiceAgentStatus } from '../Inventory.types';
Expand Down Expand Up @@ -53,4 +54,12 @@ export const beautifyAgentType = (type: AgentType): string =>
type.replace(/^\w/, (c) => c.toUpperCase()).replace(/[_-]/g, ' ');

export const getAgentStatusColor = (status: ServiceAgentStatus): BadgeColor =>
status === ServiceAgentStatus.STARTING || status === ServiceAgentStatus.RUNNING ? 'green' : 'red';
status === ServiceAgentStatus.STARTING || status === ServiceAgentStatus.RUNNING
? 'green'
: status === ServiceAgentStatus.UNKNOWN
? 'gray'
: 'red';

export const getBadgeTextForAgentStatus = (status: ServiceAgentStatus): string => {
return capitalizeText(status.replace('_', ' '));
};
4 changes: 4 additions & 0 deletions public/app/percona/inventory/Tabs/Nodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export const NodesTab = () => {
label: MonitoringStatus.FAILED,
value: MonitoringStatus.FAILED,
},
{
label: MonitoringStatus.NA,
value: MonitoringStatus.NA,
},
],
},
{
Expand Down
18 changes: 12 additions & 6 deletions public/app/percona/inventory/Tabs/Services.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BadgeColor, IconName } from '@grafana/ui';
import { capitalizeText } from 'app/percona/shared/helpers/capitalizeText';
import { DbAgent, ServiceStatus } from 'app/percona/shared/services/services/Services.types';

import { FlattenService, MonitoringStatus, ServiceAgentStatus } from '../Inventory.types';
import { AgentType, FlattenService, MonitoringStatus, ServiceAgentStatus } from '../Inventory.types';

import { stripNodeId } from './Nodes.utils';

Expand Down Expand Up @@ -43,13 +43,19 @@ export const getBadgeTextForServiceStatus = (status: ServiceStatus): string => {
};

export const getAgentsMonitoringStatus = (agents: DbAgent[]) => {
const allAgentsOk = agents?.every(
(agent) =>
agent.status === ServiceAgentStatus.RUNNING || agent.status === ServiceAgentStatus.STARTING || !!agent.isConnected
);
return allAgentsOk ? MonitoringStatus.OK : MonitoringStatus.FAILED;
const allAgentsOk = agents?.every(isAgentOk);
const hasUnknownAgents = agents?.some(isAgentUnknown);

return allAgentsOk ? MonitoringStatus.OK : hasUnknownAgents ? MonitoringStatus.NA : MonitoringStatus.FAILED;
};

const isAgentOk = (agent: DbAgent) =>
agent.status === ServiceAgentStatus.RUNNING || agent.status === ServiceAgentStatus.STARTING || !!agent.isConnected;

const isAgentUnknown = ({ status, agentType }: DbAgent) =>
agentType !== AgentType.pmmAgent &&
(status === ServiceAgentStatus.INVALID || status === ServiceAgentStatus.UNKNOWN || !status);

export const stripServiceId = (serviceId: string) => {
const regex = /\/service_id\/(.*)/gm;
const match = regex.exec(serviceId);
Expand Down
4 changes: 4 additions & 0 deletions public/app/percona/inventory/Tabs/Services/ServicesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ const ServicesTable: FC<ServicesTableProps> = ({
label: MonitoringStatus.FAILED,
value: MonitoringStatus.FAILED,
},
{
label: MonitoringStatus.NA,
value: MonitoringStatus.NA,
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { css } from '@emotion/css';

import { GrafanaTheme2 } from '@grafana/data';
import { MonitoringStatus } from 'app/percona/inventory/Inventory.types';

export const getStyles = ({ visualization }: GrafanaTheme2, allAgentsOk: boolean) => ({
export const getStyles = ({ visualization }: GrafanaTheme2, status?: string) => ({
link: css`
text-decoration: underline;
color: ${allAgentsOk ? visualization.getColorByName('green') : visualization.getColorByName('red')};
color: ${status === MonitoringStatus.OK
? visualization.getColorByName('green')
: status === MonitoringStatus.FAILED
? visualization.getColorByName('red')
: visualization.getColorByName('gray')};
`,
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Router } from 'react-router-dom';
import { locationService } from '@grafana/runtime';
import { DbAgent } from 'app/percona/shared/services/services/Services.types';

import { ServiceAgentStatus } from '../../Inventory.types';
import { AgentType, ServiceAgentStatus } from '../../Inventory.types';
import { getAgentsMonitoringStatus } from '../../Tabs/Services.utils';

import { StatusLink } from './StatusLink';
Expand Down Expand Up @@ -36,6 +36,7 @@ describe('StatusLink', () => {
expect(screen.getByText('OK')).toBeInTheDocument();
expect(screen.queryByText('Failed')).not.toBeInTheDocument();
});

it('should show "Failed" if some agent is not connected', () => {
const agents: DbAgent[] = [
{
Expand All @@ -48,6 +49,7 @@ describe('StatusLink', () => {
},
{
agentId: 'agent3',
agentType: AgentType.pmmAgent,
isConnected: false,
},
];
Expand All @@ -58,21 +60,24 @@ describe('StatusLink', () => {
</Router>
);
expect(screen.queryByText('OK')).not.toBeInTheDocument();
expect(screen.queryByText('N/A')).not.toBeInTheDocument();
expect(screen.getByText('Failed')).toBeInTheDocument();
});
it('should show "Failed" if some agent is not starting or running', () => {

it('should show "N/A" if some agent is not connected and is an external exporter', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
status: ServiceAgentStatus.STOPPING,
status: ServiceAgentStatus.STARTING,
},
{
agentId: 'agent3',
isConnected: true,
agentType: AgentType.externalExporter,
isConnected: false,
},
];
const agentsStatus = getAgentsMonitoringStatus(agents);
Expand All @@ -82,6 +87,73 @@ describe('StatusLink', () => {
</Router>
);
expect(screen.queryByText('OK')).not.toBeInTheDocument();
expect(screen.queryByText('Failed')).not.toBeInTheDocument();
expect(screen.getByText('N/A')).toBeInTheDocument();
});

it('should show "Failed" if some agent is not starting or running', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
status: ServiceAgentStatus.STOPPING,
},
];
const agentsStatus = getAgentsMonitoringStatus(agents);
render(
<Router history={locationService.getHistory()}>
<StatusLink agentsStatus={agentsStatus} type="services" strippedId="service_id_1" />
</Router>
);
expect(screen.queryByText('OK')).not.toBeInTheDocument();
expect(screen.queryByText('N/A')).not.toBeInTheDocument();
expect(screen.getByText('Failed')).toBeInTheDocument();
});

it('should show "N/A" if there are unknown agents', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
status: ServiceAgentStatus.UNKNOWN,
},
];
const agentsStatus = getAgentsMonitoringStatus(agents);
render(
<Router history={locationService.getHistory()}>
<StatusLink agentsStatus={agentsStatus} type="services" strippedId="service_id_1" />
</Router>
);
expect(screen.queryByText('OK')).not.toBeInTheDocument();
expect(screen.queryByText('Failed')).not.toBeInTheDocument();
expect(screen.getByText('N/A')).toBeInTheDocument();
});

it('should show "N/A" if there are invalid agents', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
status: ServiceAgentStatus.INVALID,
},
];
const agentsStatus = getAgentsMonitoringStatus(agents);
render(
<Router history={locationService.getHistory()}>
<StatusLink agentsStatus={agentsStatus} type="services" strippedId="service_id_1" />
</Router>
);
expect(screen.queryByText('OK')).not.toBeInTheDocument();
expect(screen.queryByText('Failed')).not.toBeInTheDocument();
expect(screen.getByText('N/A')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import React, { FC } from 'react';

import { Link, useStyles2 } from '@grafana/ui';

import { MonitoringStatus } from '../../Inventory.types';

import { getStyles } from './StatusLink.styles';
import { StatusLinkProps } from './StatusLink.types';

export const StatusLink: FC<StatusLinkProps> = ({ agentsStatus, type, strippedId }) => {
const link = `/inventory/${type}/${strippedId}/agents`;
const styles = useStyles2((theme) => getStyles(theme, agentsStatus === MonitoringStatus.OK));
const styles = useStyles2((theme) => getStyles(theme, agentsStatus));

return (
<Link href={link} className={styles.link}>
Expand Down