Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import type { FC, ReactNode, ReactText } from 'react';
import { memo, useState, useCallback, useMemo, useEffect } from 'react';
import { memo, useState, useCallback, useMemo } from 'react';
import { Button, Popover, PopoverPosition } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom-v5-compat';
import { LIMIT_STATE, Humanize } from '@console/dynamic-plugin-sdk';
import { getPrometheusQueryResponse } from '@console/internal/actions/dashboards';
import {
withDashboardResources,
DashboardItemProps,
} from '@console/internal/components/dashboard/with-dashboard-resources';
import { DataPoint } from '@console/internal/components/graphs';
import { getInstantVectorStats } from '@console/internal/components/graphs/utils';
import { ConsoleSelect } from '@console/internal/components/utils/console-select';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { resourcePathFromModel } from '@console/internal/components/utils/resource-link';
import { K8sKind, referenceForModel, K8sResourceCommon } from '@console/internal/module/k8s';
import { getName, getNamespace } from '../../..';
import { useDashboardResources } from '../../../hooks/useDashboardResources';
import { RedExclamationCircleIcon, YellowExclamationTriangleIcon } from '../../status';
import Status from '../status-card/StatusPopup';

Expand Down Expand Up @@ -116,157 +113,146 @@ export const LimitsBody: FC<LimitsBodyProps> = ({
);
};

