Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PMM-13348 fix status presentation for initialization error #773

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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.externalExporter &&
(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')};
`,
});
126 changes: 124 additions & 2 deletions public/app/percona/inventory/components/StatusLink/StatusLink.test.tsx
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,8 +60,36 @@ describe('StatusLink', () => {
</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 some agent is not connected and is an external exporter', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
status: ServiceAgentStatus.STARTING,
},
{
agentId: 'agent3',
agentType: AgentType.externalExporter,
},
];
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 "Failed" if some agent is not starting or running', () => {
const agents: DbAgent[] = [
{
Expand All @@ -72,7 +102,7 @@ describe('StatusLink', () => {
},
{
agentId: 'agent3',
isConnected: true,
agentType: AgentType.mongodbExporter,
},
];
const agentsStatus = getAgentsMonitoringStatus(agents);
Expand All @@ -82,6 +112,98 @@ describe('StatusLink', () => {
</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',
agentType: AgentType.externalExporter,
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 "Failed" if there are invalid agents', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
agentType: AgentType.mysqldExporter,
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')).toBeInTheDocument();
expect(screen.queryByText('N/A')).not.toBeInTheDocument();
});

it('should show "N/A" if there are invalid external agents', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
agentType: AgentType.externalExporter,
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();
});

it('should show "N/A" if there are external agents with no status', () => {
const agents: DbAgent[] = [
{
agentId: 'agent1',
status: ServiceAgentStatus.RUNNING,
},
{
agentId: 'agent2',
agentType: AgentType.externalExporter,
},
];
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
Loading