|
1 | 1 | /* eslint-disable @typescript-eslint/no-use-before-define */ |
2 | 2 | import type { FC, ReactNode, ReactText } from 'react'; |
3 | | -import { memo, useState, useCallback, useMemo, useEffect } from 'react'; |
| 3 | +import { memo, useState, useCallback, useMemo } from 'react'; |
4 | 4 | import { Button, Popover, PopoverPosition } from '@patternfly/react-core'; |
5 | 5 | import { useTranslation } from 'react-i18next'; |
6 | 6 | import { Link } from 'react-router-dom-v5-compat'; |
7 | 7 | import { LIMIT_STATE, Humanize } from '@console/dynamic-plugin-sdk'; |
8 | 8 | import { getPrometheusQueryResponse } from '@console/internal/actions/dashboards'; |
9 | | -import { |
10 | | - withDashboardResources, |
11 | | - DashboardItemProps, |
12 | | -} from '@console/internal/components/dashboard/with-dashboard-resources'; |
13 | 9 | import { DataPoint } from '@console/internal/components/graphs'; |
14 | 10 | import { getInstantVectorStats } from '@console/internal/components/graphs/utils'; |
15 | 11 | import { ConsoleSelect } from '@console/internal/components/utils/console-select'; |
16 | 12 | import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; |
17 | 13 | import { resourcePathFromModel } from '@console/internal/components/utils/resource-link'; |
18 | 14 | import { K8sKind, referenceForModel, K8sResourceCommon } from '@console/internal/module/k8s'; |
19 | 15 | import { getName, getNamespace } from '../../..'; |
| 16 | +import { useDashboardResources } from '../../../hooks/useDashboardResources'; |
20 | 17 | import { RedExclamationCircleIcon, YellowExclamationTriangleIcon } from '../../status'; |
21 | 18 | import Status from '../status-card/StatusPopup'; |
22 | 19 |
|
@@ -116,157 +113,146 @@ export const LimitsBody: FC<LimitsBodyProps> = ({ |
116 | 113 | ); |
117 | 114 | }; |
118 | 115 |
|
119 | | -export const PopoverBody = withDashboardResources<DashboardItemProps & PopoverBodyProps>( |
120 | | - memo( |
121 | | - ({ |
122 | | - humanize, |
123 | | - consumers, |
| 116 | +export const PopoverBody: FC<PopoverBodyProps> = memo( |
| 117 | + ({ humanize, consumers, namespace, isOpen, description, children }) => { |
| 118 | + const { t } = useTranslation(); |
| 119 | + const [currentConsumer, setCurrentConsumer] = useState(consumers[0]); |
| 120 | + const { query, model, metric, fieldSelector } = currentConsumer; |
| 121 | + const k8sResource = useMemo( |
| 122 | + () => (isOpen ? getResourceToWatch(model, namespace, fieldSelector) : null), |
| 123 | + [fieldSelector, isOpen, model, namespace], |
| 124 | + ); |
| 125 | + const [consumerData, consumerLoaded, consumersLoadError] = useK8sWatchResource< |
| 126 | + K8sResourceCommon[] |
| 127 | + >(k8sResource); |
| 128 | + |
| 129 | + const prometheusQueries = useMemo(() => (isOpen ? [{ query, namespace }] : []), [ |
| 130 | + query, |
124 | 131 | namespace, |
125 | | - watchPrometheus, |
126 | | - stopWatchPrometheusQuery, |
127 | | - prometheusResults, |
128 | 132 | isOpen, |
129 | | - description, |
130 | | - children, |
131 | | - }) => { |
132 | | - const { t } = useTranslation(); |
133 | | - const [currentConsumer, setCurrentConsumer] = useState(consumers[0]); |
134 | | - const { query, model, metric, fieldSelector } = currentConsumer; |
135 | | - const k8sResource = useMemo( |
136 | | - () => (isOpen ? getResourceToWatch(model, namespace, fieldSelector) : null), |
137 | | - [fieldSelector, isOpen, model, namespace], |
138 | | - ); |
139 | | - const [consumerData, consumerLoaded, consumersLoadError] = useK8sWatchResource< |
140 | | - K8sResourceCommon[] |
141 | | - >(k8sResource); |
142 | | - useEffect(() => { |
143 | | - if (!isOpen) { |
144 | | - return () => {}; |
145 | | - } |
146 | | - watchPrometheus(query, namespace); |
147 | | - return () => { |
148 | | - stopWatchPrometheusQuery(query); |
149 | | - }; |
150 | | - }, [query, stopWatchPrometheusQuery, watchPrometheus, namespace, isOpen]); |
| 133 | + ]); |
| 134 | + |
| 135 | + const { prometheusResults } = useDashboardResources({ |
| 136 | + prometheusQueries, |
| 137 | + }); |
151 | 138 |
|
152 | | - const top5Data = []; |
| 139 | + const top5Data = []; |
153 | 140 |
|
154 | | - const [data, error] = getPrometheusQueryResponse(prometheusResults, query); |
155 | | - const bodyData = getInstantVectorStats(data, metric); |
| 141 | + const [data, error] = getPrometheusQueryResponse(prometheusResults, query); |
| 142 | + const bodyData = getInstantVectorStats(data, metric); |
156 | 143 |
|
157 | | - if (k8sResource && consumerLoaded && !consumersLoadError) { |
158 | | - for (const d of bodyData) { |
159 | | - const consumerExists = consumerData.some( |
160 | | - (consumer) => |
161 | | - getName(consumer) === d.metric[metric] && |
162 | | - (model.namespaced ? getNamespace(consumer) === d.metric.namespace : true), |
163 | | - ); |
164 | | - if (consumerExists) { |
165 | | - top5Data.push({ ...d, y: humanize(d.y).string }); |
166 | | - } |
167 | | - if (top5Data.length === 5) { |
168 | | - break; |
169 | | - } |
| 144 | + if (k8sResource && consumerLoaded && !consumersLoadError) { |
| 145 | + for (const d of bodyData) { |
| 146 | + const consumerExists = consumerData.some( |
| 147 | + (consumer) => |
| 148 | + getName(consumer) === d.metric[metric] && |
| 149 | + (model.namespaced ? getNamespace(consumer) === d.metric.namespace : true), |
| 150 | + ); |
| 151 | + if (consumerExists) { |
| 152 | + top5Data.push({ ...d, y: humanize(d.y).string }); |
| 153 | + } |
| 154 | + if (top5Data.length === 5) { |
| 155 | + break; |
170 | 156 | } |
171 | 157 | } |
| 158 | + } |
172 | 159 |
|
173 | | - const monitoringParams = useMemo(() => { |
174 | | - const params = new URLSearchParams(); |
175 | | - params.set('query0', currentConsumer.query); |
176 | | - if (namespace) { |
177 | | - params.set('namespace', namespace); |
178 | | - } |
179 | | - return params; |
180 | | - }, [currentConsumer.query, namespace]); |
| 160 | + const monitoringParams = useMemo(() => { |
| 161 | + const params = new URLSearchParams(); |
| 162 | + params.set('query0', currentConsumer.query); |
| 163 | + if (namespace) { |
| 164 | + params.set('namespace', namespace); |
| 165 | + } |
| 166 | + return params; |
| 167 | + }, [currentConsumer.query, namespace]); |
181 | 168 |
|
182 | | - const dropdownItems = useMemo( |
183 | | - () => |
184 | | - consumers.reduce((items, curr) => { |
185 | | - items[referenceForModel(curr.model)] = t('console-shared~By {{label}}', { |
186 | | - label: curr.model.labelKey ? t(curr.model.labelKey) : curr.model.label, |
187 | | - }); |
188 | | - return items; |
189 | | - }, {}), |
190 | | - [consumers, t], |
191 | | - ); |
| 169 | + const dropdownItems = useMemo( |
| 170 | + () => |
| 171 | + consumers.reduce((items, curr) => { |
| 172 | + items[referenceForModel(curr.model)] = t('console-shared~By {{label}}', { |
| 173 | + label: curr.model.labelKey ? t(curr.model.labelKey) : curr.model.label, |
| 174 | + }); |
| 175 | + return items; |
| 176 | + }, {}), |
| 177 | + [consumers, t], |
| 178 | + ); |
192 | 179 |
|
193 | | - const onDropdownChange = useCallback( |
194 | | - (key) => setCurrentConsumer(consumers.find((c) => referenceForModel(c.model) === key)), |
195 | | - [consumers], |
196 | | - ); |
| 180 | + const onDropdownChange = useCallback( |
| 181 | + (key) => setCurrentConsumer(consumers.find((c) => referenceForModel(c.model) === key)), |
| 182 | + [consumers], |
| 183 | + ); |
197 | 184 |
|
198 | | - const monitoringURL = `/monitoring/query-browser?${monitoringParams.toString()}`; |
| 185 | + const monitoringURL = `/monitoring/query-browser?${monitoringParams.toString()}`; |
199 | 186 |
|
200 | | - let body: ReactNode; |
201 | | - if (error || consumersLoadError) { |
202 | | - body = <div className="pf-v6-u-text-color-subtle">{t('console-shared~Not available')}</div>; |
203 | | - } else if (!consumerLoaded || !data) { |
204 | | - body = ( |
205 | | - <ul className="co-utilization-card-popover__consumer-list"> |
206 | | - <li className="skeleton-consumer" /> |
207 | | - <li className="skeleton-consumer" /> |
208 | | - <li className="skeleton-consumer" /> |
209 | | - <li className="skeleton-consumer" /> |
210 | | - <li className="skeleton-consumer" /> |
| 187 | + let body: ReactNode; |
| 188 | + if (error || consumersLoadError) { |
| 189 | + body = <div className="pf-v6-u-text-color-subtle">{t('console-shared~Not available')}</div>; |
| 190 | + } else if (!consumerLoaded || !data) { |
| 191 | + body = ( |
| 192 | + <ul className="co-utilization-card-popover__consumer-list"> |
| 193 | + <li className="skeleton-consumer" /> |
| 194 | + <li className="skeleton-consumer" /> |
| 195 | + <li className="skeleton-consumer" /> |
| 196 | + <li className="skeleton-consumer" /> |
| 197 | + <li className="skeleton-consumer" /> |
| 198 | + </ul> |
| 199 | + ); |
| 200 | + } else { |
| 201 | + body = ( |
| 202 | + <> |
| 203 | + <ul |
| 204 | + className="co-utilization-card-popover__consumer-list" |
| 205 | + aria-label={t('console-shared~Top consumer by {{label}}', { label: model.label })} |
| 206 | + > |
| 207 | + {top5Data && |
| 208 | + top5Data.map((item) => { |
| 209 | + const title = String(item.x); |
| 210 | + return ( |
| 211 | + <ListItem key={title} value={item.y}> |
| 212 | + <Link |
| 213 | + className="co-utilization-card-popover__consumer-name" |
| 214 | + to={resourcePathFromModel(model, title, item.metric.namespace)} |
| 215 | + > |
| 216 | + {title} |
| 217 | + </Link> |
| 218 | + </ListItem> |
| 219 | + ); |
| 220 | + })} |
211 | 221 | </ul> |
212 | | - ); |
213 | | - } else { |
214 | | - body = ( |
215 | | - <> |
216 | | - <ul |
217 | | - className="co-utilization-card-popover__consumer-list" |
218 | | - aria-label={t('console-shared~Top consumer by {{label}}', { label: model.label })} |
219 | | - > |
220 | | - {top5Data && |
221 | | - top5Data.map((item) => { |
222 | | - const title = String(item.x); |
223 | | - return ( |
224 | | - <ListItem key={title} value={item.y}> |
225 | | - <Link |
226 | | - className="co-utilization-card-popover__consumer-name" |
227 | | - to={resourcePathFromModel(model, title, item.metric.namespace)} |
228 | | - > |
229 | | - {title} |
230 | | - </Link> |
231 | | - </ListItem> |
232 | | - ); |
233 | | - })} |
234 | | - </ul> |
235 | | - <Link to={monitoringURL}>{t('console-shared~View more')}</Link> |
236 | | - </> |
237 | | - ); |
238 | | - } |
| 222 | + <Link to={monitoringURL}>{t('console-shared~View more')}</Link> |
| 223 | + </> |
| 224 | + ); |
| 225 | + } |
239 | 226 |
|
240 | | - return ( |
241 | | - <div className="co-utilization-card-popover__body"> |
242 | | - {description && ( |
243 | | - <div className="co-utilization-card-popover__description">{description}</div> |
244 | | - )} |
245 | | - {children} |
246 | | - <div className="co-utilization-card-popover__title"> |
247 | | - {consumers.length === 1 |
248 | | - ? t('console-shared~Top {{label}} consumers', { |
249 | | - label: currentConsumer.model.label.toLowerCase(), |
250 | | - }) |
251 | | - : t('console-shared~Top consumers')} |
252 | | - </div> |
253 | | - {consumers.length > 1 && ( |
254 | | - <ConsoleSelect |
255 | | - id="consumer-select" |
256 | | - renderInline // needed for popover to not close on selection |
257 | | - isFullWidth |
258 | | - buttonClassName="pf-v6-u-my-sm" |
259 | | - aria-label={t('console-shared~Select consumer type')} |
260 | | - items={dropdownItems} |
261 | | - onChange={onDropdownChange} |
262 | | - selectedKey={referenceForModel(model)} |
263 | | - /> |
264 | | - )} |
265 | | - {body} |
| 227 | + return ( |
| 228 | + <div className="co-utilization-card-popover__body"> |
| 229 | + {description && ( |
| 230 | + <div className="co-utilization-card-popover__description">{description}</div> |
| 231 | + )} |
| 232 | + {children} |
| 233 | + <div className="co-utilization-card-popover__title"> |
| 234 | + {consumers.length === 1 |
| 235 | + ? t('console-shared~Top {{label}} consumers', { |
| 236 | + label: currentConsumer.model.label.toLowerCase(), |
| 237 | + }) |
| 238 | + : t('console-shared~Top consumers')} |
266 | 239 | </div> |
267 | | - ); |
268 | | - }, |
269 | | - ), |
| 240 | + {consumers.length > 1 && ( |
| 241 | + <ConsoleSelect |
| 242 | + id="consumer-select" |
| 243 | + renderInline // needed for popover to not close on selection |
| 244 | + isFullWidth |
| 245 | + buttonClassName="pf-v6-u-my-sm" |
| 246 | + aria-label={t('console-shared~Select consumer type')} |
| 247 | + items={dropdownItems} |
| 248 | + onChange={onDropdownChange} |
| 249 | + selectedKey={referenceForModel(model)} |
| 250 | + /> |
| 251 | + )} |
| 252 | + {body} |
| 253 | + </div> |
| 254 | + ); |
| 255 | + }, |
270 | 256 | ); |
271 | 257 |
|
272 | 258 | const ListItem: React.FCC<ListItemProps> = ({ children, value }) => ( |
|
0 commit comments