From ed37e9bf563bc9f5aadbccab0ef089ef33d8fc47 Mon Sep 17 00:00:00 2001 From: Phillip Bailey Date: Tue, 28 Jun 2022 20:18:18 -0400 Subject: [PATCH] Add duration to top consumers card --- locales/en/plugin__kubevirt-plugin.json | 2 + src/utils/i18n.ts | 19 +++++ src/utils/utils/dropdownEnum.ts | 18 +++++ .../top-consumers-card/TopConsumersCard.scss | 3 +- .../top-consumers-card/TopConsumersCard.tsx | 75 ++++++++++++------- .../utils/DurationDropdown.tsx | 43 +++++++++++ .../utils/DurationOption.ts | 49 ++++++++++++ .../utils/TopConsumerCard.tsx | 8 +- .../utils/TopConsumersChartList.tsx | 48 +++++------- .../top-consumers-card/utils/constants.ts | 3 +- .../utils/topConsumerMetric.ts | 19 +---- .../utils/topConsumerScope.ts | 10 +-- .../top-consumers-card/utils/utils.ts | 20 +++++ 13 files changed, 225 insertions(+), 92 deletions(-) create mode 100644 src/utils/utils/dropdownEnum.ts create mode 100644 src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationDropdown.tsx create mode 100644 src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationOption.ts diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 99b7adf5f..fb55cdfbb 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -40,8 +40,10 @@ "<0>The descheduler can be used to evict a running pod to allow the pod to be rescheduled onto a more suitable node.<1><2>Note: if VirtualMachine have LiveMigration=False condition, edit is disabled.": "<0>The descheduler can be used to evict a running pod to allow the pod to be rescheduled onto a more suitable node.<1><2>Note: if VirtualMachine have LiveMigration=False condition, edit is disabled.", "<0>This is a Windows VirtualMachine but no Service for the RDP (Remote Desktop Protocol) can be found.<1><2>For better experience accessing Windows console, it is recommended to use the RDP. To do so, create a service:<1><0>exposing the <2>{DEFAULT_RDP_PORT}/tcp port of the VirtualMachine<1>using selector: <2>{TEMPLATE_VM_NAME_LABEL}: {name}<2>Example: virtctl expose VirtualMachine {name} --name {name}-rdp --port [UNIQUE_PORT] --target-port {DEFAULT_RDP_PORT} --type NodePortMake sure, the VirtualMachine object has <3>spec.template.metadata.labels set to <6>{TEMPLATE_VM_NAME_LABEL}: {name}": "<0>This is a Windows VirtualMachine but no Service for the RDP (Remote Desktop Protocol) can be found.<1><2>For better experience accessing Windows console, it is recommended to use the RDP. To do so, create a service:<1><0>exposing the <2>{DEFAULT_RDP_PORT}/tcp port of the VirtualMachine<1>using selector: <2>{TEMPLATE_VM_NAME_LABEL}: {name}<2>Example: virtctl expose VirtualMachine {name} --name {name}-rdp --port [UNIQUE_PORT] --target-port {DEFAULT_RDP_PORT} --type NodePortMake sure, the VirtualMachine object has <3>spec.template.metadata.labels set to <6>{TEMPLATE_VM_NAME_LABEL}: {name}", "<0>Unattend.xml<1>Unattend can be used to configure windows setup and can be picked up several times during windows setup/configuration.<2><0>{t('Learn more')}": "<0>Unattend.xml<1>Unattend can be used to configure windows setup and can be picked up several times during windows setup/configuration.<2><0>{t('Learn more')}", + "1 day": "1 day", "1 hour": "1 hour", "24 hours": "24 hours", + "4 hours": "4 hours", "5 min": "5 min", "5 minutes": "5 minutes", "6 hours": "6 hours", diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index 35b444c77..fb49636f5 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -12,3 +12,22 @@ // t('plugin__kubevirt-plugin~Templates') // t('plugin__kubevirt-plugin~Overview') // t('plugin__kubevirt-plugin~MTV') +// t('plugin__kubevirt-plugin~5 minutes') +// t('plugin__kubevirt-plugin~1 hour') +// t('plugin__kubevirt-plugin~4 hours') +// t('plugin__kubevirt-plugin~1 day') +// t('plugin__kubevirt-plugin~By CPU') +// t('plugin__kubevirt-plugin~CPU') +// t('plugin__kubevirt-plugin~By memory') +// t('plugin__kubevirt-plugin~Memory') +// t('plugin__kubevirt-plugin~By memory swap traffic') +// t('plugin__kubevirt-plugin~Memory swap traffic') +// t('plugin__kubevirt-plugin~By vCPU wait') +// t('plugin__kubevirt-plugin~vCPU wait') +// t('plugin__kubevirt-plugin~By throughput') +// t('plugin__kubevirt-plugin~Storage throughput') +// t('plugin__kubevirt-plugin~By IOPS') +// t('plugin__kubevirt-plugin~Storage IOPS') +// t('plugin__kubevirt-plugin~Project') +// t('plugin__kubevirt-plugin~VM') +// t('plugin__kubevirt-plugin~Node') diff --git a/src/utils/utils/dropdownEnum.ts b/src/utils/utils/dropdownEnum.ts new file mode 100644 index 000000000..96c525958 --- /dev/null +++ b/src/utils/utils/dropdownEnum.ts @@ -0,0 +1,18 @@ +import { ObjectEnum } from '@kubevirt-utils/utils/ObjectEnum'; + +export type DropdownProps = { + dropdownLabel: string; +}; + +abstract class DropdownEnum extends ObjectEnum { + protected readonly dropdownLabel: string; + + protected constructor(value: T, { dropdownLabel }) { + super(value); + this.dropdownLabel = dropdownLabel; + } + + getDropdownLabel = (): string => this.dropdownLabel; +} + +export default DropdownEnum; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.scss b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.scss index 53e498cca..7f4b3a034 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.scss +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.scss @@ -4,8 +4,7 @@ border-bottom: var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100); } -.kv-top-consumers-card__dropdown { - display: inline-block; +.kv-top-consumers-card__dropdown--duration { margin-left: var(--pf-global--spacer--lg); } diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.tsx b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.tsx index 7304e1659..fdf66f538 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.tsx +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/TopConsumersCard.tsx @@ -13,7 +13,13 @@ import { SelectVariant, } from '@patternfly/react-core'; -import { SHOW_TOP_5_ITEMS, TOP_CONSUMER_NUM_ITEMS_KEY } from './utils/constants'; +import { + SHOW_TOP_5_ITEMS, + TOP_CONSUMERS_DURATION_KEY, + TOP_CONSUMERS_NUM_ITEMS_KEY, +} from './utils/constants'; +import DurationDropdown from './utils/DurationDropdown'; +import DurationOption from './utils/DurationOption'; import FormPFSelect from './utils/FormPFSelect'; import TopConsumersGridRow from './utils/TopConsumersGridRow'; import { topAmountSelectOptions } from './utils/utils'; @@ -23,39 +29,50 @@ import './TopConsumersCard.scss'; const TopConsumersCard: React.FC = () => { const { t } = useKubevirtTranslation(); const [numItemsToShow, setNumItemsToShow] = useLocalStorage( - TOP_CONSUMER_NUM_ITEMS_KEY, + TOP_CONSUMERS_NUM_ITEMS_KEY, SHOW_TOP_5_ITEMS, ); + const [duration, setDuration] = useLocalStorage( + TOP_CONSUMERS_DURATION_KEY, + DurationOption.FIVE_MIN.toString(), + ); - const onTopAmountSelect = (value) => setNumItemsToShow(value); + const onNumItemsSelect = (value) => setNumItemsToShow(value); + const onDurationSelect = (value: string) => + setDuration(DurationOption.fromDropdownLabel(value).toString()); return ( - - - {t('Top consumers')} - - - {t('View virtualization dashboard')} - -
- onTopAmountSelect(value)} - > - {topAmountSelectOptions(t).map((opt) => ( - - ))} - -
-
-
- - - - -
+
+ + + {t('Top consumers')} + + + {t('View virtualization dashboard')} + +
+ +
+
+ onNumItemsSelect(value)} + > + {topAmountSelectOptions(t).map((opt) => ( + + ))} + +
+
+
+ + + + +
+
); }; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationDropdown.tsx b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationDropdown.tsx new file mode 100644 index 000000000..36bf3c37d --- /dev/null +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationDropdown.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; + +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; + +import DurationOption from './DurationOption'; + +export type DurationDropdownProps = { + selectedDuration: string; + selectHandler: (value: string) => void; +}; + +const DurationDropdown: React.FC = ({ selectedDuration, selectHandler }) => { + const [isOpen, setIsOpen] = React.useState(false); + + const onSelect = (event, durationOption: string) => { + selectHandler(durationOption); + setIsOpen(false); + }; + + return ( + + ); +}; + +export default DurationDropdown; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationOption.ts b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationOption.ts new file mode 100644 index 000000000..489094c48 --- /dev/null +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/DurationOption.ts @@ -0,0 +1,49 @@ +import DropdownEnum from '@kubevirt-utils/utils/dropdownEnum'; +import { ObjectEnum } from '@kubevirt-utils/utils/ObjectEnum'; + +class DurationOption extends DropdownEnum { + static readonly FIVE_MIN = new DurationOption('5m', { + dropdownLabel: '5 minutes', + }); + + static readonly ONE_HOUR = new DurationOption('1h', { + dropdownLabel: '1 hour', + }); + + static readonly FOUR_HOURS = new DurationOption('4h', { + dropdownLabel: '4 hours', + }); + + static readonly ONE_DAY = new DurationOption('1d', { + dropdownLabel: '1 day', + }); + + private static readonly ALL = Object.freeze( + ObjectEnum.getAllClassEnumProperties(DurationOption), + ); + + static getAll = () => DurationOption.ALL; + + private static readonly stringMapper = DurationOption.ALL.reduce( + (accumulator, durationOption: DurationOption) => ({ + ...accumulator, + [durationOption.value]: durationOption, + }), + {}, + ); + + private static readonly dropdownLabelMapper = DurationOption.ALL.reduce( + (accumulator, durationOption: DurationOption) => ({ + ...accumulator, + [durationOption.dropdownLabel]: durationOption, + }), + {}, + ); + + static fromString = (source: string): DurationOption => DurationOption.stringMapper[source]; + + static fromDropdownLabel = (dropdownLabel: string): DurationOption => + DurationOption.dropdownLabelMapper[dropdownLabel]; +} + +export default DurationOption; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumerCard.tsx b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumerCard.tsx index 4a88ad76c..0ac0e9bf1 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumerCard.tsx +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumerCard.tsx @@ -4,7 +4,6 @@ import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTransla import useLocalStorage from '@kubevirt-utils/hooks/useLocalStorage'; import { Card, SelectOption, SelectVariant } from '@patternfly/react-core'; -import { SHOW_TOP_5_ITEMS, TOP_CONSUMER_NUM_ITEMS_KEY } from './constants'; import FormPFSelect from './FormPFSelect'; import { TopConsumerMetric } from './topConsumerMetric'; import { TopConsumersChartList } from './TopConsumersChartList'; @@ -19,11 +18,7 @@ type TopConsumersMetricCard = { const TopConsumerCard: React.FC = ({ cardID }) => { const { t } = useKubevirtTranslation(); - const [numItemsLabel] = useLocalStorage(TOP_CONSUMER_NUM_ITEMS_KEY); - const numItemsToShow = React.useMemo( - () => (numItemsLabel === SHOW_TOP_5_ITEMS ? 5 : 10), - [numItemsLabel], - ); + const [metricKey, setMetricKey] = useLocalStorage( `${cardID}-metric-value`, initialTopConsumerCardSettings[cardID]?.metric.toString(), @@ -75,7 +70,6 @@ const TopConsumerCard: React.FC = ({ cardID }) => {
{t('Usage')}
diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumersChartList.tsx b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumersChartList.tsx index f1a428c39..dcd077f17 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumersChartList.tsx +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/TopConsumersChartList.tsx @@ -1,59 +1,47 @@ import * as React from 'react'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import useLocalStorage from '@kubevirt-utils/hooks/useLocalStorage'; import { PrometheusEndpoint, usePrometheusPoll } from '@openshift-console/dynamic-plugin-sdk'; import { CardBody } from '@patternfly/react-core'; +import { + SHOW_TOP_5_ITEMS, + TOP_CONSUMERS_DURATION_KEY, + TOP_CONSUMERS_NUM_ITEMS_KEY, +} from './constants'; import NoDataAvailableMessage from './NoDataAvailableMessage'; import { getTopConsumerQuery } from './queries'; import { TopConsumerMetric } from './topConsumerMetric'; import { TopConsumerScope } from './topConsumerScope'; import TopConsumersProgressChart from './TopConsumersProgressChart'; -import { getHumanizedValue, getValue } from './utils'; +import { getChartTitle, getHumanizedValue, getValue } from './utils'; import './TopConsumersChartList.scss'; -const getChartTitle = (scope, queryData) => { - let title = ''; - const metricData = queryData?.metric; - switch (scope) { - case TopConsumerScope.NODE: - title = metricData?.node; - break; - case TopConsumerScope.PROJECT: - title = metricData?.namespace; - break; - case TopConsumerScope.VM: - default: - title = - metricData?.name || metricData?.label_vm_kubevirt_io_name || `VMI (${metricData.pod})`; - break; - } - - return title; -}; - type TopConsumersChartListProps = { - numItems: number; metric: TopConsumerMetric; scope: TopConsumerScope; }; -export const TopConsumersChartList: React.FC = ({ - numItems, - metric, - scope, -}) => { +export const TopConsumersChartList: React.FC = ({ metric, scope }) => { const { t } = useKubevirtTranslation(); + const [duration] = useLocalStorage(TOP_CONSUMERS_DURATION_KEY); + const [numItemsLabel] = useLocalStorage(TOP_CONSUMERS_NUM_ITEMS_KEY); + const numItemsToShow = React.useMemo( + () => (numItemsLabel === SHOW_TOP_5_ITEMS ? 5 : 10), + [numItemsLabel], + ); + const [query] = usePrometheusPoll({ endpoint: PrometheusEndpoint.QUERY, - query: getTopConsumerQuery(metric.getValue(), scope.getValue(), numItems), + query: getTopConsumerQuery(metric.getValue(), scope.getValue(), numItemsToShow, duration), endTime: Date.now(), }); const numQueryResults = query?.data?.result?.length; const ChartList = React.useMemo(() => { - const numLinesToShow = numQueryResults >= numItems ? numItems : numQueryResults; + const numLinesToShow = numQueryResults >= numItemsToShow ? numItemsToShow : numQueryResults; const max = getValue(query?.data?.result[0]?.value?.[1]); const charts = []; @@ -75,7 +63,7 @@ export const TopConsumersChartList: React.FC = ({ ); } return charts; - }, [query, metric, scope, numItems, numQueryResults]); + }, [query, metric, scope, numItemsToShow, numQueryResults]); const showNoDataMessage = numQueryResults === 0; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/constants.ts b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/constants.ts index 74a65fb2c..6a92771dc 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/constants.ts +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/constants.ts @@ -5,6 +5,7 @@ export const NON_VCPU_LINK = export const vCPU_LINK = 'https://access.redhat.com/documentation/fr-fr/openshift_container_platform/4.8/html/openshift_virtualization/logging-events-and-monitoring#virt-promql-vcpu-metrics_virt-prometheus-queries'; -export const TOP_CONSUMER_NUM_ITEMS_KEY = 'top-consumer-card-num-items'; +export const TOP_CONSUMERS_NUM_ITEMS_KEY = 'top-consumers-card-num-items'; +export const TOP_CONSUMERS_DURATION_KEY = 'top-consumers-card-duration'; export const SHOW_TOP_5_ITEMS = 'Show top 5'; diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerMetric.ts b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerMetric.ts index 6bc474963..4f19d4fd6 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerMetric.ts +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerMetric.ts @@ -1,3 +1,4 @@ +import DropdownEnum from '@kubevirt-utils/utils/dropdownEnum'; import { ObjectEnum } from '@kubevirt-utils/utils/ObjectEnum'; type TopConsumerMetricData = { @@ -5,62 +6,48 @@ type TopConsumerMetricData = { chartLabel: string; }; -abstract class TopConsumerMetricObjectEnum extends ObjectEnum { +abstract class TopConsumerMetricObjectEnum extends DropdownEnum { protected readonly dropdownLabel: string; private readonly chartLabel: string; protected constructor(value: T, { dropdownLabel, chartLabel }: TopConsumerMetricData) { - super(value); + super(value, { dropdownLabel }); this.dropdownLabel = dropdownLabel; this.chartLabel = chartLabel; } - getDropdownLabel = (): string => this.dropdownLabel; - getChartLabel = (): string => this.chartLabel; } export class TopConsumerMetric extends TopConsumerMetricObjectEnum { static readonly CPU = new TopConsumerMetric('cpu', { - // t('By CPU') dropdownLabel: 'By CPU', - // t('CPU') chartLabel: 'CPU', }); static readonly MEMORY = new TopConsumerMetric('memory', { - // t('By memory') dropdownLabel: 'By memory', - // t('Memory') chartLabel: 'Memory', }); static readonly MEMORY_SWAP_TRAFFIC = new TopConsumerMetric('memory-swap-traffic', { - // t('By memory swap traffic') dropdownLabel: 'By memory swap traffic', - // t('Memory swap traffic') chartLabel: 'Memory swap traffic', }); static readonly VCPU_WAIT = new TopConsumerMetric('vcpu-wait', { - // t('By vCPU wait') dropdownLabel: 'By vCPU wait', - // t('vCPU wait') chartLabel: 'vCPU wait', }); static readonly STORAGE_THROUGHPUT = new TopConsumerMetric('storage-throughput', { - // t('By throughput') dropdownLabel: 'By throughput', - // t('Storage throughput') chartLabel: 'Storage throughput', }); static readonly STORAGE_IOPS = new TopConsumerMetric('storage-iops', { - // t('By IOPS') dropdownLabel: 'By IOPS', - // t('Storage IOPS') chartLabel: 'Storage IOPS', }); diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerScope.ts b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerScope.ts index efc5fb72d..1bafc407c 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerScope.ts +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/topConsumerScope.ts @@ -1,33 +1,29 @@ +import DropdownEnum from '@kubevirt-utils/utils/dropdownEnum'; import { ObjectEnum } from '@kubevirt-utils/utils/ObjectEnum'; type TopConsumerScopeData = { dropdownLabel: string; }; -abstract class TopConsumerScopeObjectEnum extends ObjectEnum { +abstract class TopConsumerScopeObjectEnum extends DropdownEnum { protected readonly dropdownLabel: string; protected constructor(value: T, { dropdownLabel }: TopConsumerScopeData) { - super(value); + super(value, { dropdownLabel }); this.dropdownLabel = dropdownLabel; } - - getDropdownLabel = (): string => this.dropdownLabel; } export class TopConsumerScope extends TopConsumerScopeObjectEnum { static readonly PROJECT = new TopConsumerScope('PROJECT', { - // t('Project') dropdownLabel: 'Project', }); static readonly VM = new TopConsumerScope('VM', { - // t('VM') dropdownLabel: 'VM', }); static readonly NODE = new TopConsumerScope('NODE', { - // t('Node') dropdownLabel: 'Node', }); diff --git a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/utils.ts b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/utils.ts index 35342a14d..e04484818 100644 --- a/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/utils.ts +++ b/src/views/clusteroverview/overview/components/MonitoringTab/top-consumers-card/utils/utils.ts @@ -88,3 +88,23 @@ export const initialTopConsumerCardSettings: { metric: TopConsumerMetric.STORAGE_IOPS, }, }; + +export const getChartTitle = (scope, queryData) => { + let title = ''; + const metricData = queryData?.metric; + switch (scope) { + case TopConsumerScope.NODE: + title = metricData?.node; + break; + case TopConsumerScope.PROJECT: + title = metricData?.namespace; + break; + case TopConsumerScope.VM: + default: + title = + metricData?.name || metricData?.label_vm_kubevirt_io_name || `VMI (${metricData.pod})`; + break; + } + + return title; +};