diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index e8e1e4ac1ee53..c9fec5ce5cfe8 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -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 { text: React.ReactNode; diff --git a/public/app/percona/inventory/Inventory.types.ts b/public/app/percona/inventory/Inventory.types.ts index a3acfb9408eb8..08ef2a611b2aa 100644 --- a/public/app/percona/inventory/Inventory.types.ts +++ b/public/app/percona/inventory/Inventory.types.ts @@ -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', @@ -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 { diff --git a/public/app/percona/inventory/Tabs/Agents.tsx b/public/app/percona/inventory/Tabs/Agents.tsx index bb9ca8dda81cb..d51b4502a9b6c 100644 --- a/public/app/percona/inventory/Tabs/Agents.tsx +++ b/public/app/percona/inventory/Tabs/Agents.tsx @@ -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'; @@ -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'; @@ -64,7 +63,7 @@ export const Agents: FC ( - + ), type: FilterFieldTypes.DROPDOWN, options: [ @@ -92,6 +91,10 @@ export const Agents: FC 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('_', ' ')); +}; diff --git a/public/app/percona/inventory/Tabs/Nodes.tsx b/public/app/percona/inventory/Tabs/Nodes.tsx index 7d738f55d8cc1..d09efd2edc156 100644 --- a/public/app/percona/inventory/Tabs/Nodes.tsx +++ b/public/app/percona/inventory/Tabs/Nodes.tsx @@ -143,6 +143,10 @@ export const NodesTab = () => { label: MonitoringStatus.FAILED, value: MonitoringStatus.FAILED, }, + { + label: MonitoringStatus.NA, + value: MonitoringStatus.NA, + }, ], }, { diff --git a/public/app/percona/inventory/Tabs/Services.utils.ts b/public/app/percona/inventory/Tabs/Services.utils.ts index f0dac00394ec4..e45d3b91f1d94 100644 --- a/public/app/percona/inventory/Tabs/Services.utils.ts +++ b/public/app/percona/inventory/Tabs/Services.utils.ts @@ -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'; @@ -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); diff --git a/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx b/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx index 55d14ab928267..2ea443c2e175e 100644 --- a/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx +++ b/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx @@ -160,6 +160,10 @@ const ServicesTable: FC = ({ label: MonitoringStatus.FAILED, value: MonitoringStatus.FAILED, }, + { + label: MonitoringStatus.NA, + value: MonitoringStatus.NA, + }, ], }, { diff --git a/public/app/percona/inventory/components/StatusLink/StatusLink.styles.ts b/public/app/percona/inventory/components/StatusLink/StatusLink.styles.ts index ff4040da7eb5f..fb2779a332c5a 100644 --- a/public/app/percona/inventory/components/StatusLink/StatusLink.styles.ts +++ b/public/app/percona/inventory/components/StatusLink/StatusLink.styles.ts @@ -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')}; `, }); diff --git a/public/app/percona/inventory/components/StatusLink/StatusLink.test.tsx b/public/app/percona/inventory/components/StatusLink/StatusLink.test.tsx index b04cff161770e..4d59433cd7c7f 100644 --- a/public/app/percona/inventory/components/StatusLink/StatusLink.test.tsx +++ b/public/app/percona/inventory/components/StatusLink/StatusLink.test.tsx @@ -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'; @@ -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[] = [ { @@ -48,6 +49,7 @@ describe('StatusLink', () => { }, { agentId: 'agent3', + agentType: AgentType.pmmAgent, isConnected: false, }, ]; @@ -58,8 +60,36 @@ describe('StatusLink', () => { ); 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( + + + + ); + 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[] = [ { @@ -72,7 +102,7 @@ describe('StatusLink', () => { }, { agentId: 'agent3', - isConnected: true, + agentType: AgentType.mongodbExporter, }, ]; const agentsStatus = getAgentsMonitoringStatus(agents); @@ -82,6 +112,98 @@ describe('StatusLink', () => { ); 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( + + + + ); + 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( + + + + ); + 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( + + + + ); + 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( + + + + ); + expect(screen.queryByText('OK')).not.toBeInTheDocument(); + expect(screen.queryByText('Failed')).not.toBeInTheDocument(); + expect(screen.getByText('N/A')).toBeInTheDocument(); + }); }); diff --git a/public/app/percona/inventory/components/StatusLink/StatusLink.tsx b/public/app/percona/inventory/components/StatusLink/StatusLink.tsx index d2e37e960124e..a58297789a7f5 100644 --- a/public/app/percona/inventory/components/StatusLink/StatusLink.tsx +++ b/public/app/percona/inventory/components/StatusLink/StatusLink.tsx @@ -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 = ({ 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 (