= ({ alerts, ruleID }) => {
|
{alertDescription(a)}
@@ -126,7 +128,7 @@ export const ActiveAlerts: FC = ({ alerts, ruleID }) => {
navigate(getNewSilenceAlertUrl(perspective, a))}
+ onClick={() => navigate(getNewSilenceAlertUrl(perspective, a, namespace))}
>
{t('Silence alert')}
,
@@ -141,7 +143,8 @@ export const ActiveAlerts: FC = ({ alerts, ruleID }) => {
};
const AlertRulesDetailsPage_: FC = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
- const params = useParams<{ ns?: string; id: string }>();
+ const params = useParams<{ id: string }>();
+ const { namespace } = useMonitoringNamespace();
const { rules, rulesAlertLoading } = useAlerts();
@@ -184,7 +187,10 @@ const AlertRulesDetailsPage_: FC = () => {
-
+
{t('Alerting rules')}
@@ -310,6 +316,7 @@ const AlertRulesDetailsPage_: FC = () => {
to={getQueryBrowserUrl({
perspective: perspective,
query: rule?.query,
+ namespace,
})}
>
diff --git a/web/src/components/alerting/AlertRulesPage.tsx b/web/src/components/alerting/AlertRulesPage.tsx
index 2e6903d2d..281579d6c 100644
--- a/web/src/components/alerting/AlertRulesPage.tsx
+++ b/web/src/components/alerting/AlertRulesPage.tsx
@@ -41,6 +41,7 @@ import { severityRowFilter } from './AlertUtils';
import { MonitoringProvider } from '../../contexts/MonitoringContext';
import { DataTestIDs } from '../data-test';
import { useAlerts } from '../../hooks/useAlerts';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
const StateCounts: FC<{ alerts: PrometheusAlert[] }> = ({ alerts }) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
@@ -85,6 +86,7 @@ const alertStateFilter = (t): RowFilter => ({
const RuleTableRow: FC> = ({ obj }) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const title: string = obj.annotations?.description || obj.annotations?.message;
@@ -97,7 +99,7 @@ const RuleTableRow: FC> = ({ obj }) => {
diff --git a/web/src/components/alerting/AlertUtils.tsx b/web/src/components/alerting/AlertUtils.tsx
index 13a213fb0..16e767a2b 100644
--- a/web/src/components/alerting/AlertUtils.tsx
+++ b/web/src/components/alerting/AlertUtils.tsx
@@ -1,5 +1,3 @@
-import type { FC, ReactNode } from 'react';
-import { memo } from 'react';
import {
Action,
Alert,
@@ -15,18 +13,15 @@ import {
SilenceStates,
Timestamp,
} from '@openshift-console/dynamic-plugin-sdk';
-import { AlertSource } from '../types';
-import * as _ from 'lodash-es';
-import { useTranslation } from 'react-i18next';
import {
- Alert as PFAlert,
- Popover,
Button,
DescriptionList,
+ DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
- DescriptionListDescription,
Label,
+ Alert as PFAlert,
+ Popover,
Tooltip,
} from '@patternfly/react-core';
import {
@@ -38,11 +33,6 @@ import {
OutlinedBellIcon,
SeverityUndefinedIcon,
} from '@patternfly/react-icons';
-import { FormatSeriesTitle, QueryBrowser } from '../query-browser';
-import { Link } from 'react-router-dom-v5-compat';
-import { TFunction } from 'i18next';
-import { getQueryBrowserUrl, usePerspective } from '../hooks/usePerspective';
-import { NamespaceModel } from '../console/models';
import {
t_global_border_color_status_info_default,
t_global_color_status_danger_default,
@@ -53,6 +43,17 @@ import {
t_global_text_color_disabled,
t_global_text_color_subtle,
} from '@patternfly/react-tokens';
+import { TFunction } from 'i18next';
+import * as _ from 'lodash-es';
+import type { FC, ReactNode } from 'react';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Link } from 'react-router-dom-v5-compat';
+import { NamespaceModel } from '../console/models';
+import { getQueryBrowserUrl, usePerspective } from '../hooks/usePerspective';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
+import { FormatSeriesTitle, QueryBrowser } from '../query-browser';
+import { AlertSource } from '../types';
export const getAdditionalSources = (
data: Array,
@@ -247,13 +248,14 @@ export const Graph: FC = ({
}) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
// 3 times the rule's duration, but not less than 30 minutes
const timespan = Math.max(3 * ruleDuration, 30 * 60) * 1000;
const GraphLink = () =>
query && perspective !== 'acm' ? (
-
+
{t('Inspect')}
) : null;
diff --git a/web/src/components/alerting/AlertingPage.tsx b/web/src/components/alerting/AlertingPage.tsx
index a5a68321a..c9b2b0a39 100644
--- a/web/src/components/alerting/AlertingPage.tsx
+++ b/web/src/components/alerting/AlertingPage.tsx
@@ -9,11 +9,11 @@ import {
} from '@openshift-console/dynamic-plugin-sdk';
import { MonitoringProvider } from '../../contexts/MonitoringContext';
import { useMonitoring } from '../../hooks/useMonitoring';
-import { useLocation } from 'react-router-dom';
+import { useLocation } from 'react-router-dom-v5-compat';
import { AlertResource, RuleResource, SilenceResource } from '../utils';
import { useDispatch } from 'react-redux';
import { alertingClearSelectorData } from '../../store/actions';
-import { useQueryNamespace } from '../hooks/useQueryNamespace';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
const CmoAlertsPage = lazy(() =>
import(/* webpackChunkName: "CmoAlertsPage" */ './AlertsPage').then((module) => ({
@@ -58,10 +58,8 @@ const namespacedPages = [
const AlertingPage: FC = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const dispatch = useDispatch();
- const { useAlertsTenancy, accessCheckLoading } = useMonitoring();
-
const [perspective] = useActivePerspective();
- const { setNamespace } = useQueryNamespace();
+ const { setNamespace } = useMonitoringNamespace();
const { plugin, prometheus } = useMonitoring();
@@ -97,7 +95,7 @@ const AlertingPage: FC = () => {
return (
<>
- {namespacedPages.includes(pathname) && !accessCheckLoading && useAlertsTenancy && (
+ {namespacedPages.includes(pathname) && (
{
dispatch(alertingClearSelectorData(prometheus, namespace));
diff --git a/web/src/components/alerting/AlertsDetailsPage.tsx b/web/src/components/alerting/AlertsDetailsPage.tsx
index 682fa5d76..1db080929 100644
--- a/web/src/components/alerting/AlertsDetailsPage.tsx
+++ b/web/src/components/alerting/AlertsDetailsPage.tsx
@@ -26,6 +26,7 @@ import {
getRuleUrl,
usePerspective,
} from '../hooks/usePerspective';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
import { AlertResource, alertState, RuleResource } from '../utils';
import { MonitoringProvider } from '../../contexts/MonitoringContext';
@@ -95,6 +96,7 @@ const AlertsDetailsPage_: FC = () => {
const params = useParams<{ ruleID: string }>();
const navigate = useNavigate();
const { plugin } = useMonitoring();
+ const { namespace } = useMonitoringNamespace();
const { perspective } = usePerspective();
@@ -156,7 +158,7 @@ const AlertsDetailsPage_: FC = () => {
-
+
{t('Alerts')}
@@ -190,7 +192,7 @@ const AlertsDetailsPage_: FC = () => {
{state !== AlertStates.Silenced && (
{_.get(rule, 'name')}
diff --git a/web/src/components/alerting/AlertsPage.tsx b/web/src/components/alerting/AlertsPage.tsx
index a21cdee9a..416a33907 100644
--- a/web/src/components/alerting/AlertsPage.tsx
+++ b/web/src/components/alerting/AlertsPage.tsx
@@ -4,13 +4,12 @@ import {
DocumentTitle,
ListPageFilter,
RowFilter,
- useActiveNamespace,
useListPageFilter,
} from '@openshift-console/dynamic-plugin-sdk';
import { Flex, PageSection } from '@patternfly/react-core';
import { Table, TableGridBreakpoint, Th, Thead, Tr } from '@patternfly/react-table';
import * as _ from 'lodash-es';
-import type { FC } from 'react';
+import { type FC } from 'react';
import { useTranslation } from 'react-i18next';
import withFallback from '../console/console-shared/error/fallbacks/withFallback';
import { EmptyBox } from '../console/console-shared/src/components/empty-state/EmptyBox';
@@ -33,11 +32,12 @@ import { MonitoringProvider } from '../../contexts/MonitoringContext';
import { useAlerts } from '../../hooks/useAlerts';
import { AccessDenied } from '../console/console-shared/src/components/empty-state/AccessDenied';
import { useMonitoring } from '../../hooks/useMonitoring';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
const AlertsPage_: FC = () => {
const { useAlertsTenancy } = useMonitoring();
const { t } = useTranslation(process.env.I18N_NAMESPACE);
- const [namespace] = useActiveNamespace();
+ const { namespace } = useMonitoringNamespace();
const { defaultAlertTenant, perspective } = usePerspective();
const { alerts, additionalAlertSourceLabels, alertClusterLabels, rulesAlertLoading, silences } =
@@ -110,7 +110,20 @@ const AlertsPage_: FC = () => {
rowFilters = rowFilters.filter((filter) => filter.type !== 'alert-source');
}
- const [staticData, filteredData, onFilterChange] = useListPageFilter(alerts, rowFilters);
+ /**
+ * Filters alerts based on tenancy:
+ * - with tenancy: alerts are automatically pre-filtered.
+ * - without tenancy (admin): filters by selected namespace for UX consistency.
+ * - "All Projects": returns all alerts, including those without a namespace label.
+ */
+ const namespacedAlerts =
+ useAlertsTenancy || ALL_NAMESPACES_KEY === namespace
+ ? alerts
+ : alerts?.filter((a) => a.labels?.namespace === namespace);
+ const [staticData, filteredData, onFilterChange] = useListPageFilter(
+ namespacedAlerts,
+ rowFilters,
+ );
const columns = useAggregateAlertColumns();
const selectedFilters = useSelectedFilters();
diff --git a/web/src/components/alerting/SilenceCreatePage.tsx b/web/src/components/alerting/SilenceCreatePage.tsx
index 4b1999727..8058ff469 100644
--- a/web/src/components/alerting/SilenceCreatePage.tsx
+++ b/web/src/components/alerting/SilenceCreatePage.tsx
@@ -5,14 +5,14 @@ import { SilenceForm } from './SilenceForm';
import { MonitoringProvider } from '../../contexts/MonitoringContext';
import { useMonitoring } from '../../hooks/useMonitoring';
import { LoadingBox } from '../console/console-shared/src/components/loading/LoadingBox';
-import { useQueryNamespace } from '../hooks/useQueryNamespace';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
const CreateSilencePage = () => {
const { accessCheckLoading, useAlertsTenancy } = useMonitoring();
const { t } = useTranslation(process.env.I18N_NAMESPACE);
// Set the activeNamespace to be the namespace query parameter if it is set
- useQueryNamespace();
+ useMonitoringNamespace();
const matchers = _.map(getAllQueryArguments(), (value, name) => ({
name,
diff --git a/web/src/components/alerting/SilenceForm.tsx b/web/src/components/alerting/SilenceForm.tsx
index 469157098..19330b227 100644
--- a/web/src/components/alerting/SilenceForm.tsx
+++ b/web/src/components/alerting/SilenceForm.tsx
@@ -1,8 +1,4 @@
-import {
- consoleFetchJSON,
- DocumentTitle,
- useActiveNamespace,
-} from '@openshift-console/dynamic-plugin-sdk';
+import { consoleFetchJSON, DocumentTitle } from '@openshift-console/dynamic-plugin-sdk';
import {
ActionGroup,
Alert,
@@ -49,6 +45,7 @@ import {
import { ExternalLink } from '../console/utils/link';
import { useBoolean } from '../hooks/useBoolean';
import { getSilenceAlertUrl, usePerspective } from '../hooks/usePerspective';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
import { DataTestIDs } from '../data-test';
import { ALL_NAMESPACES_KEY, getAlertmanagerSilencesUrl } from '../utils';
import { useAlerts } from '../../hooks/useAlerts';
@@ -132,7 +129,7 @@ const NegativeMatcherHelp = () => {
const SilenceForm_: FC = ({ defaults, Info, title, isNamespaced }) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
- const [namespace] = useActiveNamespace();
+ const { namespace } = useMonitoringNamespace();
const { prometheus } = useMonitoring();
const navigate = useNavigate();
const isPageNamespaceLocked = isNamespaced && namespace !== ALL_NAMESPACES_KEY;
@@ -287,7 +284,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace
.then(({ silenceID }) => {
setError(undefined);
refetchSilencesAndAlerts();
- navigate(getSilenceAlertUrl(perspective, silenceID));
+ navigate(getSilenceAlertUrl(perspective, silenceID, namespace));
})
.catch((err) => {
let errorMessage =
diff --git a/web/src/components/alerting/SilencesDetailsPage.tsx b/web/src/components/alerting/SilencesDetailsPage.tsx
index 1aa731792..d265077c0 100644
--- a/web/src/components/alerting/SilencesDetailsPage.tsx
+++ b/web/src/components/alerting/SilencesDetailsPage.tsx
@@ -29,6 +29,7 @@ import {
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { getAlertUrl, getRuleUrl, getSilencesUrl, usePerspective } from '../hooks/usePerspective';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
import KebabDropdown from '../kebab-dropdown';
import { alertDescription, SilenceResource } from '../utils';
import { SeverityBadge, SeverityCounts } from './AlertUtils';
@@ -46,6 +47,7 @@ const SilencesDetailsPage_: FC = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const params = useParams<{ id: string }>();
+ const { namespace } = useMonitoringNamespace();
const id = params.id;
@@ -70,7 +72,10 @@ const SilencesDetailsPage_: FC = () => {
-
+
{t('Silences')}
@@ -213,6 +218,7 @@ const SilencedAlertsList: FC = ({ alerts }) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const navigate = useNavigate();
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
return _.isEmpty(alerts) ? (
{t('No Alerts found')}
@@ -230,7 +236,7 @@ const SilencedAlertsList: FC = ({ alerts }) => {
{a.labels.alertname}
@@ -244,7 +250,7 @@ const SilencedAlertsList: FC = ({ alerts }) => {
dropdownItems={[
navigate(getRuleUrl(perspective, a.rule))}
+ onClick={() => navigate(getRuleUrl(perspective, a.rule, namespace))}
>
{t('View alerting rule')}
,
diff --git a/web/src/components/alerting/SilencesPage.tsx b/web/src/components/alerting/SilencesPage.tsx
index 5e6080f72..a0b773bf5 100644
--- a/web/src/components/alerting/SilencesPage.tsx
+++ b/web/src/components/alerting/SilencesPage.tsx
@@ -29,15 +29,16 @@ import withFallback from '../console/console-shared/error/fallbacks/withFallback
import { EmptyBox } from '../console/console-shared/src/components/empty-state/EmptyBox';
import { useBoolean } from '../hooks/useBoolean';
import { getFetchSilenceUrl, getNewSilenceUrl, usePerspective } from '../hooks/usePerspective';
-import { fuzzyCaseInsensitive, silenceCluster, silenceState } from '../utils';
+import { ALL_NAMESPACES_KEY, fuzzyCaseInsensitive, silenceCluster, silenceState } from '../utils';
import { SelectedSilencesContext, SilenceTableRow } from './SilencesUtils';
import { MonitoringProvider } from '../../contexts/MonitoringContext';
import { DataTestIDs } from '../data-test';
import { useAlerts } from '../../hooks/useAlerts';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
const SilencesPage_: FC = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
-
+ const { namespace } = useMonitoringNamespace();
const { perspective } = usePerspective();
const [selectedSilences, setSelectedSilences] = useState(new Set());
@@ -98,7 +99,20 @@ const SilencesPage_: FC = () => {
return filters;
}, [perspective, t, silenceClusterLabels]);
- const [staticData, filteredData, onFilterChange] = useListPageFilter(silences?.data, rowFilters);
+ /**
+ * Filters silences based on the selected namespace.
+ * "All Projects": returns all silences, including those without a namespace matcher.
+ */
+ const namespacedSilences =
+ ALL_NAMESPACES_KEY === namespace
+ ? silences?.data
+ : silences?.data?.filter((s) =>
+ s.matchers.some((m) => m.name === 'namespace' && m.value === namespace),
+ );
+ const [staticData, filteredData, onFilterChange] = useListPageFilter(
+ namespacedSilences,
+ rowFilters,
+ );
const columns = useMemo>>(() => {
const cols: Array> = [
@@ -273,6 +287,7 @@ const ExpireAllSilencesButton: FC = ({ setErrorMes
const { trigger: refetchSilencesAndAlerts } = useAlerts();
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const [isInProgress, , setInProgress, setNotInProgress] = useBoolean(false);
@@ -282,7 +297,7 @@ const ExpireAllSilencesButton: FC = ({ setErrorMes
setInProgress();
Promise.allSettled(
[...selectedSilences].map((silenceID: string) =>
- consoleFetchJSON.delete(getFetchSilenceUrl(perspective, silenceID)),
+ consoleFetchJSON.delete(getFetchSilenceUrl(perspective, silenceID, namespace)),
),
).then((values) => {
setNotInProgress();
@@ -320,9 +335,10 @@ const SilenceTableRowWithCheckbox: FC> = ({ obj }) => (
const CreateSilenceButton: FC = memo(() => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
return (
-
+
diff --git a/web/src/components/alerting/SilencesUtils.tsx b/web/src/components/alerting/SilencesUtils.tsx
index b42661b3d..fb39d64ff 100644
--- a/web/src/components/alerting/SilencesUtils.tsx
+++ b/web/src/components/alerting/SilencesUtils.tsx
@@ -49,6 +49,7 @@ import {
getSilenceAlertUrl,
usePerspective,
} from '../hooks/usePerspective';
+import { useMonitoringNamespace } from '../hooks/useMonitoringNamespace';
import { silenceMatcherEqualitySymbol, SilenceResource, silenceState } from '../utils';
import { SeverityCounts, StateTimestamp } from './AlertUtils';
import { DataTestIDs } from '../data-test';
@@ -56,6 +57,7 @@ import { DataTestIDs } from '../data-test';
export const SilenceTableRow: FC = ({ obj, showCheckbox }) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const { createdBy, endsAt, firingAlerts, id, name, startsAt, matchers } = obj;
const state = silenceState(obj);
@@ -105,7 +107,7 @@ export const SilenceTableRow: FC = ({ obj, showCheckbox })
{name}
@@ -204,12 +206,13 @@ export const SilenceDropdown: FC = ({ silence, toggleText
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
const navigate = useNavigate();
+ const { namespace } = useMonitoringNamespace();
const [isOpen, setIsOpen, , setClosed] = useBoolean(false);
const [isModalOpen, , setModalOpen, setModalClosed] = useBoolean(false);
const editSilence = () => {
- navigate(getEditSilenceAlertUrl(perspective, silence.id));
+ navigate(getEditSilenceAlertUrl(perspective, silence.id, namespace));
};
const dropdownItems =
@@ -278,6 +281,7 @@ export const ExpireSilenceModal: FC = ({
}) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const [isInProgress, , setInProgress, setNotInProgress] = useBoolean(false);
const [success, , setSuccess] = useBoolean(false);
@@ -285,7 +289,7 @@ export const ExpireSilenceModal: FC = ({
const expireSilence = () => {
setInProgress();
- const url = getFetchSilenceUrl(perspective, silenceID);
+ const url = getFetchSilenceUrl(perspective, silenceID, namespace);
consoleFetchJSON
.delete(url)
.then(() => {
diff --git a/web/src/components/console/graphs/promethues-graph.tsx b/web/src/components/console/graphs/promethues-graph.tsx
index d97e22ee5..29212a566 100644
--- a/web/src/components/console/graphs/promethues-graph.tsx
+++ b/web/src/components/console/graphs/promethues-graph.tsx
@@ -7,6 +7,7 @@ import { Link } from 'react-router-dom-v5-compat';
import { Title } from '@patternfly/react-core';
import { getMutlipleQueryBrowserUrl, usePerspective } from '../../hooks/usePerspective';
+import { useMonitoringNamespace } from '../../hooks/useMonitoringNamespace';
import { RootState } from '../../../store/store';
const getActiveNamespace = ({ UI }: RootState): string => UI.get('activeNamespace');
@@ -24,6 +25,7 @@ const PrometheusGraphLink_: FC = ({
ariaChartLinkLabel,
}) => {
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const queries = _.compact(_.castArray(query));
if (!queries.length) {
return <>{children}>;
@@ -31,7 +33,7 @@ const PrometheusGraphLink_: FC = ({
const params = new URLSearchParams();
queries.forEach((q, index) => params.set(`query${index}`, q));
- const url = getMutlipleQueryBrowserUrl(perspective, params);
+ const url = getMutlipleQueryBrowserUrl(perspective, params, namespace);
return (
{
const controller = useRef();
useEffect(() => {
@@ -9,5 +12,5 @@ export const useSafeFetch = () => {
}, []);
return (url: string): Promise =>
- consoleFetchJSON(url, 'get', { signal: controller.current.signal as AbortSignal });
+ consoleFetchJSON(url, 'GET', { signal: controller.current.signal as AbortSignal }, NO_TIMEOUT);
};
diff --git a/web/src/components/dashboards/legacy/graph.tsx b/web/src/components/dashboards/legacy/graph.tsx
index 414a20f9d..49a5e7585 100644
--- a/web/src/components/dashboards/legacy/graph.tsx
+++ b/web/src/components/dashboards/legacy/graph.tsx
@@ -15,6 +15,7 @@ type Props = {
customDataSource?: CustomDataSource;
formatSeriesTitle?: FormatSeriesTitle;
isStack: boolean;
+ onLoadingChange?: (isLoading: boolean) => void;
pollInterval: number;
queries: string[];
showLegend?: boolean;
@@ -27,6 +28,7 @@ const Graph: FC = ({
customDataSource,
formatSeriesTitle,
isStack,
+ onLoadingChange,
pollInterval,
queries,
showLegend,
@@ -60,6 +62,7 @@ const Graph: FC = ({
formatSeriesTitle={formatSeriesTitle}
hideControls
isStack={isStack}
+ onLoadingChange={onLoadingChange}
onZoom={onZoom}
pollInterval={pollInterval}
queries={queries}
diff --git a/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx b/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx
index c102b1eb5..7fa810db0 100644
--- a/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx
+++ b/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx
@@ -10,14 +10,17 @@ import ErrorAlert from './error';
import { DashboardSkeletonLegacy } from './dashboard-skeleton-legacy';
import { useLegacyDashboards } from './useLegacyDashboards';
import { MonitoringProvider } from '../../../contexts/MonitoringContext';
-import { useOpenshiftProject } from './useOpenshiftProject';
+import { useMonitoringNamespace } from '../../hooks/useMonitoringNamespace';
+import { useMonitoring } from '../../../hooks/useMonitoring';
+import { StringParam, useQueryParam } from 'use-query-params';
+import { QueryParams } from '../../../components/query-params';
type LegacyDashboardsPageProps = {
urlBoard: string;
};
const LegacyDashboardsPage_: FC = ({ urlBoard }) => {
- const { project, setProject } = useOpenshiftProject();
+ const { namespace, setNamespace } = useMonitoringNamespace();
const {
legacyDashboardsError,
legacyRows,
@@ -25,13 +28,14 @@ const LegacyDashboardsPage_: FC = ({ urlBoard }) => {
legacyDashboardsMetadata,
changeLegacyDashboard,
legacyDashboard,
- } = useLegacyDashboards(project, urlBoard);
+ } = useLegacyDashboards(namespace, urlBoard);
const { perspective } = usePerspective();
+ const { displayNamespaceSelector } = useMonitoring();
const { t } = useTranslation(process.env.I18N_NAMESPACE);
return (
<>
- setProject(namespace)} />
+ {displayNamespaceSelector && setNamespace(ns)} />}
{
);
};
+
+// Small wrapper to be able to use the query params provided by the monitoring provider
+const DashboardQueryWrapper = () => {
+ const [dashboard] = useQueryParam(QueryParams.Dashboard, StringParam);
+
+ return ;
+};
+
+export const MpCmoLegacyDevDashboardsPage: FC = () => {
+ return (
+
+
+
+ );
+};
diff --git a/web/src/components/dashboards/legacy/legacy-dashboard.tsx b/web/src/components/dashboards/legacy/legacy-dashboard.tsx
index 02ced1e49..b1e04f4e0 100644
--- a/web/src/components/dashboards/legacy/legacy-dashboard.tsx
+++ b/web/src/components/dashboards/legacy/legacy-dashboard.tsx
@@ -15,6 +15,7 @@ import {
Flex,
FlexItem,
ExpandableSectionToggle,
+ Spinner,
} from '@patternfly/react-core';
import type { FC } from 'react';
import { memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';
@@ -36,6 +37,7 @@ import {
getObserveState,
usePerspective,
} from '../../hooks/usePerspective';
+import { useMonitoringNamespace } from '../../hooks/useMonitoringNamespace';
import KebabDropdown from '../../kebab-dropdown';
import { MonitoringState } from '../../../store/store';
import { evaluateVariableTemplate, Variable } from './legacy-variable-dropdowns';
@@ -47,7 +49,6 @@ import {
isDataSource,
} from '@openshift-console/dynamic-plugin-sdk/lib/extensions/dashboard-data-source';
import { t_global_font_size_heading_h2 } from '@patternfly/react-tokens';
-import { GraphEmpty } from '../../../components/console/graphs/graph-empty';
import { GraphUnits } from '../../../components/metrics/units';
import { LegacyDashboardPageTestIDs } from '../../../components/data-test';
import { useMonitoring } from '../../../hooks/useMonitoring';
@@ -63,6 +64,7 @@ const QueryBrowserLink = ({
}) => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
const { perspective } = usePerspective();
+ const { namespace } = useMonitoringNamespace();
const params = new URLSearchParams();
queries.forEach((q, i) => params.set(`query${i}`, q));
@@ -77,7 +79,7 @@ const QueryBrowserLink = ({
return (
{t('Inspect')}
@@ -121,6 +123,7 @@ const Card: FC = memo(({ panel, perspective }) => {
const [isError, setIsError] = useState(false);
const [dataSourceInfoLoading, setDataSourceInfoLoading] = useState(true);
const [customDataSource, setCustomDataSource] = useState(undefined);
+ const [isChartLoading, setIsChartLoading] = useState(panel.type === 'graph');
const customDataSourceName = panel.datasource?.name;
const [extensions, extensionsResolved] = useResolvedExtensions(isDataSource);
const hasExtensions = !_.isEmpty(extensions);
@@ -304,6 +307,7 @@ const Card: FC = memo(({ panel, perspective }) => {
actions={{
actions: (
<>
+ {(isLoading || isChartLoading) && }
{!isLoading && (
= memo(({ panel, perspective }) => {
{t('Error loading card')}
>
) : (
-
- {isLoading || !wasEverVisible ? (
-
- ) : (
+
+ {!isLoading && wasEverVisible && (
<>
{panel.type === 'grafana-piechart-panel' && (
= memo(({ panel, perspective }) => {
{
const { t } = useTranslation('plugin__monitoring-plugin');
@@ -34,7 +34,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => {
const [legacyDashboardsError, setLegacyDashboardsError] = useState();
const [refreshInterval] = useQueryParam(QueryParams.RefreshInterval, NumberParam);
const [legacyDashboardsLoading, , , setLegacyDashboardsLoaded] = useBoolean(true);
- const [initialLoad, , setInitialUnloaded, setInitialLoaded] = useBoolean(true);
+ const [initialLoad, , , setInitialLoaded] = useBoolean(true);
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -117,7 +117,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => {
}, [legacyDashboards, legacyDashboardsLoading]);
const changeLegacyDashboard = useCallback(
- (newBoard: string) => {
+ (newBoard: string, forceRefresh = false) => {
if (!newBoard) {
// If the board is being cleared then don't do anything
return;
@@ -128,11 +128,12 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => {
const queryArguments = getAllQueryArguments();
const params = new URLSearchParams(queryArguments);
- const url = `${getLegacyDashboardsUrl(perspective, newBoard)}?${params.toString()}`;
+ const url = getLegacyDashboardsUrl(perspective, newBoard, namespace);
- if (newBoard !== urlBoard || initialLoad) {
- if (params.get(QueryParams.Dashboard) !== newBoard) {
- navigate(url, { replace: true });
+ if (newBoard !== urlBoard || forceRefresh) {
+ if (!params.has(QueryParams.Dashboard) || params.get(QueryParams.Dashboard) !== newBoard) {
+ params.set(QueryParams.Dashboard, newBoard);
+ navigate(`${url}?${params.toString()}`, { replace: true });
}
dispatch(dashboardsPatchAllVariables(allVariables));
@@ -150,16 +151,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => {
);
}
},
- [
- perspective,
- urlBoard,
- dispatch,
- navigate,
- namespace,
- legacyDashboards,
- initialLoad,
- refreshInterval,
- ],
+ [perspective, urlBoard, dispatch, navigate, namespace, legacyDashboards, refreshInterval],
);
useEffect(() => {
@@ -169,19 +161,22 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => {
initialLoad) &&
!_.isEmpty(legacyDashboards)
) {
- changeLegacyDashboard(urlBoard || legacyDashboards?.[0]?.name);
+ changeLegacyDashboard(urlBoard || legacyDashboards?.[0]?.name, initialLoad);
setInitialLoaded();
}
}, [legacyDashboards, changeLegacyDashboard, initialLoad, setInitialLoaded, urlBoard]);
useEffect(() => {
- // Basically perform a full reload when changing a namespace to force the variables and the
- // dashboard to reset. This is needed for when we transition between ALL_NS and a normal
- // namespace, but is performed quickly and should help insure consistency when transitioning
- // between any namespaces
- setInitialUnloaded();
- /* eslint-disable react-hooks/exhaustive-deps */
- }, [namespace]);
+ if (initialLoad || _.isEmpty(legacyDashboards)) {
+ return;
+ }
+
+ const currentBoard = urlBoard || legacyDashboards?.[0]?.name;
+ if (currentBoard) {
+ const allVariables = getAllVariables(legacyDashboards, currentBoard, namespace);
+ dispatch(dashboardsPatchAllVariables(allVariables));
+ }
+ }, [namespace, legacyDashboards, urlBoard, dispatch, initialLoad]);
// Clear variables on unmount
useEffect(() => {
diff --git a/web/src/components/dashboards/legacy/useOpenshiftProject.ts b/web/src/components/dashboards/legacy/useOpenshiftProject.ts
deleted file mode 100644
index 10a8cfb69..000000000
--- a/web/src/components/dashboards/legacy/useOpenshiftProject.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
-import { useCallback, useEffect } from 'react';
-import { QueryParams } from '../../query-params';
-import { StringParam, useQueryParam } from 'use-query-params';
-import { useDispatch, useSelector } from 'react-redux';
-import { dashboardsPatchVariable } from '../../../store/actions';
-import { MonitoringState } from '../../../store/store';
-import { getObserveState } from '../../hooks/usePerspective';
-import { useMonitoring } from '../../../hooks/useMonitoring';
-import { ALL_NAMESPACES_KEY } from '../../utils';
-
-export const useOpenshiftProject = () => {
- const [activeNamespace, setActiveNamespace] = useActiveNamespace();
- const [openshiftProject, setOpenshiftProject] = useQueryParam(
- QueryParams.OpenshiftProject,
- StringParam,
- );
- const { plugin } = useMonitoring();
- const variableNamespace = useSelector(
- (state: MonitoringState) =>
- getObserveState(plugin, state).dashboards.variables['namespace']?.value ?? '',
- );
- const dispatch = useDispatch();
-
- useEffect(() => {
- // If the URL parameter is set, but the activeNamespace doesn't match it, then
- // set the activeNamespace to match the URL parameter
- if (openshiftProject && openshiftProject !== activeNamespace) {
- setActiveNamespace(openshiftProject);
- if (variableNamespace !== openshiftProject && openshiftProject !== ALL_NAMESPACES_KEY) {
- dispatch(
- dashboardsPatchVariable('namespace', {
- // Dashboards space variable shouldn't use the ALL_NAMESPACES_KEY
- value: openshiftProject,
- }),
- );
- }
- return;
- }
- if (!openshiftProject) {
- setOpenshiftProject(activeNamespace);
- if (variableNamespace !== activeNamespace && openshiftProject !== ALL_NAMESPACES_KEY) {
- // Dashboards space variable shouldn't use the ALL_NAMESPACES_KEY
- dispatch(
- dashboardsPatchVariable('namespace', {
- value: activeNamespace,
- }),
- );
- }
- return;
- }
- }, [
- activeNamespace,
- setActiveNamespace,
- openshiftProject,
- setOpenshiftProject,
- dispatch,
- variableNamespace,
- ]);
-
- const setProject = useCallback(
- (namespace: string) => {
- setActiveNamespace(namespace);
- setOpenshiftProject(namespace);
- dispatch(dashboardsPatchVariable('namespace', { value: namespace }));
- },
- [setActiveNamespace, setOpenshiftProject, dispatch],
- );
-
- return {
- project: openshiftProject,
- setProject,
- };
-};
diff --git a/web/src/components/fetch-alerts.tsx b/web/src/components/fetch-alerts.tsx
index 50701f33f..9088c21dd 100644
--- a/web/src/components/fetch-alerts.tsx
+++ b/web/src/components/fetch-alerts.tsx
@@ -1,5 +1,8 @@
import { consoleFetchJSON, PrometheusRulesResponse } from '@openshift-console/dynamic-plugin-sdk';
+// Disable client-side timeout (-1) to let the backend control query timeouts
+const NO_TIMEOUT = -1;
+
// Merges Prometheus monitoring alerts with external sources
export const fetchAlerts = async (
prometheusURL: string,
@@ -10,7 +13,7 @@ export const fetchAlerts = async (
namespace?: string,
): Promise => {
if (!externalAlertsFetch || externalAlertsFetch.length === 0) {
- return consoleFetchJSON(prometheusURL);
+ return consoleFetchJSON(prometheusURL, 'GET', {}, NO_TIMEOUT);
}
const resolvedExternalAlertsSources = externalAlertsFetch.map((extensionProperties) => ({
@@ -22,7 +25,7 @@ export const fetchAlerts = async (
try {
const groups = await Promise.allSettled([
- consoleFetchJSON(prometheusURL),
+ consoleFetchJSON(prometheusURL, 'GET', {}, NO_TIMEOUT),
...resolvedExternalAlertsSources.map((source) => source.fetch(namespace)),
]).then((results) =>
results
@@ -39,6 +42,6 @@ export const fetchAlerts = async (
return { data: { groups }, status: 'success' };
} catch {
- return consoleFetchJSON(prometheusURL);
+ return consoleFetchJSON(prometheusURL, 'GET', {}, NO_TIMEOUT);
}
};
diff --git a/web/src/components/hooks/useMonitoringNamespace.ts b/web/src/components/hooks/useMonitoringNamespace.ts
new file mode 100644
index 000000000..b42b2939c
--- /dev/null
+++ b/web/src/components/hooks/useMonitoringNamespace.ts
@@ -0,0 +1,27 @@
+import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
+import { useEffect } from 'react';
+import { useParams } from 'react-router-dom-v5-compat';
+import { useMonitoring } from '../../hooks/useMonitoring';
+
+/**
+ * Utility hook to synchronize the namespace route in the URL with the activeNamespace
+ * the console uses. It checks for namespace in the following order:
+ * 1. Route param `:ns` (used in dev console routes like /dev-monitoring/ns/:ns/...)
+ * 2. Active namespace from console SDK
+ */
+export const useMonitoringNamespace = () => {
+ const { ns: routeNamespace } = useParams<{ ns?: string }>();
+ const [activeNamespace, setActiveNamespace] = useActiveNamespace();
+ const { displayNamespaceSelector } = useMonitoring();
+
+ useEffect(() => {
+ if (routeNamespace && activeNamespace !== routeNamespace) {
+ setActiveNamespace(routeNamespace);
+ }
+ }, [routeNamespace, activeNamespace, setActiveNamespace, displayNamespaceSelector]);
+
+ return {
+ namespace: routeNamespace || activeNamespace,
+ setNamespace: setActiveNamespace,
+ };
+};
diff --git a/web/src/components/hooks/usePerspective.tsx b/web/src/components/hooks/usePerspective.tsx
index b2794782d..8380986f4 100644
--- a/web/src/components/hooks/usePerspective.tsx
+++ b/web/src/components/hooks/usePerspective.tsx
@@ -5,6 +5,7 @@ import * as _ from 'lodash-es';
import {
ALERTMANAGER_BASE_PATH,
ALERTMANAGER_PROXY_PATH,
+ ALERTMANAGER_TENANCY_BASE_PATH,
AlertResource,
labelsToParams,
MonitoringPlugins,
@@ -61,119 +62,155 @@ export const usePerspective = (): usePerspectiveReturn => {
}
};
-export const getAlertsUrl = (perspective: Perspective) => {
+export const getAlertsUrl = (perspective: Perspective, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${AlertResource.url}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/alerts`;
case 'virtualization-perspective':
- return `/virt-monitoring/alerts`;
+ return AlertResource.virtUrl;
case 'admin':
default:
return AlertResource.url;
}
};
-// There is no equivalent rules list page in the developer perspective
-export const getAlertRulesUrl = (perspective: Perspective) => {
+export const getAlertRulesUrl = (perspective: Perspective, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${RuleResource.url}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/alertrules`;
case 'virtualization-perspective':
- return `/virt-monitoring/alertrules`;
+ return RuleResource.virtUrl;
case 'admin':
default:
return RuleResource.url;
}
};
-export const getSilencesUrl = (perspective: Perspective) => {
+export const getSilencesUrl = (perspective: Perspective, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${SilenceResource.url}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/silences`;
case 'virtualization-perspective':
- return `/virt-monitoring/silences`;
+ return SilenceResource.virtUrl;
case 'admin':
default:
return SilenceResource.url;
}
};
-export const getNewSilenceAlertUrl = (perspective: Perspective, alert: PrometheusAlert) => {
+export const getNewSilenceAlertUrl = (
+ perspective: Perspective,
+ alert: PrometheusAlert,
+ namespace?: string,
+) => {
switch (perspective) {
case 'acm':
return `/multicloud${SilenceResource.url}/~new?${labelsToParams(alert.labels)}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/silences/~new?${labelsToParams(alert.labels)}`;
case 'virtualization-perspective':
- return `/virt-monitoring/silences/~new?${labelsToParams(alert.labels)}`;
+ return `${SilenceResource.virtUrl}/~new?${labelsToParams(alert.labels)}`;
case 'admin':
default:
return `${SilenceResource.url}/~new?${labelsToParams(alert.labels)}`;
}
};
-export const getNewSilenceUrl = (perspective: Perspective) => {
+export const getNewSilenceUrl = (perspective: Perspective, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${SilenceResource.url}/~new`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/silences/~new`;
case 'virtualization-perspective':
- return `/virt-monitoring/silences/~new`;
+ return `${SilenceResource.virtUrl}/~new`;
case 'admin':
default:
return `${SilenceResource.url}/~new`;
}
};
-export const getRuleUrl = (perspective: Perspective, rule: Rule) => {
+export const getRuleUrl = (perspective: Perspective, rule: Rule, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${RuleResource.url}/${_.get(rule, 'id')}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/rules/${rule?.id}`;
case 'virtualization-perspective':
- return `/virt-monitoring/alertrules/${rule?.id}`;
+ return `${RuleResource.virtUrl}/${rule?.id}`;
case 'admin':
default:
return `${RuleResource.url}/${_.get(rule, 'id')}`;
}
};
-export const getSilenceAlertUrl = (perspective: Perspective, id: string) => {
+export const getSilenceAlertUrl = (perspective: Perspective, id: string, namespace?: string) => {
switch (perspective) {
case 'acm':
return `/multicloud${SilenceResource.url}/${id}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/silences/${id}`;
case 'virtualization-perspective':
- return `/virt-monitoring/silences/${id}`;
+ return `${SilenceResource.virtUrl}/${id}`;
case 'admin':
default:
return `${SilenceResource.url}/${id}`;
}
};
-export const getEditSilenceAlertUrl = (perspective: Perspective, id: string) => {
+export const getEditSilenceAlertUrl = (
+ perspective: Perspective,
+ id: string,
+ namespace?: string,
+) => {
switch (perspective) {
case 'acm':
return `/multicloud${SilenceResource.url}/${id}/edit`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/silences/${id}/edit`;
case 'virtualization-perspective':
- return `/virt-monitoring/silences/${id}/edit`;
+ return `${SilenceResource.virtUrl}/${id}/edit`;
case 'admin':
default:
return `${SilenceResource.url}/${id}/edit`;
}
};
-export const getAlertUrl = (perspective: Perspective, alert: PrometheusAlert, ruleID: string) => {
+export const getAlertUrl = (
+ perspective: Perspective,
+ alert: PrometheusAlert,
+ ruleID: string,
+ namespace?: string,
+) => {
switch (perspective) {
case 'acm':
return `/multicloud${AlertResource.url}/${ruleID}?${labelsToParams(alert.labels)}`;
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/alerts/${ruleID}?${labelsToParams(alert.labels)}`;
case 'virtualization-perspective':
- return `/virt-monitoring/alerts/${ruleID}?${labelsToParams(alert.labels)}`;
+ return `${AlertResource.virtUrl}/${ruleID}?${labelsToParams(alert.labels)}`;
case 'admin':
default:
return `${AlertResource.url}/${ruleID}?${labelsToParams(alert.labels)}`;
}
};
-export const getFetchSilenceUrl = (perspective: Perspective, silenceID: string) => {
+export const getFetchSilenceUrl = (
+ perspective: Perspective,
+ silenceID: string,
+ namespace?: string,
+) => {
switch (perspective) {
case 'acm':
return `${ALERTMANAGER_PROXY_PATH}/api/v2/silence/${silenceID}`;
+ case 'dev':
+ return `${ALERTMANAGER_TENANCY_BASE_PATH}/api/v2/silence/${silenceID}?namespace=${namespace}`;
case 'virtualization-perspective':
return `${ALERTMANAGER_BASE_PATH}/api/v2/silence/${silenceID}`;
default:
@@ -196,42 +233,60 @@ export const getObserveState = (plugin: MonitoringPlugins, state: MonitoringStat
export const getQueryBrowserUrl = ({
perspective,
query,
+ namespace,
units,
}: {
perspective: Perspective;
query: string;
+ namespace?: string;
units?: GraphUnits;
}) => {
const unitsQueryParam = units ? `&${QueryParams.Units}=${units}` : '';
switch (perspective) {
- case 'virtualization-perspective':
- return `/virt-monitoring/query-browser?query0=${encodeURIComponent(query)}${unitsQueryParam}`;
case 'acm':
return '';
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/metrics?query0=${encodeURIComponent(
+ query,
+ )}${unitsQueryParam}`;
+ case 'virtualization-perspective':
+ return `/virt-monitoring/query-browser?query0=${encodeURIComponent(query)}${unitsQueryParam}`;
case 'admin':
default:
return `/monitoring/query-browser?query0=${encodeURIComponent(query)}${unitsQueryParam}`;
}
};
-export const getMutlipleQueryBrowserUrl = (perspective: Perspective, params: URLSearchParams) => {
+export const getMutlipleQueryBrowserUrl = (
+ perspective: Perspective,
+ params: URLSearchParams,
+ namespace?: string,
+) => {
switch (perspective) {
- case 'virtualization-perspective':
- return `/virt-monitoring/query-browser?${params.toString()}`;
case 'acm':
return '';
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}/metrics?${params.toString()}`;
+ case 'virtualization-perspective':
+ return `/virt-monitoring/query-browser?${params.toString()}`;
case 'admin':
default:
return `/monitoring/query-browser?${params.toString()}`;
}
};
-export const getLegacyDashboardsUrl = (perspective: Perspective, boardName: string) => {
+export const getLegacyDashboardsUrl = (
+ perspective: Perspective,
+ boardName: string,
+ namespace?: string,
+) => {
switch (perspective) {
- case 'virtualization-perspective':
- return `/virt-monitoring/dashboards/${boardName}`;
case 'acm':
return '';
+ case 'dev':
+ return `/dev-monitoring/ns/${namespace}`;
+ case 'virtualization-perspective':
+ return `/virt-monitoring/dashboards/${boardName}`;
case 'admin':
default:
return `/monitoring/dashboards/${boardName}`;
diff --git a/web/src/components/hooks/useQueryNamespace.ts b/web/src/components/hooks/useQueryNamespace.ts
deleted file mode 100644
index 8151c2901..000000000
--- a/web/src/components/hooks/useQueryNamespace.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useEffect } from 'react';
-import { StringParam, useQueryParam } from 'use-query-params';
-import { QueryParams } from '../query-params';
-import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
-
-// Utility hook to syncronize the namespace parameter in the URL with the activeNamespace
-// the console uses. It will return the namespace parameter if set or the activeNamespace if
-// it isn't set.
-export const useQueryNamespace = () => {
- const [queryNamespace, setQueryNamespace] = useQueryParam(QueryParams.Namespace, StringParam);
- const [activeNamespace, setActiveNamespace] = useActiveNamespace();
-
- useEffect(() => {
- if (queryNamespace && activeNamespace !== queryNamespace) {
- setActiveNamespace(queryNamespace);
- }
- }, [queryNamespace, activeNamespace, setActiveNamespace, setQueryNamespace]);
-
- return {
- namespace: queryNamespace || activeNamespace,
- setNamespace: setQueryNamespace,
- };
-};
diff --git a/web/src/components/metrics/promql-expression-input.tsx b/web/src/components/metrics/promql-expression-input.tsx
index 4f2900464..55ebe2a6e 100644
--- a/web/src/components/metrics/promql-expression-input.tsx
+++ b/web/src/components/metrics/promql-expression-input.tsx
@@ -358,6 +358,7 @@ export const PromQLExpressionInput: FC = ({
.then((response) => {
const metrics = response?.data;
setMetricNames(metrics);
+ setErrorMessage(undefined);
})
.catch((err) => {
if (err.name !== 'AbortError') {
diff --git a/web/src/components/proxied-fetch.ts b/web/src/components/proxied-fetch.ts
index 54f250704..8f60453a9 100644
--- a/web/src/components/proxied-fetch.ts
+++ b/web/src/components/proxied-fetch.ts
@@ -43,7 +43,8 @@ export const proxiedFetch = (url: string, init?: RequestInitWithTimeout): Pro
return response.json();
});
- const timeout = init?.timeout ?? 30 * 1000;
+ // Disable client-side timeout by default (-1) to let the backend control query timeouts
+ const timeout = init?.timeout ?? -1;
if (timeout <= 0) {
return fetchPromise;
diff --git a/web/src/components/query-browser.tsx b/web/src/components/query-browser.tsx
index 0ab30202f..b775bb1b2 100644
--- a/web/src/components/query-browser.tsx
+++ b/web/src/components/query-browser.tsx
@@ -45,7 +45,7 @@ import { ChartLineIcon } from '@patternfly/react-icons';
import classNames from 'classnames';
import * as _ from 'lodash-es';
import type { FC, Ref, ReactNode, KeyboardEvent, MouseEvent, ComponentType } from 'react';
-import { memo, useState, useEffect, useCallback, useLayoutEffect } from 'react';
+import { memo, useState, useEffect, useCallback, useLayoutEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -596,6 +596,7 @@ const QueryBrowser_: FC = ({
GraphLink,
hideControls,
isStack = false,
+ onLoadingChange,
onZoom,
pollInterval,
queries,
@@ -638,6 +639,12 @@ const QueryBrowser_: FC = ({
const [graphData, setGraphData] = useState(null);
const [samples, setSamples] = useState(maxSamplesForSpan);
const [updating, setUpdating] = useState(true);
+ // Track if we ever received valid data to prevent flickering "No datapoints" during refresh
+ const hasReceivedData = useRef(false);
+
+ useEffect(() => {
+ onLoadingChange?.(updating);
+ }, [updating, onLoadingChange]);
const [containerRef, width] = useRefWidth();
@@ -808,6 +815,10 @@ const QueryBrowser_: FC = ({
);
setGraphData(newGraphData);
onDataChange?.(newGraphData);
+ // Mark that we've received valid data to prevent flickering during refresh
+ if (newGraphData && newGraphData.some((d) => d.length > 0)) {
+ hasReceivedData.current = true;
+ }
setIsDisconnectedEnabled(dataIsDisconnected);
@@ -932,7 +943,7 @@ const QueryBrowser_: FC = ({
<>
{hideControls ? (
- <>{updating && }>
+ <>{updating && !onLoadingChange && }>
) : (
@@ -1014,7 +1025,9 @@ const QueryBrowser_: FC = ({
data-test={DataTestIDs.MetricGraph}
>
{error && }
- {isGraphDataEmpty && }
+ {isGraphDataEmpty && !(hideControls && (updating || hasReceivedData.current)) && (
+
+ )}
{!isGraphDataEmpty && width > 0 && (
<>
{disableZoom ? (
@@ -1102,6 +1115,7 @@ export type QueryBrowserProps = {
GraphLink?: ComponentType;
hideControls?: boolean;
isStack?: boolean;
+ onLoadingChange?: (isLoading: boolean) => void;
onZoom?: GraphOnZoom;
pollInterval?: number;
queries: string[];
diff --git a/web/src/components/redirects/dev-redirects.tsx b/web/src/components/redirects/dev-redirects.tsx
deleted file mode 100644
index db2c8bd19..000000000
--- a/web/src/components/redirects/dev-redirects.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import type { FC } from 'react';
-import { Navigate, useParams } from 'react-router-dom-v5-compat';
-import {
- getAlertRulesUrl,
- getAlertsUrl,
- getEditSilenceAlertUrl,
- getLegacyDashboardsUrl,
- getSilenceAlertUrl,
-} from '../hooks/usePerspective';
-import { QueryParams } from '../query-params';
-import { SilenceResource } from '../utils';
-
-export const DashboardRedirect: FC = () => {
- const pathParams = useParams<{ ns: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.OpenshiftProject, pathParams.ns);
-
- const dashboardName = queryParams.get(QueryParams.Dashboard);
- queryParams.delete(QueryParams.Dashboard);
-
- return ;
-};
-
-export const AlertRedirect: FC = () => {
- const pathParams = useParams<{ ns: string; ruleID: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return (
-
- );
-};
-
-export const RulesRedirect: FC = () => {
- const pathParams = useParams<{ ns: string; id: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return (
-
- );
-};
-
-export const SilenceRedirect: FC = () => {
- const pathParams = useParams<{ ns: string; id: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return (
-
- );
-};
-
-export const SilenceEditRedirect: FC = () => {
- const pathParams = useParams<{ ns: string; id: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return (
-
- );
-};
-
-export const SilenceNewRedirect: FC = () => {
- const pathParams = useParams<{ ns: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return ;
-};
-
-export const MetricsRedirect: FC = () => {
- const pathParams = useParams<{ ns: string }>();
-
- const queryParams = new URLSearchParams(window.location.search);
- queryParams.append(QueryParams.Namespace, pathParams.ns);
-
- return ;
-};
diff --git a/web/src/contexts/MonitoringContext.tsx b/web/src/contexts/MonitoringContext.tsx
index fb48a4316..c1b4449ae 100644
--- a/web/src/contexts/MonitoringContext.tsx
+++ b/web/src/contexts/MonitoringContext.tsx
@@ -19,6 +19,11 @@ type MonitoringContextType = {
useMetricsTenancy: boolean;
/** Dictates if the users access is being loaded. */
accessCheckLoading: boolean;
+ /**
+ * Dictates if the namespace selector is shown inside the view,
+ * in some perspectives the selector already exist outside monitoring components scope
+ */
+ displayNamespaceSelector: boolean;
};
export const MonitoringContext = React.createContext({
@@ -27,12 +32,14 @@ export const MonitoringContext = React.createContext({
useAlertsTenancy: false,
useMetricsTenancy: false,
accessCheckLoading: true,
+ displayNamespaceSelector: true,
});
export const MonitoringProvider: React.FC<{
monitoringContext: {
plugin: MonitoringPlugins;
prometheus: Prometheus;
+ displayNamespaceSelector?: boolean;
};
}> = ({ children, monitoringContext }) => {
const [allNamespaceAlertsTenancy, alertAccessCheckLoading] = useAccessReview({
@@ -56,6 +63,7 @@ export const MonitoringProvider: React.FC<{
useAlertsTenancy: monitoringContext.prometheus === 'cmo' && !allNamespaceAlertsTenancy,
useMetricsTenancy: monitoringContext.prometheus === 'cmo' && !allNamespaceMeticsTenancy,
accessCheckLoading: alertAccessCheckLoading || metricsAccessCheckLoading,
+ displayNamespaceSelector: monitoringContext.displayNamespaceSelector ?? true,
};
}, [
monitoringContext,
diff --git a/web/src/hooks/useAlerts.ts b/web/src/hooks/useAlerts.ts
index 34cd2080d..af1af20be 100644
--- a/web/src/hooks/useAlerts.ts
+++ b/web/src/hooks/useAlerts.ts
@@ -26,14 +26,14 @@ import {
} from '../components/alerting/AlertUtils';
import { MonitoringState } from '../store/store';
import { getObserveState } from '../components/hooks/usePerspective';
-import { useQueryNamespace } from '../components/hooks/useQueryNamespace';
+import { useMonitoringNamespace } from '../components/hooks/useMonitoringNamespace';
const POLLING_INTERVAL_MS = 15 * 1000; // 15 seconds
export const useAlerts = (props?: { dontUseTenancy?: boolean }) => {
// Retrieve external information which dictates which alerts to load and use
const { plugin } = useMonitoring();
- const { namespace } = useQueryNamespace();
+ const { namespace } = useMonitoringNamespace();
const { prometheus, useAlertsTenancy, accessCheckLoading } = useMonitoring();
const overriddenNamespace =
props?.dontUseTenancy || !useAlertsTenancy ? ALL_NAMESPACES_KEY : namespace;
diff --git a/web/src/hooks/useMonitoring.ts b/web/src/hooks/useMonitoring.ts
index a21002ac1..b3fa3f7e8 100644
--- a/web/src/hooks/useMonitoring.ts
+++ b/web/src/hooks/useMonitoring.ts
@@ -2,13 +2,20 @@ import { useContext } from 'react';
import { MonitoringContext } from '../contexts/MonitoringContext';
export const useMonitoring = () => {
- const { prometheus, plugin, useAlertsTenancy, useMetricsTenancy, accessCheckLoading } =
- useContext(MonitoringContext);
+ const {
+ prometheus,
+ plugin,
+ useAlertsTenancy,
+ useMetricsTenancy,
+ accessCheckLoading,
+ displayNamespaceSelector,
+ } = useContext(MonitoringContext);
return {
prometheus,
plugin,
useAlertsTenancy,
useMetricsTenancy,
accessCheckLoading,
+ displayNamespaceSelector,
};
};
| |