From 7901c69c7ca8f0f28b8a74e78010b12cab695535 Mon Sep 17 00:00:00 2001 From: Matej Kubinec Date: Mon, 28 Aug 2023 13:38:47 +0200 Subject: [PATCH 01/10] PMM-12378 Extract services table --- .../app/percona/inventory/Tabs/Services.tsx | 206 ++--------------- .../inventory/Tabs/Services/ServicesTable.tsx | 216 ++++++++++++++++++ 2 files changed, 229 insertions(+), 193 deletions(-) create mode 100644 public/app/percona/inventory/Tabs/Services/ServicesTable.tsx diff --git a/public/app/percona/inventory/Tabs/Services.tsx b/public/app/percona/inventory/Tabs/Services.tsx index 5cb66638236a3..22355be5e709e 100644 --- a/public/app/percona/inventory/Tabs/Services.tsx +++ b/public/app/percona/inventory/Tabs/Services.tsx @@ -5,15 +5,10 @@ import { Row } from 'react-table'; import { AppEvents } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { Badge, Button, HorizontalGroup, Icon, Link, Modal, TagList, useStyles2 } from '@grafana/ui'; +import { Button, HorizontalGroup, Modal, 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'; import { CheckboxField } from 'app/percona/shared/components/Elements/Checkbox'; -import { DetailsRow } from 'app/percona/shared/components/Elements/DetailsRow/DetailsRow'; import { FeatureLoader } from 'app/percona/shared/components/Elements/FeatureLoader'; -import { ServiceIconWithText } from 'app/percona/shared/components/Elements/ServiceIconWithText/ServiceIconWithText'; -import { ExtendedColumn, FilterFieldTypes, Table } from 'app/percona/shared/components/Elements/Table'; import { FormElement } from 'app/percona/shared/components/Form'; import { useCancelToken } from 'app/percona/shared/components/hooks/cancelToken.hook'; import { usePerconaNavModel } from 'app/percona/shared/components/hooks/perconaNavModel'; @@ -24,28 +19,17 @@ import { } from 'app/percona/shared/core/reducers/services'; import { getServices } from 'app/percona/shared/core/selectors'; import { isApiCancelError } from 'app/percona/shared/helpers/api'; -import { getDashboardLinkForService } from 'app/percona/shared/helpers/getDashboardLinkForService'; -import { getExpandAndActionsCol } from 'app/percona/shared/helpers/getExpandAndActionsCol'; import { logger } from 'app/percona/shared/helpers/logger'; -import { ServiceStatus } from 'app/percona/shared/services/services/Services.types'; import { useAppDispatch } from 'app/store/store'; import { useSelector } from 'app/types'; import { appEvents } from '../../../core/app_events'; import { GET_SERVICES_CANCEL_TOKEN } from '../Inventory.constants'; import { Messages } from '../Inventory.messages'; -import { FlattenService, MonitoringStatus } from '../Inventory.types'; -import { StatusBadge } from '../components/StatusBadge/StatusBadge'; -import { StatusInfo } from '../components/StatusInfo/StatusInfo'; -import { StatusLink } from '../components/StatusLink/StatusLink'; +import { FlattenService } from '../Inventory.types'; -import { - getBadgeColorForServiceStatus, - getBadgeIconForServiceStatus, - getBadgeTextForServiceStatus, - getAgentsMonitoringStatus, - getNodeLink, -} from './Services.utils'; +import { getAgentsMonitoringStatus } from './Services.utils'; +import ServicesTable from './Services/ServicesTable'; import { getStyles } from './Tabs.styles'; export const Services = () => { @@ -69,129 +53,6 @@ export const Services = () => { [fetchedServices] ); - const getActions = useCallback( - (row: Row): Action[] => [ - { - content: ( - - - {Messages.delete} - - ), - action: () => { - setActionItem(row.original); - setModalVisible(true); - }, - }, - { - content: Messages.services.actions.dashboard, - action: () => { - locationService.push(getDashboardLinkForService(row.original.type, row.original.serviceName)); - }, - }, - { - content: Messages.services.actions.qan, - action: () => { - locationService.push(`/d/pmm-qan/pmm-query-analytics?var-service_name=${row.original.serviceName}`); - }, - }, - ], - [styles.deleteItemTxtSpan] - ); - - 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', - Cell: ({ value }: { value: ServiceStatus }) => ( - - ), - tooltipInfo: , - type: FilterFieldTypes.DROPDOWN, - options: [ - { - label: 'Up', - value: ServiceStatus.UP, - }, - { - label: 'Down', - value: ServiceStatus.DOWN, - }, - { - label: 'Unknown', - value: ServiceStatus.UNKNOWN, - }, - { - label: 'N/A', - value: ServiceStatus.NA, - }, - ], - }, - { - Header: Messages.services.columns.serviceName, - accessor: 'serviceName', - Cell: ({ value, row }: { row: Row; value: string }) => ( - - ), - type: FilterFieldTypes.TEXT, - }, - { - Header: Messages.services.columns.nodeName, - accessor: 'nodeName', - Cell: ({ value, row }: { row: Row; value: string }) => ( - - {value} - - ), - type: FilterFieldTypes.TEXT, - }, - { - Header: Messages.services.columns.monitoring, - accessor: 'agentsStatus', - width: '70px', - Cell: ({ value, row }) => ( - - ), - type: FilterFieldTypes.RADIO_BUTTON, - options: [ - { - label: MonitoringStatus.OK, - value: MonitoringStatus.OK, - }, - { - label: MonitoringStatus.FAILED, - value: MonitoringStatus.FAILED, - }, - ], - }, - { - Header: Messages.services.columns.address, - accessor: 'address', - type: FilterFieldTypes.TEXT, - }, - { - Header: Messages.services.columns.port, - accessor: 'port', - width: '100px', - type: FilterFieldTypes.TEXT, - }, - getExpandAndActionsCol(getActions), - ], - [styles, getActions] - ); - const deletionMsg = useMemo(() => { const servicesToDelete = actionItem ? [actionItem] : selected; @@ -257,40 +118,10 @@ export const Services = () => { setSelectedRows(rows); }, []); - const renderSelectedSubRow = React.useCallback( - (row: Row) => { - const labels = row.original.customLabels || {}; - const labelKeys = Object.keys(labels); - const agents = row.original.agents || []; - - return ( - - {!!agents.length && ( - - - - )} - - {row.original.serviceId} - - {!!labelKeys.length && ( - - `${label}=${labels![label]}`)} - /> - - )} - - ); - }, - [styles.tagList] - ); + const handleDelete = useCallback((service: FlattenService) => { + setActionItem(service); + setModalVisible(true); + }, []); const onModalClose = useCallback(() => { setModalVisible(false); @@ -357,22 +188,11 @@ export const Services = () => { )} /> - row.serviceId, [])} - showFilter + diff --git a/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx b/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx new file mode 100644 index 0000000000000..15fff9aeb8817 --- /dev/null +++ b/public/app/percona/inventory/Tabs/Services/ServicesTable.tsx @@ -0,0 +1,216 @@ +import React, { FC, useCallback, useMemo } from 'react'; +import { Row } from 'react-table'; + +import { locationService } from '@grafana/runtime'; +import { Badge, HorizontalGroup, Icon, Link, TagList, useStyles2 } from '@grafana/ui'; +import { Action } from 'app/percona/dbaas/components/MultipleActions'; +import { DetailsRow } from 'app/percona/shared/components/Elements/DetailsRow/DetailsRow'; +import { ServiceIconWithText } from 'app/percona/shared/components/Elements/ServiceIconWithText/ServiceIconWithText'; +import { ExtendedColumn, FilterFieldTypes, Table } from 'app/percona/shared/components/Elements/Table'; +import { getDashboardLinkForService } from 'app/percona/shared/helpers/getDashboardLinkForService'; +import { getExpandAndActionsCol } from 'app/percona/shared/helpers/getExpandAndActionsCol'; +import { ServiceStatus } from 'app/percona/shared/services/services/Services.types'; + +import { Messages } from '../../Inventory.messages'; +import { FlattenService, MonitoringStatus } from '../../Inventory.types'; +import { StatusBadge } from '../../components/StatusBadge/StatusBadge'; +import { StatusInfo } from '../../components/StatusInfo/StatusInfo'; +import { StatusLink } from '../../components/StatusLink/StatusLink'; +import { + getBadgeColorForServiceStatus, + getBadgeTextForServiceStatus, + getBadgeIconForServiceStatus, + getNodeLink, + stripServiceId, +} from '../Services.utils'; +import { getStyles } from '../Tabs.styles'; + +interface ServicesTableProps { + isLoading: boolean; + flattenServices: FlattenService[]; + onSelectionChange: (rows: Array>) => void; + onDelete: (service: FlattenService) => void; +} + +const ServicesTable: FC = ({ isLoading, flattenServices, onSelectionChange, onDelete }) => { + const styles = useStyles2(getStyles); + + const getActions = useCallback( + (row: Row): Action[] => [ + { + content: ( + + + {Messages.delete} + + ), + action: () => { + onDelete(row.original); + }, + }, + { + content: Messages.services.actions.dashboard, + action: () => { + locationService.push(getDashboardLinkForService(row.original.type, row.original.serviceName)); + }, + }, + { + content: Messages.services.actions.qan, + action: () => { + locationService.push(`/d/pmm-qan/pmm-query-analytics?var-service_name=${row.original.serviceName}`); + }, + }, + ], + [styles.deleteItemTxtSpan, onDelete] + ); + + 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', + Cell: ({ value }: { value: ServiceStatus }) => ( + + ), + tooltipInfo: , + type: FilterFieldTypes.DROPDOWN, + options: [ + { + label: 'Up', + value: ServiceStatus.UP, + }, + { + label: 'Down', + value: ServiceStatus.DOWN, + }, + { + label: 'Unknown', + value: ServiceStatus.UNKNOWN, + }, + { + label: 'N/A', + value: ServiceStatus.NA, + }, + ], + }, + { + Header: Messages.services.columns.serviceName, + accessor: 'serviceName', + Cell: ({ value, row }: { row: Row; value: string }) => ( + + ), + type: FilterFieldTypes.TEXT, + }, + { + Header: Messages.services.columns.nodeName, + accessor: 'nodeName', + Cell: ({ value, row }: { row: Row; value: string }) => ( + + {value} + + ), + type: FilterFieldTypes.TEXT, + }, + { + Header: Messages.services.columns.monitoring, + accessor: 'agentsStatus', + width: '70px', + Cell: ({ value, row }) => ( + + ), + type: FilterFieldTypes.RADIO_BUTTON, + options: [ + { + label: MonitoringStatus.OK, + value: MonitoringStatus.OK, + }, + { + label: MonitoringStatus.FAILED, + value: MonitoringStatus.FAILED, + }, + ], + }, + { + Header: Messages.services.columns.address, + accessor: 'address', + type: FilterFieldTypes.TEXT, + }, + { + Header: Messages.services.columns.port, + accessor: 'port', + width: '100px', + type: FilterFieldTypes.TEXT, + }, + getExpandAndActionsCol(getActions), + ], + [styles, getActions] + ); + + const renderSelectedSubRow = React.useCallback( + (row: Row) => { + const labels = row.original.customLabels || {}; + const labelKeys = Object.keys(labels); + const agents = row.original.agents || []; + + return ( + + {!!agents.length && ( + + + + )} + + {row.original.serviceId} + + {!!labelKeys.length && ( + + `${label}=${labels![label]}`)} + /> + + )} + + ); + }, + [styles.tagList] + ); + + return ( +
row.serviceId, [])} + showFilter + /> + ); +}; + +export default ServicesTable; From 26044377b74e2c6c747d98648e1d24cbecc1f741 Mon Sep 17 00:00:00 2001 From: Matej Kubinec Date: Mon, 28 Aug 2023 16:06:28 +0200 Subject: [PATCH 02/10] PMM-12378 Add basic cluster view --- .../percona/inventory/Inventory.messages.ts | 1 + .../app/percona/inventory/Tabs/Services.tsx | 33 ++++++++++++----- .../inventory/Tabs/Services/ClusterItem.tsx | 35 +++++++++++++++++++ .../inventory/Tabs/Services/Clusters.tsx | 19 ++++++++++ .../inventory/Tabs/Services/Clusters.type.tsx | 19 ++++++++++ .../inventory/Tabs/Services/Clusters.utils.ts | 20 +++++++++++ .../app/percona/inventory/Tabs/Tabs.styles.ts | 9 +++++ 7 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 public/app/percona/inventory/Tabs/Services/ClusterItem.tsx create mode 100644 public/app/percona/inventory/Tabs/Services/Clusters.tsx create mode 100644 public/app/percona/inventory/Tabs/Services/Clusters.type.tsx create mode 100644 public/app/percona/inventory/Tabs/Services/Clusters.utils.ts diff --git a/public/app/percona/inventory/Inventory.messages.ts b/public/app/percona/inventory/Inventory.messages.ts index db383537f97f6..8a01749ceed5b 100644 --- a/public/app/percona/inventory/Inventory.messages.ts +++ b/public/app/percona/inventory/Inventory.messages.ts @@ -27,6 +27,7 @@ export const Messages = { `Are you sure that you want to permanently delete ${nrItems} service${nrItems ? 's' : ''}`, servicesDeleted: (deletedItems: number, totalItems: number) => `${deletedItems} of ${totalItems} services successfully deleted`, + organizeByClusters: 'Organize by clusters', }, agents: { goBackToServices: 'Go back to services', diff --git a/public/app/percona/inventory/Tabs/Services.tsx b/public/app/percona/inventory/Tabs/Services.tsx index 22355be5e709e..27acad3337d74 100644 --- a/public/app/percona/inventory/Tabs/Services.tsx +++ b/public/app/percona/inventory/Tabs/Services.tsx @@ -2,10 +2,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Form } from 'react-final-form'; import { Row } from 'react-table'; +import { useLocalStorage } from 'react-use'; import { AppEvents } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { Button, HorizontalGroup, Modal, useStyles2 } from '@grafana/ui'; +import { Button, HorizontalGroup, InlineSwitch, Modal, useStyles2 } from '@grafana/ui'; import { OldPage } from 'app/core/components/Page/Page'; import { CheckboxField } from 'app/percona/shared/components/Elements/Checkbox'; import { FeatureLoader } from 'app/percona/shared/components/Elements/FeatureLoader'; @@ -29,6 +30,7 @@ import { Messages } from '../Inventory.messages'; import { FlattenService } from '../Inventory.types'; import { getAgentsMonitoringStatus } from './Services.utils'; +import Clusters from './Services/Clusters'; import ServicesTable from './Services/ServicesTable'; import { getStyles } from './Tabs.styles'; @@ -46,12 +48,14 @@ export const Services = () => { fetchedServices.map((value) => { return { type: value.type, + cluster: value.params.cluster || value.params.customLabels?.cluster, ...value.params, agentsStatus: getAgentsMonitoringStatus(value.params.agents ?? []), }; }), [fetchedServices] ); + const [showClusters, setShowClusters] = useLocalStorage('pmm-organize-by-clusters', false); const deletionMsg = useMemo(() => { const servicesToDelete = actionItem ? [actionItem] : selected; @@ -130,9 +134,18 @@ export const Services = () => { return ( - + + setShowClusters(!showClusters)} + showLabel + transparent + />