From 5614654fd00e07e4af49feed6056e137f55fe548 Mon Sep 17 00:00:00 2001 From: Matej Kubinec <32638572+matejkubinec@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:27:47 +0200 Subject: [PATCH] PMM-11963 Add links between nodes and services (#678) * PMM-11963 Add links between nodes and services * PMM-11963 Trigger filter form change only on value change --- .../percona/inventory/Inventory.messages.ts | 3 ++ public/app/percona/inventory/Tabs/Nodes.tsx | 32 +++++++++++++++---- .../app/percona/inventory/Tabs/Nodes.utils.ts | 7 ++++ .../app/percona/inventory/Tabs/Services.tsx | 17 ++++++++-- .../percona/inventory/Tabs/Services.utils.ts | 20 +++++++++++- .../app/percona/inventory/Tabs/Tabs.styles.ts | 5 ++- .../Elements/Table/Filter/Filter.tsx | 9 +++++- .../components/Elements/Table/Table.tsx | 1 + .../components/Elements/Table/Table.types.ts | 1 + 9 files changed, 84 insertions(+), 11 deletions(-) diff --git a/public/app/percona/inventory/Inventory.messages.ts b/public/app/percona/inventory/Inventory.messages.ts index 945f7ff342034..db383537f97f6 100644 --- a/public/app/percona/inventory/Inventory.messages.ts +++ b/public/app/percona/inventory/Inventory.messages.ts @@ -2,7 +2,9 @@ export const Messages = { services: { add: 'Add Service', columns: { + nodeId: 'Node ID', status: 'Status', + serviceId: 'Service ID', serviceName: 'Service Name', nodeName: 'Node Name', monitoring: 'Monitoring', @@ -51,6 +53,7 @@ export const Messages = { forceConfirmation: 'Force mode is going to delete all agents and services associated with the nodes', emptyTable: 'No nodes available', noServices: 'No services', + servicesCount: (count: number) => `${count} services`, columns: { nodeName: 'Node Name', nodeId: 'Node ID', diff --git a/public/app/percona/inventory/Tabs/Nodes.tsx b/public/app/percona/inventory/Tabs/Nodes.tsx index 29ca68d2b00ed..0edb673654620 100644 --- a/public/app/percona/inventory/Tabs/Nodes.tsx +++ b/public/app/percona/inventory/Tabs/Nodes.tsx @@ -4,7 +4,7 @@ import { Form } from 'react-final-form'; import { Row } from 'react-table'; import { AppEvents } from '@grafana/data'; -import { Badge, Button, HorizontalGroup, Icon, Modal, TagList, useStyles2 } from '@grafana/ui'; +import { Badge, Button, HorizontalGroup, Icon, Link, Modal, TagList, useStyles2 } from '@grafana/ui'; import { OldPage } from 'app/core/components/Page/Page'; import { Action } from 'app/percona/dbaas/components/MultipleActions'; import { CheckboxField } from 'app/percona/shared/components/Elements/Checkbox'; @@ -33,7 +33,7 @@ import { FlattenNode, MonitoringStatus, Node } from '../Inventory.types'; import { StatusBadge } from '../components/StatusBadge/StatusBadge'; import { StatusLink } from '../components/StatusLink/StatusLink'; -import { stripNodeId } from './Nodes.utils'; +import { getServiceLink, stripNodeId } from './Nodes.utils'; import { getBadgeColorForServiceStatus, getBadgeIconForServiceStatus } from './Services.utils'; import { getStyles } from './Tabs.styles'; @@ -67,6 +67,13 @@ export const NodesTab = () => { const columns = useMemo( (): Array> => [ + { + Header: Messages.services.columns.nodeId, + id: 'nodeId', + accessor: 'nodeId', + hidden: true, + type: FilterFieldTypes.TEXT, + }, { Header: Messages.services.columns.status, accessor: 'status', @@ -145,12 +152,21 @@ export const NodesTab = () => { if (!value || value.length < 1) { return
{Messages.nodes.noServices}
; } - return
{value.length > 1 ? `${value.length} services` : value[0].serviceName}
; + + if (value.length === 1) { + return ( + + {value[0].serviceName} + + ); + } + + return
{Messages.nodes.servicesCount(value.length)}
; }, }, getExpandAndActionsCol(getActions), ], - [getActions] + [styles, getActions] ); const loadData = useCallback(async () => { @@ -189,7 +205,11 @@ export const NodesTab = () => { {row.original.services && row.original.services.length && ( {row.original.services.map((service) => ( -
{service.serviceName}
+
+ + {service.serviceName} + +
))}
)} @@ -214,7 +234,7 @@ export const NodesTab = () => { ); }, - [styles.tagList] + [styles.tagList, styles.link] ); const deletionMsg = useMemo(() => { diff --git a/public/app/percona/inventory/Tabs/Nodes.utils.ts b/public/app/percona/inventory/Tabs/Nodes.utils.ts index 8e5529f4618e7..b7fc329abefb0 100644 --- a/public/app/percona/inventory/Tabs/Nodes.utils.ts +++ b/public/app/percona/inventory/Tabs/Nodes.utils.ts @@ -1,3 +1,5 @@ +import { stripServiceId } from './Services.utils'; + export const stripNodeId = (nodeId: string) => { const regex = /\/node_id\/(.*)/gm; const match = regex.exec(nodeId); @@ -10,3 +12,8 @@ export const stripNodeId = (nodeId: string) => { }; export const formatNodeId = (nodeId: string) => `/node_id/${nodeId}`; + +export const getServiceLink = (serviceId: string) => { + const strippedId = stripServiceId(serviceId); + return `/inventory/services?search-text-input=${strippedId}&search-select=serviceId`; +}; diff --git a/public/app/percona/inventory/Tabs/Services.tsx b/public/app/percona/inventory/Tabs/Services.tsx index 788de34c0c85a..5cb66638236a3 100644 --- a/public/app/percona/inventory/Tabs/Services.tsx +++ b/public/app/percona/inventory/Tabs/Services.tsx @@ -5,7 +5,7 @@ import { Row } from 'react-table'; import { AppEvents } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { Badge, Button, HorizontalGroup, Icon, Modal, TagList, useStyles2 } from '@grafana/ui'; +import { Badge, Button, HorizontalGroup, Icon, Link, Modal, TagList, useStyles2 } from '@grafana/ui'; import { OldPage } from 'app/core/components/Page/Page'; import { stripServiceId } from 'app/percona/check/components/FailedChecksTab/FailedChecksTab.utils'; import { Action } from 'app/percona/dbaas/components/MultipleActions'; @@ -44,6 +44,7 @@ import { getBadgeIconForServiceStatus, getBadgeTextForServiceStatus, getAgentsMonitoringStatus, + getNodeLink, } from './Services.utils'; import { getStyles } from './Tabs.styles'; @@ -100,6 +101,13 @@ export const Services = () => { const columns = useMemo( (): Array> => [ + { + Header: Messages.services.columns.serviceId, + id: 'serviceId', + accessor: 'serviceId', + hidden: true, + type: FilterFieldTypes.TEXT, + }, { Header: Messages.services.columns.status, accessor: 'status', @@ -142,6 +150,11 @@ export const Services = () => { { Header: Messages.services.columns.nodeName, accessor: 'nodeName', + Cell: ({ value, row }: { row: Row; value: string }) => ( + + {value} + + ), type: FilterFieldTypes.TEXT, }, { @@ -176,7 +189,7 @@ export const Services = () => { }, getExpandAndActionsCol(getActions), ], - [getActions] + [styles, getActions] ); const deletionMsg = useMemo(() => { diff --git a/public/app/percona/inventory/Tabs/Services.utils.ts b/public/app/percona/inventory/Tabs/Services.utils.ts index 444940ff42a4e..f0dac00394ec4 100644 --- a/public/app/percona/inventory/Tabs/Services.utils.ts +++ b/public/app/percona/inventory/Tabs/Services.utils.ts @@ -2,7 +2,9 @@ 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 { MonitoringStatus, ServiceAgentStatus } from '../Inventory.types'; +import { FlattenService, MonitoringStatus, ServiceAgentStatus } from '../Inventory.types'; + +import { stripNodeId } from './Nodes.utils'; const SERVICE_STATUS_TO_BADGE_COLOR: Record = { [ServiceStatus.UP]: 'green', @@ -47,3 +49,19 @@ export const getAgentsMonitoringStatus = (agents: DbAgent[]) => { ); return allAgentsOk ? MonitoringStatus.OK : MonitoringStatus.FAILED; }; + +export const stripServiceId = (serviceId: string) => { + const regex = /\/service_id\/(.*)/gm; + const match = regex.exec(serviceId); + + if (match && match.length > 0) { + return match[1] || ''; + } + + return ''; +}; + +export const getNodeLink = (service: FlattenService) => { + const nodeId = service.nodeId === 'pmm-server' ? 'pmm-server' : stripNodeId(service.nodeId); + return `/inventory/nodes?search-text-input=${nodeId}&search-select=nodeId`; +}; diff --git a/public/app/percona/inventory/Tabs/Tabs.styles.ts b/public/app/percona/inventory/Tabs/Tabs.styles.ts index 8cba75c57dd8e..361c6757ebcb1 100644 --- a/public/app/percona/inventory/Tabs/Tabs.styles.ts +++ b/public/app/percona/inventory/Tabs/Tabs.styles.ts @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -export const getStyles = ({ spacing }: GrafanaTheme2) => ({ +export const getStyles = ({ colors, spacing }: GrafanaTheme2) => ({ detailsWrapper: css` display: flex; flex-direction: column; @@ -53,4 +53,7 @@ export const getStyles = ({ spacing }: GrafanaTheme2) => ({ goBack: css` vertical-align: middle; `, + link: css` + color: ${colors.text.link}; + `, }); diff --git a/public/app/percona/shared/components/Elements/Table/Filter/Filter.tsx b/public/app/percona/shared/components/Elements/Table/Filter/Filter.tsx index b414c13f0fd92..15ccbc8f4df53 100644 --- a/public/app/percona/shared/components/Elements/Table/Filter/Filter.tsx +++ b/public/app/percona/shared/components/Elements/Table/Filter/Filter.tsx @@ -167,7 +167,14 @@ export const Filter = ({ columns, rawData, setFilteredData, hasBackendFiltering )} )} - {!hasBackendFiltering && onFormChange(state.values)}>} + {!hasBackendFiltering && ( + onFormChange(state.values)} + > + )} )} /> diff --git a/public/app/percona/shared/components/Elements/Table/Table.tsx b/public/app/percona/shared/components/Elements/Table/Table.tsx index e5afbabf0cc81..b37dd80132304 100644 --- a/public/app/percona/shared/components/Elements/Table/Table.tsx +++ b/public/app/percona/shared/components/Elements/Table/Table.tsx @@ -63,6 +63,7 @@ export const Table: FC = ({ const manualPagination = !!(totalPages && totalPages >= 0); const initialState: Partial = { pageIndex: propPageIndex, + hiddenColumns: columns.filter((c) => c.hidden && c.id).map((c) => c.id!), }; const tableOptions: PaginatedTableOptions = { columns, diff --git a/public/app/percona/shared/components/Elements/Table/Table.types.ts b/public/app/percona/shared/components/Elements/Table/Table.types.ts index 08ec10de3959e..8242729b5a7df 100644 --- a/public/app/percona/shared/components/Elements/Table/Table.types.ts +++ b/public/app/percona/shared/components/Elements/Table/Table.types.ts @@ -34,6 +34,7 @@ export type ExtendedColumn = Column & { label?: string; noHiddenOverflow?: boolean; tooltipInfo?: PopoverContent; + hidden?: boolean; }; export enum FilterFieldTypes {