export const PopoverBody = withDashboardResources<DashboardItemProps & PopoverBodyProps>(
memo(
({
humanize,
consumers,
export const PopoverBody: FC<PopoverBodyProps> = memo(
({ humanize, consumers, namespace, isOpen, description, children }) => {
const { t } = useTranslation();
const [currentConsumer, setCurrentConsumer] = useState(consumers[0]);
const { query, model, metric, fieldSelector } = currentConsumer;
const k8sResource = useMemo(
() => (isOpen ? getResourceToWatch(model, namespace, fieldSelector) : null),
[fieldSelector, isOpen, model, namespace],
);
const [consumerData, consumerLoaded, consumersLoadError] = useK8sWatchResource<
K8sResourceCommon[]
>(k8sResource);

const prometheusQueries = useMemo(() => (isOpen ? [{ query, namespace }] : []), [
query,
namespace,
watchPrometheus,
stopWatchPrometheusQuery,
prometheusResults,
isOpen,
description,
children,
}) => {
const { t } = useTranslation();
const [currentConsumer, setCurrentConsumer] = useState(consumers[0]);
const { query, model, metric, fieldSelector } = currentConsumer;
const k8sResource = useMemo(
() => (isOpen ? getResourceToWatch(model, namespace, fieldSelector) : null),
[fieldSelector, isOpen, model, namespace],
);
const [consumerData, consumerLoaded, consumersLoadError] = useK8sWatchResource<
K8sResourceCommon[]
>(k8sResource);
useEffect(() => {
if (!isOpen) {
return () => {};
}
watchPrometheus(query, namespace);
return () => {
stopWatchPrometheusQuery(query);
};
}, [query, stopWatchPrometheusQuery, watchPrometheus, namespace, isOpen]);
]);

const { prometheusResults } = useDashboardResources({
prometheusQueries,
});

const top5Data = [];
const top5Data = [];

const [data, error] = getPrometheusQueryResponse(prometheusResults, query);
const bodyData = getInstantVectorStats(data, metric);
const [data, error] = getPrometheusQueryResponse(prometheusResults, query);
const bodyData = getInstantVectorStats(data, metric);

if (k8sResource && consumerLoaded && !consumersLoadError) {
for (const d of bodyData) {
const consumerExists = consumerData.some(
(consumer) =>
getName(consumer) === d.metric[metric] &&
(model.namespaced ? getNamespace(consumer) === d.metric.namespace : true),
);
if (consumerExists) {
top5Data.push({ ...d, y: humanize(d.y).string });
}
if (top5Data.length === 5) {
break;
}
if (k8sResource && consumerLoaded && !consumersLoadError) {
for (const d of bodyData) {
const consumerExists = consumerData.some(
(consumer) =>
getName(consumer) === d.metric[metric] &&
(model.namespaced ? getNamespace(consumer) === d.metric.namespace : true),
);
if (consumerExists) {
top5Data.push({ ...d, y: humanize(d.y).string });
}
if (top5Data.length === 5) {
break;
}
}
}

const monitoringParams = useMemo(() => {
const params = new URLSearchParams();
params.set('query0', currentConsumer.query);
if (namespace) {
params.set('namespace', namespace);
}
return params;
}, [currentConsumer.query, namespace]);
const monitoringParams = useMemo(() => {
const params = new URLSearchParams();
params.set('query0', currentConsumer.query);
if (namespace) {
params.set('namespace', namespace);
}
return params;
}, [currentConsumer.query, namespace]);

const dropdownItems = useMemo(
() =>
consumers.reduce((items, curr) => {
items[referenceForModel(curr.model)] = t('console-shared~By {{label}}', {
label: curr.model.labelKey ? t(curr.model.labelKey) : curr.model.label,
});
return items;
}, {}),
[consumers, t],
);
const dropdownItems = useMemo(
() =>
consumers.reduce((items, curr) => {
items[referenceForModel(curr.model)] = t('console-shared~By {{label}}', {
label: curr.model.labelKey ? t(curr.model.labelKey) : curr.model.label,
});
return items;
}, {}),
[consumers, t],
);

const onDropdownChange = useCallback(
(key) => setCurrentConsumer(consumers.find((c) => referenceForModel(c.model) === key)),
[consumers],
);
const onDropdownChange = useCallback(
(key) => setCurrentConsumer(consumers.find((c) => referenceForModel(c.model) === key)),
[consumers],
);

const monitoringURL = `/monitoring/query-browser?${monitoringParams.toString()}`;
const monitoringURL = `/monitoring/query-browser?${monitoringParams.toString()}`;

let body: ReactNode;
if (error || consumersLoadError) {
body = <div className="pf-v6-u-text-color-subtle">{t('console-shared~Not available')}</div>;
} else if (!consumerLoaded || !data) {
body = (
<ul className="co-utilization-card-popover__consumer-list">
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
let body: ReactNode;
if (error || consumersLoadError) {
body = <div className="pf-v6-u-text-color-subtle">{t('console-shared~Not available')}</div>;
} else if (!consumerLoaded || !data) {
body = (
<ul className="co-utilization-card-popover__consumer-list">
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
<li className="skeleton-consumer" />
</ul>
);
} else {
body = (
<>
<ul
className="co-utilization-card-popover__consumer-list"
aria-label={t('console-shared~Top consumer by {{label}}', { label: model.label })}
>
{top5Data &&
top5Data.map((item) => {
const title = String(item.x);
return (
<ListItem key={title} value={item.y}>
<Link
className="co-utilization-card-popover__consumer-name"
to={resourcePathFromModel(model, title, item.metric.namespace)}
>
{title}
</Link>
</ListItem>
);
})}
</ul>
);
} else {
body = (
<>
<ul
className="co-utilization-card-popover__consumer-list"
aria-label={t('console-shared~Top consumer by {{label}}', { label: model.label })}
>
{top5Data &&
top5Data.map((item) => {
const title = String(item.x);
return (
<ListItem key={title} value={item.y}>
<Link
className="co-utilization-card-popover__consumer-name"
to={resourcePathFromModel(model, title, item.metric.namespace)}
>
{title}
</Link>
</ListItem>
);
})}
</ul>
<Link to={monitoringURL}>{t('console-shared~View more')}</Link>
</>
);
}
<Link to={monitoringURL}>{t('console-shared~View more')}</Link>
</>
);
}

return (
<div className="co-utilization-card-popover__body">
{description && (
<div className="co-utilization-card-popover__description">{description}</div>
)}
{children}
<div className="co-utilization-card-popover__title">
{consumers.length === 1
? t('console-shared~Top {{label}} consumers', {
label: currentConsumer.model.label.toLowerCase(),
})
: t('console-shared~Top consumers')}
</div>
{consumers.length > 1 && (
<ConsoleSelect
id="consumer-select"
renderInline // needed for popover to not close on selection
isFullWidth
buttonClassName="pf-v6-u-my-sm"
aria-label={t('console-shared~Select consumer type')}
items={dropdownItems}
onChange={onDropdownChange}
selectedKey={referenceForModel(model)}
/>
)}
{body}
return (
<div className="co-utilization-card-popover__body">
{description && (
<div className="co-utilization-card-popover__description">{description}</div>
)}
{children}
<div className="co-utilization-card-popover__title">
{consumers.length === 1
? t('console-shared~Top {{label}} consumers', {
label: currentConsumer.model.label.toLowerCase(),
})
: t('console-shared~Top consumers')}
</div>
);
},
),
{consumers.length > 1 && (
<ConsoleSelect
id="consumer-select"
renderInline // needed for popover to not close on selection
isFullWidth
buttonClassName="pf-v6-u-my-sm"
aria-label={t('console-shared~Select consumer type')}
items={dropdownItems}
onChange={onDropdownChange}
selectedKey={referenceForModel(model)}
/>
)}
{body}
</div>
);
},
);

const ListItem: FC<ListItemProps> = ({ children, value }) => (
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/console-shared/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export * from './useCopyCodeModal';
export * from './useCopyLoginCommands';
export * from './useQuickStartContext';
export * from './useUser';
export * from './useDynamicK8sWatchResources';
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const useDashboardResources: UseDashboardResources = ({
prometheusQueries?.forEach((query) =>
dispatch(watchPrometheusQuery(query.query, null, query.timespan)),
);
urls?.forEach((url) => dispatch(watchURL(url?.url)));
urls?.forEach((url) => dispatch(watchURL(url?.url, url?.fetch)));

return () => {
prometheusQueries?.forEach((query) => {
Expand Down
Loading