From 4a59c2392bda679035bedc0a6118e42ceb20fc08 Mon Sep 17 00:00:00 2001 From: Matej Kubinec <32638572+matejkubinec@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:33:49 +0200 Subject: [PATCH] PMM-12378 Cluster view (#683) * PMM-12378 Extract services table * PMM-12378 Add basic cluster view * PMM-12378 Add edit option back in * PMM-12310 Handle service selection correctly * PMM-12378 Extend services table options * PMM-12378 Expand cluster if filtered & reset on close * PMM-12378 Add tech preview text * PMM-12378 Revert typings change * PMM-12378 Fix link from nodes * PMM-12378 Clear toggle when clicking on services link --- .../percona/inventory/Inventory.constants.ts | 1 + .../percona/inventory/Inventory.messages.ts | 2 + public/app/percona/inventory/Tabs/Nodes.tsx | 15 +- .../app/percona/inventory/Tabs/Services.tsx | 253 ++++-------------- .../inventory/Tabs/Services/ClusterItem.tsx | 54 ++++ .../inventory/Tabs/Services/Clusters.tsx | 42 +++ .../inventory/Tabs/Services/Clusters.type.tsx | 23 ++ .../inventory/Tabs/Services/Clusters.utils.ts | 29 ++ .../inventory/Tabs/Services/ServicesTable.tsx | 238 ++++++++++++++++ .../app/percona/inventory/Tabs/Tabs.styles.ts | 13 + .../TechnicalPreview/TechnicalPreview.tsx | 4 +- 11 files changed, 459 insertions(+), 215 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 create mode 100644 public/app/percona/inventory/Tabs/Services/ServicesTable.tsx diff --git a/public/app/percona/inventory/Inventory.constants.ts b/public/app/percona/inventory/Inventory.constants.ts index a357f6c9ad833..f17755c294f47 100644 --- a/public/app/percona/inventory/Inventory.constants.ts +++ b/public/app/percona/inventory/Inventory.constants.ts @@ -29,3 +29,4 @@ export const inventoryTypes = { export const GET_SERVICES_CANCEL_TOKEN = 'getServices'; export const GET_NODES_CANCEL_TOKEN = 'getNodes'; export const GET_AGENTS_CANCEL_TOKEN = 'getAgents'; +export const CLUSTERS_SWITCH_KEY = 'pmm-organize-by-clusters'; diff --git a/public/app/percona/inventory/Inventory.messages.ts b/public/app/percona/inventory/Inventory.messages.ts index 444cce68032f8..44f590eabd96f 100644 --- a/public/app/percona/inventory/Inventory.messages.ts +++ b/public/app/percona/inventory/Inventory.messages.ts @@ -27,6 +27,8 @@ 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', + technicalPreview: '(Technical Preview) ', }, agents: { goBackToServices: 'Go back to services', diff --git a/public/app/percona/inventory/Tabs/Nodes.tsx b/public/app/percona/inventory/Tabs/Nodes.tsx index 5019cb6240751..7d738f55d8cc1 100644 --- a/public/app/percona/inventory/Tabs/Nodes.tsx +++ b/public/app/percona/inventory/Tabs/Nodes.tsx @@ -27,7 +27,7 @@ import { useAppDispatch } from 'app/store/store'; import { useSelector } from 'app/types'; import { appEvents } from '../../../core/app_events'; -import { GET_NODES_CANCEL_TOKEN } from '../Inventory.constants'; +import { CLUSTERS_SWITCH_KEY, GET_NODES_CANCEL_TOKEN } from '../Inventory.constants'; import { Messages } from '../Inventory.messages'; import { FlattenNode, MonitoringStatus, Node } from '../Inventory.types'; import { StatusBadge } from '../components/StatusBadge/StatusBadge'; @@ -65,6 +65,11 @@ export const NodesTab = () => { [styles.actionItemTxtSpan] ); + const clearClusterToggle = useCallback(() => { + // Reset toggle to false when linking from nodes + localStorage.removeItem(CLUSTERS_SWITCH_KEY); + }, []); + const columns = useMemo( (): Array> => [ { @@ -155,7 +160,7 @@ export const NodesTab = () => { if (value.length === 1) { return ( - + {value[0].serviceName} ); @@ -166,7 +171,7 @@ export const NodesTab = () => { }, getExpandAndActionsCol(getActions), ], - [styles, getActions] + [styles, getActions, clearClusterToggle] ); const loadData = useCallback(async () => { @@ -206,7 +211,7 @@ export const NodesTab = () => { {row.original.services.map((service) => (
- + {service.serviceName}
@@ -234,7 +239,7 @@ export const NodesTab = () => { ); }, - [styles.tagList, styles.link] + [styles.tagList, styles.link, clearClusterToggle] ); const deletionMsg = useMemo(() => { diff --git a/public/app/percona/inventory/Tabs/Services.tsx b/public/app/percona/inventory/Tabs/Services.tsx index 80fa54e56259f..6e900b62d5b85 100644 --- a/public/app/percona/inventory/Tabs/Services.tsx +++ b/public/app/percona/inventory/Tabs/Services.tsx @@ -1,44 +1,31 @@ /* eslint-disable @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-explicit-any */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Row } from 'react-table'; +import { useLocalStorage } from 'react-use'; import { locationService } from '@grafana/runtime'; -import { Badge, Button, HorizontalGroup, Icon, Link, TagList, useStyles2 } from '@grafana/ui'; +import { Button, HorizontalGroup, Icon, InlineSwitch, Tooltip, 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 { 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 { ReadMoreLink } from 'app/percona/shared/components/Elements/TechnicalPreview/TechnicalPreview'; import { useCancelToken } from 'app/percona/shared/components/hooks/cancelToken.hook'; import { usePerconaNavModel } from 'app/percona/shared/components/hooks/perconaNavModel'; import { fetchActiveServiceTypesAction, fetchServicesAction } 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 { GET_SERVICES_CANCEL_TOKEN } from '../Inventory.constants'; +import { CLUSTERS_SWITCH_KEY, GET_SERVICES_CANCEL_TOKEN } from '../Inventory.constants'; import { Messages } from '../Inventory.messages'; -import { FlattenService, MonitoringStatus } from '../Inventory.types'; +import { FlattenService } from '../Inventory.types'; import DeleteServiceModal from '../components/DeleteServiceModal'; import DeleteServicesModal from '../components/DeleteServicesModal'; -import { StatusBadge } from '../components/StatusBadge/StatusBadge'; -import { StatusInfo } from '../components/StatusInfo/StatusInfo'; -import { StatusLink } from '../components/StatusLink/StatusLink'; -import { - getBadgeColorForServiceStatus, - getBadgeIconForServiceStatus, - getBadgeTextForServiceStatus, - getAgentsMonitoringStatus, - getNodeLink, -} from './Services.utils'; +import { getAgentsMonitoringStatus } from './Services.utils'; +import Clusters from './Services/Clusters'; +import ServicesTable from './Services/ServicesTable'; import { getStyles } from './Tabs.styles'; export const Services = () => { @@ -56,146 +43,13 @@ export const Services = () => { return { type: value.type, ...value.params, + cluster: value.params.cluster || value.params.customLabels?.cluster || '', agentsStatus: getAgentsMonitoringStatus(value.params.agents ?? []), }; }), [fetchedServices] ); - - const getActions = useCallback( - (row: Row): Action[] => [ - { - content: ( - - - {Messages.delete} - - ), - action: () => { - setActionItem(row.original); - setModalVisible(true); - }, - }, - { - content: ( - - - {Messages.edit} - - ), - action: () => { - const serviceId = row.original.serviceId.split('/').pop(); - locationService.push(`/edit-instance/${serviceId}`); - }, - }, - { - 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.actionItemTxtSpan] - ); - - 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 [showClusters, setShowClusters] = useLocalStorage(CLUSTERS_SWITCH_KEY, false); const loadData = useCallback(async () => { try { @@ -216,6 +70,7 @@ export const Services = () => { useEffect(() => { loadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -223,40 +78,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); @@ -271,9 +96,28 @@ export const Services = () => { return ( - + + + setShowClusters(!showClusters)} + showLabel + transparent + /> + }> +
+ + {Messages.services.technicalPreview} + + +
+
+