diff --git a/web/console-extensions.json b/web/console-extensions.json index c6a0a3f1..56f36786 100644 --- a/web/console-extensions.json +++ b/web/console-extensions.json @@ -99,17 +99,6 @@ "insertAfter": "dashboards-virt" } }, - { - "type": "console.tab", - "properties": { - "contextId": "dev-console-observe", - "name": "%plugin__monitoring-plugin~Dashboards%", - "href": "", - "component": { - "$codeRef": "LegacyDashboardsPage.MpCmoLegacyDashboardsPage" - } - } - }, { "type": "console.redux-reducer", "properties": { @@ -276,5 +265,53 @@ "path": ["/virt-monitoring/alerts/:ruleID"], "component": { "$codeRef": "AlertsDetailsPage.MpCmoAlertsDetailsPage" } } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/alerts/:ruleID", + "component": { "$codeRef": "DevRedirects.AlertRedirect" } + } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/rules/:id", + "component": { "$codeRef": "DevRedirects.RulesRedirect" } + } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/silences/:id", + "component": { "$codeRef": "DevRedirects.SilenceRedirect" } + } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/silences/:id/edit", + "component": { "$codeRef": "DevRedirects.SilenceEditRedirect" } + } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/silences/~new", + "component": { "$codeRef": "DevRedirects.SilenceNewRedirect" } + } + }, + { + "type": "console.page/route", + "properties": { + "exact": false, + "path": "/dev-monitoring/ns/:ns/metrics", + "component": { "$codeRef": "DevRedirects.MetricsRedirect" } + } } ] diff --git a/web/package.json b/web/package.json index 0680a652..69683b17 100644 --- a/web/package.json +++ b/web/package.json @@ -192,7 +192,8 @@ "MonitoringReducer": "./store/reducers", "IncidentsPage": "./components/Incidents/IncidentsPage", "TargetsPage": "./components/targets-page", - "PrometheusRedirectPage": "./components/prometheus-redirect-page", + "PrometheusRedirectPage": "./components/redirects/prometheus-redirect-page", + "DevRedirects": "./components/redirects/dev-redirects", "MonitoringContext": "./contexts/MonitoringContext" }, "dependencies": { diff --git a/web/src/components/Incidents/IncidentsDetailsRowTable.tsx b/web/src/components/Incidents/IncidentsDetailsRowTable.tsx index 2f9a64c1..0bd6a203 100644 --- a/web/src/components/Incidents/IncidentsDetailsRowTable.tsx +++ b/web/src/components/Incidents/IncidentsDetailsRowTable.tsx @@ -17,7 +17,7 @@ interface IncidentsDetailsRowTableProps { } const IncidentsDetailsRowTable = ({ alerts }: IncidentsDetailsRowTableProps) => { - const [namespace, setNamespace] = useActiveNamespace(); + const [, setNamespace] = useActiveNamespace(); const { perspective } = usePerspective(); const { t } = useTranslation(process.env.I18N_NAMESPACE); @@ -34,7 +34,7 @@ const IncidentsDetailsRowTable = ({ alerts }: IncidentsDetailsRowTableProps) => setNamespace(ALL_NAMESPACES_KEY)} > {alertDetails.alertname} @@ -63,7 +63,7 @@ const IncidentsDetailsRowTable = ({ alerts }: IncidentsDetailsRowTableProps) => } return null; - }, [alerts, perspective, namespace, setNamespace]); + }, [alerts, perspective, setNamespace]); return ( diff --git a/web/src/components/MetricsPage.tsx b/web/src/components/MetricsPage.tsx index 53e2f5a0..e8b70d3e 100644 --- a/web/src/components/MetricsPage.tsx +++ b/web/src/components/MetricsPage.tsx @@ -113,8 +113,7 @@ import { t_global_spacer_sm, t_global_font_family_mono, } from '@patternfly/react-tokens'; -import { QueryParamProvider, StringParam, useQueryParam } from 'use-query-params'; -import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; +import { StringParam, useQueryParam } from 'use-query-params'; import { GraphUnits, isGraphUnit } from './metrics/units'; import { SimpleSelect, SimpleSelectOption } from '@patternfly/react-templates'; import { valueFormatter } from './console/console-shared/src/components/query-browser/QueryBrowserTooltip'; @@ -122,6 +121,7 @@ import { ALL_NAMESPACES_KEY } from './utils'; import { MonitoringProvider } from '../contexts/MonitoringContext'; import { DataTestIDs } from './data-test'; import { useMonitoring } from '../hooks/useMonitoring'; +import { useQueryNamespace } from './hooks/useQueryNamespace'; // Stores information about the currently focused query input let focusedQuery; @@ -1277,14 +1277,7 @@ const GraphUnitsDropDown: FC = () => { const MetricsPage_: FC = () => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const [units, setUnits] = useQueryParam(QueryParams.Units, StringParam); - const [queryNamespace, setQueryNamespace] = useQueryParam(QueryParams.Namespace, StringParam); - const [activeNamespace, setActiveNamespace] = useActiveNamespace(); - - useEffect(() => { - if (queryNamespace && activeNamespace !== queryNamespace) { - setActiveNamespace(queryNamespace); - } - }, [queryNamespace, activeNamespace, setActiveNamespace]); + const { setNamespace } = useQueryNamespace(); const dispatch = useDispatch(); @@ -1369,7 +1362,7 @@ const MetricsPage_: FC = () => { { dispatch(queryBrowserDeleteAllQueries()); - setQueryNamespace(namespace); + setNamespace(namespace); }} /> @@ -1432,9 +1425,7 @@ const MetricsPage = withFallback(MetricsPage_); export const MpCmoMetricsPage: React.FC = () => { return ( - - - + ); }; diff --git a/web/src/components/alerting/AlertDetail/SilencedByTable.tsx b/web/src/components/alerting/AlertDetail/SilencedByTable.tsx index 7263eb37..87b34702 100644 --- a/web/src/components/alerting/AlertDetail/SilencedByTable.tsx +++ b/web/src/components/alerting/AlertDetail/SilencedByTable.tsx @@ -1,11 +1,6 @@ import type { FC, MouseEvent } from 'react'; import { useState, useMemo } from 'react'; -import { - ResourceIcon, - Silence, - SilenceStates, - useActiveNamespace, -} from '@openshift-console/dynamic-plugin-sdk'; +import { ResourceIcon, Silence, SilenceStates } from '@openshift-console/dynamic-plugin-sdk'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom-v5-compat'; import { @@ -30,13 +25,12 @@ import { t_global_spacer_xs } from '@patternfly/react-tokens'; export const SilencedByList: FC<{ silences: Silence[] }> = ({ silences }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); const navigate = useNavigate(); const [isModalOpen, , setModalOpen, setModalClosed] = useBoolean(false); const [silence, setSilence] = useState(null); const editSilence = (event: MouseEvent, rowIndex: number) => { - navigate(getEditSilenceAlertUrl(perspective, silences.at(rowIndex)?.id, namespace)); + navigate(getEditSilenceAlertUrl(perspective, silences.at(rowIndex)?.id)); }; const rowActions = (silence: Silence): IAction[] => { @@ -79,7 +73,7 @@ export const SilencedByList: FC<{ silences: Silence[] }> = ({ silences }) => { {silence.name} diff --git a/web/src/components/alerting/AlertList/AggregateAlertTableRow.tsx b/web/src/components/alerting/AlertList/AggregateAlertTableRow.tsx index 5ac37454..f20958bb 100644 --- a/web/src/components/alerting/AlertList/AggregateAlertTableRow.tsx +++ b/web/src/components/alerting/AlertList/AggregateAlertTableRow.tsx @@ -1,9 +1,4 @@ -import { - Alert, - ResourceIcon, - TableColumn, - useActiveNamespace, -} from '@openshift-console/dynamic-plugin-sdk'; +import { Alert, ResourceIcon, TableColumn } from '@openshift-console/dynamic-plugin-sdk'; import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import type { FC } from 'react'; import { useState, useMemo } from 'react'; @@ -33,7 +28,6 @@ const AggregateAlertTableRow: AggregateAlertTableRowProps = ({ const { perspective } = usePerspective(); const title = aggregatedAlert.name; const isACMPerspective = perspective === 'acm'; - const [namespace] = useActiveNamespace(); const filteredAlerts = useMemo( () => filterAlerts(aggregatedAlert.alerts, selectedFilters), @@ -99,11 +93,7 @@ const AggregateAlertTableRow: AggregateAlertTableRowProps = ({ diff --git a/web/src/components/alerting/AlertList/AlertTableRow.tsx b/web/src/components/alerting/AlertList/AlertTableRow.tsx index 30de8322..a84c52cb 100644 --- a/web/src/components/alerting/AlertList/AlertTableRow.tsx +++ b/web/src/components/alerting/AlertList/AlertTableRow.tsx @@ -21,7 +21,7 @@ import { DropdownItem, Flex, FlexItem } from '@patternfly/react-core'; import KebabDropdown from '../../../components/kebab-dropdown'; import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate, useParams } from 'react-router-dom-v5-compat'; +import { useNavigate } from 'react-router-dom-v5-compat'; import { AlertResource, alertState } from '../../../components/utils'; import { getAlertUrl, @@ -35,9 +35,6 @@ const AlertTableRow: FC<{ alert: Alert }> = ({ alert }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); const navigate = useNavigate(); - const params = useParams<{ ns: string }>(); - - const namespace = params.ns; const state = alertState(alert); @@ -49,7 +46,7 @@ const AlertTableRow: FC<{ alert: Alert }> = ({ alert }) => { dropdownItems.unshift( navigate(getNewSilenceAlertUrl(perspective, alert, namespace))} + onClick={() => navigate(getNewSilenceAlertUrl(perspective, alert))} data-test={DataTestIDs.SilenceAlertDropdownItem} > {t('Silence alert')} @@ -86,12 +83,7 @@ const AlertTableRow: FC<{ alert: Alert }> = ({ alert }) => { diff --git a/web/src/components/alerting/AlertRulesDetailsPage.tsx b/web/src/components/alerting/AlertRulesDetailsPage.tsx index 790b73e1..322d4c17 100644 --- a/web/src/components/alerting/AlertRulesDetailsPage.tsx +++ b/web/src/components/alerting/AlertRulesDetailsPage.tsx @@ -5,7 +5,6 @@ import { PrometheusAlert, ResourceIcon, Timestamp, - useActiveNamespace, useResolvedExtensions, } from '@openshift-console/dynamic-plugin-sdk'; import { @@ -69,6 +68,7 @@ import { MonitoringProvider } from '../../contexts/MonitoringContext'; import { DataTestIDs } from '../data-test'; import { useAlerts } from '../../hooks/useAlerts'; +import { useQueryNamespace } from '../hooks/useQueryNamespace'; // Renders Prometheus template text and highlights any {{ ... }} tags that it contains const PrometheusTemplate = ({ text }) => ( @@ -83,11 +83,10 @@ const PrometheusTemplate = ({ text }) => ( type ActiveAlertsProps = { alerts: PrometheusAlert[]; - namespace: string; ruleID: string; }; -export const ActiveAlerts: FC = ({ alerts, namespace, ruleID }) => { +export const ActiveAlerts: FC = ({ alerts, ruleID }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); const navigate = useNavigate(); @@ -110,7 +109,7 @@ export const ActiveAlerts: FC = ({ alerts, namespace, ruleID
{alertDescription(a)} @@ -148,7 +147,7 @@ const AlertRulesDetailsPage_: FC = () => { const { rules, rulesAlertLoading } = useAlerts(); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); + const { namespace } = useQueryNamespace(); const rule = _.find(rules, { id: params.id }); @@ -187,10 +186,7 @@ const AlertRulesDetailsPage_: FC = () => { - + {t('Alerting rules')} @@ -316,7 +312,6 @@ const AlertRulesDetailsPage_: FC = () => { to={getQueryBrowserUrl({ perspective: perspective, query: rule?.query, - namespace: namespace, })} > @@ -372,7 +367,7 @@ const AlertRulesDetailsPage_: FC = () => { {_.isEmpty(rule?.alerts) ? (
{t('None found')}
) : ( - + )} diff --git a/web/src/components/alerting/AlertRulesPage.tsx b/web/src/components/alerting/AlertRulesPage.tsx index 45fe6ba6..247491a7 100644 --- a/web/src/components/alerting/AlertRulesPage.tsx +++ b/web/src/components/alerting/AlertRulesPage.tsx @@ -97,7 +97,7 @@ const RuleTableRow: FC> = ({ obj }) => { diff --git a/web/src/components/alerting/AlertUtils.tsx b/web/src/components/alerting/AlertUtils.tsx index 25dc4941..49e36fd2 100644 --- a/web/src/components/alerting/AlertUtils.tsx +++ b/web/src/components/alerting/AlertUtils.tsx @@ -255,7 +255,7 @@ export const Graph: FC = ({ 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 38812d60..860a826d 100644 --- a/web/src/components/alerting/AlertingPage.tsx +++ b/web/src/components/alerting/AlertingPage.tsx @@ -13,6 +13,7 @@ import { useLocation } from 'react-router-dom'; import { AlertResource, RuleResource, SilenceResource } from '../utils'; import { useDispatch } from 'react-redux'; import { alertingClearSelectorData } from '../../store/actions'; +import { useQueryNamespace } from '../hooks/useQueryNamespace'; const CmoAlertsPage = lazy(() => import(/* webpackChunkName: "CmoAlertsPage" */ './AlertsPage').then((module) => ({ @@ -59,6 +60,7 @@ const AlertingPage: FC = () => { const dispatch = useDispatch(); const [perspective] = useActivePerspective(); + const { setNamespace } = useQueryNamespace(); const { plugin, prometheus } = useMonitoring(); @@ -100,9 +102,10 @@ const AlertingPage: FC = () => { <> {namespacedPages.includes(pathname) && ( - dispatch(alertingClearSelectorData(prometheus, namespace)) - } + onNamespaceChange={(namespace) => { + dispatch(alertingClearSelectorData(prometheus, namespace)); + setNamespace(namespace); + }} /> )} diff --git a/web/src/components/alerting/AlertsDetailsPage.tsx b/web/src/components/alerting/AlertsDetailsPage.tsx index 6320e8eb..5d8ecdeb 100644 --- a/web/src/components/alerting/AlertsDetailsPage.tsx +++ b/web/src/components/alerting/AlertsDetailsPage.tsx @@ -8,7 +8,6 @@ import { ResourceIcon, ResourceLink, Rule, - useActiveNamespace, useResolvedExtensions, } from '@openshift-console/dynamic-plugin-sdk'; import * as _ from 'lodash-es'; @@ -90,6 +89,7 @@ import { import { DataTestIDs } from '../data-test'; import { useAlerts } from '../../hooks/useAlerts'; import { useMonitoring } from '../../hooks/useMonitoring'; +import { useQueryNamespace } from '../hooks/useQueryNamespace'; const AlertsDetailsPage_: FC = () => { const { t } = useTranslation(process.env.I18N_NAMESPACE); @@ -101,7 +101,7 @@ const AlertsDetailsPage_: FC = () => { const { alerts, rulesAlertLoading, silences } = useAlerts(); - const [namespace] = useActiveNamespace(); + const { namespace } = useQueryNamespace(); const hideGraphs = useSelector( (state: MonitoringState) => !!getObserveState(plugin, state).hideGraphs, @@ -159,7 +159,7 @@ const AlertsDetailsPage_: FC = () => { - + {t('Alerts')} @@ -193,7 +193,7 @@ const AlertsDetailsPage_: FC = () => { {state !== AlertStates.Silenced && (
{a.labels.alertname} @@ -250,7 +240,7 @@ const SilencedAlertsList: FC = ({ alerts }) => { dropdownItems={[ navigate(getRuleUrl(perspective, a.rule, namespace))} + onClick={() => navigate(getRuleUrl(perspective, a.rule))} > {t('View alerting rule')} , diff --git a/web/src/components/alerting/SilencesPage.tsx b/web/src/components/alerting/SilencesPage.tsx index a68d382c..a2c527f8 100644 --- a/web/src/components/alerting/SilencesPage.tsx +++ b/web/src/components/alerting/SilencesPage.tsx @@ -6,7 +6,6 @@ import { Silence, SilenceStates, TableColumn, - useActiveNamespace, useListPageFilter, VirtualizedTable, } from '@openshift-console/dynamic-plugin-sdk'; @@ -281,13 +280,11 @@ const ExpireAllSilencesButton: FC = ({ setErrorMes const { selectedSilences, setSelectedSilences } = useContext(SelectedSilencesContext); - const [namespace] = useActiveNamespace(); - const onClick = () => { setInProgress(); Promise.allSettled( [...selectedSilences].map((silenceID: string) => - consoleFetchJSON.delete(getFetchSilenceUrl(perspective, silenceID, namespace)), + consoleFetchJSON.delete(getFetchSilenceUrl(perspective, silenceID)), ), ).then((values) => { setNotInProgress(); @@ -325,10 +322,9 @@ const SilenceTableRowWithCheckbox: FC> = ({ obj }) => ( const CreateSilenceButton: FC = memo(() => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); return ( - + diff --git a/web/src/components/alerting/SilencesUtils.tsx b/web/src/components/alerting/SilencesUtils.tsx index c9e0d051..b42661b3 100644 --- a/web/src/components/alerting/SilencesUtils.tsx +++ b/web/src/components/alerting/SilencesUtils.tsx @@ -4,7 +4,6 @@ import { ResourceIcon, Silence, SilenceStates, - useActiveNamespace, } from '@openshift-console/dynamic-plugin-sdk'; import { Button, @@ -57,7 +56,6 @@ import { DataTestIDs } from '../data-test'; export const SilenceTableRow: FC = ({ obj, showCheckbox }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); const { createdBy, endsAt, firingAlerts, id, name, startsAt, matchers } = obj; const state = silenceState(obj); @@ -107,7 +105,7 @@ export const SilenceTableRow: FC = ({ obj, showCheckbox }) {name} @@ -205,14 +203,13 @@ export const SilenceState = ({ silence }) => { export const SilenceDropdown: FC = ({ silence, toggleText }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); const navigate = useNavigate(); const [isOpen, setIsOpen, , setClosed] = useBoolean(false); const [isModalOpen, , setModalOpen, setModalClosed] = useBoolean(false); const editSilence = () => { - navigate(getEditSilenceAlertUrl(perspective, silence.id, namespace)); + navigate(getEditSilenceAlertUrl(perspective, silence.id)); }; const dropdownItems = @@ -281,7 +278,6 @@ export const ExpireSilenceModal: FC = ({ }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { perspective } = usePerspective(); - const [namespace] = useActiveNamespace(); const [isInProgress, , setInProgress, setNotInProgress] = useBoolean(false); const [success, , setSuccess] = useBoolean(false); @@ -289,7 +285,7 @@ export const ExpireSilenceModal: FC = ({ const expireSilence = () => { setInProgress(); - const url = getFetchSilenceUrl(perspective, silenceID, namespace); + const url = getFetchSilenceUrl(perspective, silenceID); 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 c4229c2a..d97e22ee 100644 --- a/web/src/components/console/graphs/promethues-graph.tsx +++ b/web/src/components/console/graphs/promethues-graph.tsx @@ -21,7 +21,6 @@ const mapStateToProps = (state: RootState) => ({ const PrometheusGraphLink_: FC = ({ children, query, - namespace, ariaChartLinkLabel, }) => { const { perspective } = usePerspective(); @@ -32,7 +31,7 @@ const PrometheusGraphLink_: FC = ({ const params = new URLSearchParams(); queries.forEach((q, index) => params.set(`query${index}`, q)); - const url = getMutlipleQueryBrowserUrl(perspective, params, namespace); + const url = getMutlipleQueryBrowserUrl(perspective, params); return ( = forwardRef( type PrometheusGraphLinkProps = { canAccessMonitoring: boolean; query: string | string[]; - namespace?: string; ariaChartLinkLabel?: string; }; diff --git a/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx b/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx index d5935ae0..c102b1eb 100644 --- a/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx +++ b/web/src/components/dashboards/legacy/legacy-dashboard-page.tsx @@ -1,9 +1,7 @@ -import { Overview } from '@openshift-console/dynamic-plugin-sdk'; +import { NamespaceBar, Overview } from '@openshift-console/dynamic-plugin-sdk'; import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom-v5-compat'; -import { QueryParamProvider } from 'use-query-params'; -import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; import { LoadingInline } from '../../../components/console/console-shared/src/components/loading/LoadingInline'; import withFallback from '../../console/console-shared/error/fallbacks/withFallback'; import { usePerspective } from '../../hooks/usePerspective'; @@ -12,16 +10,14 @@ import ErrorAlert from './error'; import { DashboardSkeletonLegacy } from './dashboard-skeleton-legacy'; import { useLegacyDashboards } from './useLegacyDashboards'; import { MonitoringProvider } from '../../../contexts/MonitoringContext'; +import { useOpenshiftProject } from './useOpenshiftProject'; type LegacyDashboardsPageProps = { urlBoard: string; - namespace?: string; }; -const LegacyDashboardsPage_: FC = ({ - urlBoard, - namespace, // only used in developer perspective -}) => { +const LegacyDashboardsPage_: FC = ({ urlBoard }) => { + const { project, setProject } = useOpenshiftProject(); const { legacyDashboardsError, legacyRows, @@ -29,41 +25,42 @@ const LegacyDashboardsPage_: FC = ({ legacyDashboardsMetadata, changeLegacyDashboard, legacyDashboard, - } = useLegacyDashboards(namespace, urlBoard); + } = useLegacyDashboards(project, urlBoard); const { perspective } = usePerspective(); const { t } = useTranslation(process.env.I18N_NAMESPACE); return ( - - - {legacyDashboardsLoading ? ( - - ) : legacyDashboardsError ? ( - - ) : ( - - )} - - + <> + setProject(namespace)} /> + + + {legacyDashboardsLoading ? ( + + ) : legacyDashboardsError ? ( + + ) : ( + + )} + + + ); }; const LegacyDashboardsPageWithFallback = withFallback(LegacyDashboardsPage_); export const MpCmoLegacyDashboardsPage: FC = () => { - const params = useParams<{ ns?: string; dashboardName: string }>(); + const params = useParams<{ dashboardName: string }>(); return ( - - - + ); }; diff --git a/web/src/components/dashboards/legacy/legacy-dashboard.tsx b/web/src/components/dashboards/legacy/legacy-dashboard.tsx index 69768342..02ced1e4 100644 --- a/web/src/components/dashboards/legacy/legacy-dashboard.tsx +++ b/web/src/components/dashboards/legacy/legacy-dashboard.tsx @@ -1,7 +1,6 @@ import * as _ from 'lodash-es'; import { RedExclamationCircleIcon, - useActiveNamespace, useResolvedExtensions, } from '@openshift-console/dynamic-plugin-sdk'; import { @@ -39,7 +38,7 @@ import { } from '../../hooks/usePerspective'; import KebabDropdown from '../../kebab-dropdown'; import { MonitoringState } from '../../../store/store'; -import { evaluateVariableTemplate } from './legacy-variable-dropdowns'; +import { evaluateVariableTemplate, Variable } from './legacy-variable-dropdowns'; import { Panel, Row } from './types'; import { QueryParams } from '../../query-params'; import { CustomDataSource } from '@openshift-console/dynamic-plugin-sdk-internal/lib/extensions/dashboard-data-source'; @@ -70,7 +69,6 @@ const QueryBrowserLink = ({ if (units) { params.set(QueryParams.Units, units); } - const [namespace] = useActiveNamespace(); if (customDataSourceName) { params.set('datasource', customDataSourceName); @@ -79,7 +77,7 @@ const QueryBrowserLink = ({ return ( {t('Inspect')} @@ -104,7 +102,6 @@ const Card: FC = memo(({ panel, perspective }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { plugin } = useMonitoring(); - const [namespace] = useActiveNamespace(); const pollInterval = useSelector( (state: MonitoringState) => getObserveState(plugin, state).dashboards.pollInterval, ); @@ -115,6 +112,9 @@ const Card: FC = memo(({ panel, perspective }) => { (state: MonitoringState) => getObserveState(plugin, state).dashboards.variables, ); + // Directly use the namespace variable to prevent desync + const namespace = variables?.['namespace'] as Variable; + const ref = useRef(); const [, wasEverVisible] = useIsVisible(ref); @@ -281,7 +281,9 @@ const Card: FC = memo(({ panel, perspective }) => { if (!rawQueries.length) { return null; } - const queries = rawQueries.map((expr) => evaluateVariableTemplate(expr, variables, timespan)); + const queries = rawQueries.map((expr) => + evaluateVariableTemplate(expr, variables, timespan, namespace?.value ?? ''), + ); const isLoading = (_.some(queries, _.isUndefined) && dataSourceInfoLoading) || customDataSource === undefined; @@ -353,7 +355,7 @@ const Card: FC = memo(({ panel, perspective }) => { panel={panel} pollInterval={pollInterval} query={queries[0]} - namespace={namespace} + namespace={namespace?.value ?? ''} customDataSource={customDataSource} /> )} @@ -362,7 +364,7 @@ const Card: FC = memo(({ panel, perspective }) => { panel={panel} pollInterval={pollInterval} queries={queries} - namespace={namespace} + namespace={namespace?.value ?? ''} customDataSource={customDataSource} /> )} diff --git a/web/src/components/dashboards/legacy/legacy-variable-dropdowns.tsx b/web/src/components/dashboards/legacy/legacy-variable-dropdowns.tsx index 922c4ac5..99e841bf 100644 --- a/web/src/components/dashboards/legacy/legacy-variable-dropdowns.tsx +++ b/web/src/components/dashboards/legacy/legacy-variable-dropdowns.tsx @@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import { SingleTypeaheadDropdown } from '../../console/utils/single-typeahead-dropdown'; -import { getPrometheusBasePath, buildPrometheusUrl } from '../../utils'; +import { getPrometheusBasePath, buildPrometheusUrl, ALL_NAMESPACES_KEY } from '../../utils'; import { getQueryArgument, setQueryArgument } from '../../console/utils/router'; import { useSafeFetch } from '../../console/utils/safe-fetch-hook'; @@ -48,6 +48,7 @@ export const evaluateVariableTemplate = ( template: string, variables: any, timespan: number, + namespace: string, ): string => { if (_.isEmpty(template)) { return undefined; @@ -81,8 +82,11 @@ export const evaluateVariableTemplate = ( result = undefined; return false; } - const replacement = + let replacement = v.value === MONITORING_DASHBOARDS_VARIABLE_ALL_OPTION_KEY ? '.+' : v.value || ''; + if (v.name === 'namespace' && namespace !== ALL_NAMESPACES_KEY) { + replacement = namespace; + } result = result.replace(re, replacement); } }); @@ -103,9 +107,10 @@ const LegacyDashboardsVariableOption = ({ value, isSelected, ...rest }) => ); -const LegacyDashboardsVariableDropdown: FC = ({ id, name, namespace }) => { +const LegacyDashboardsVariableDropdown: FC = ({ id, name }) => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const { plugin } = useMonitoring(); + const [namespace] = useActiveNamespace(); const timespan = useSelector( (state: MonitoringState) => getObserveState(plugin, state).dashboards.timespan, @@ -115,11 +120,12 @@ const LegacyDashboardsVariableDropdown: FC = ({ id, name, (state: MonitoringState) => getObserveState(plugin, state).dashboards.variables, ); const variable = variables?.[name] as Variable; + const options = useDeepMemo(() => { return variable?.options; }, [variable?.options]); - const query = evaluateVariableTemplate(variable?.query, variables, timespan); + const query = evaluateVariableTemplate(variable?.query, variables, timespan, namespace); const dispatch = useDispatch(); @@ -309,7 +315,6 @@ const LegacyDashboardsVariableDropdown: FC = ({ id, name, // Expects to be inside of a Patternfly Split Component export const LegacyDashboardsAllVariableDropdowns: FC = () => { - const [namespace] = useActiveNamespace(); const { plugin } = useMonitoring(); const variables = useSelector( @@ -323,7 +328,7 @@ export const LegacyDashboardsAllVariableDropdowns: FC = () => { return ( {Object.keys(variables).map((name: string) => ( - + ))} ); @@ -342,5 +347,4 @@ export type Variable = { type VariableDropdownProps = { id: string; name: string; - namespace?: string; }; diff --git a/web/src/components/dashboards/legacy/useLegacyDashboards.ts b/web/src/components/dashboards/legacy/useLegacyDashboards.ts index 62398af6..acc3237e 100644 --- a/web/src/components/dashboards/legacy/useLegacyDashboards.ts +++ b/web/src/components/dashboards/legacy/useLegacyDashboards.ts @@ -21,7 +21,8 @@ import { import { CombinedDashboardMetadata } from '../perses/hooks/useDashboardsData'; import { useNavigate } from 'react-router-dom-v5-compat'; import { QueryParams } from '../../query-params'; -import { NumberParam, StringParam, useQueryParam } from 'use-query-params'; +import { NumberParam, useQueryParam } from 'use-query-params'; +import { ALL_NAMESPACES_KEY } from '../../utils'; export const useLegacyDashboards = (namespace: string, urlBoard: string) => { const { t } = useTranslation('plugin__monitoring-plugin'); @@ -31,18 +32,11 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { const safeFetch = useCallback(useSafeFetch(), []); const [legacyDashboards, setLegacyDashboards] = useState([]); const [legacyDashboardsError, setLegacyDashboardsError] = useState(); - const [dashboardParam] = useQueryParam(QueryParams.Dashboard, StringParam); const [refreshInterval] = useQueryParam(QueryParams.RefreshInterval, NumberParam); const [legacyDashboardsLoading, , , setLegacyDashboardsLoaded] = useBoolean(true); - const [initialLoad, , , setInitialLoaded] = useBoolean(true); + const [initialLoad, , setInitialUnloaded, setInitialLoaded] = useBoolean(true); const dispatch = useDispatch(); const navigate = useNavigate(); - const legacyDashboard = useMemo(() => { - if (perspective === 'dev') { - return dashboardParam; - } - return urlBoard; - }, [perspective, dashboardParam, urlBoard]); useEffect(() => { safeFetch('/api/console/monitoring-dashboard-config') @@ -50,7 +44,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { setLegacyDashboardsLoaded(); setLegacyDashboardsError(undefined); let items = response.items; - if (namespace) { + if (namespace && namespace !== ALL_NAMESPACES_KEY) { items = _.filter( items, (item) => item.metadata?.labels['console.openshift.io/odc-dashboard'] === 'true', @@ -83,7 +77,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { }, [namespace, safeFetch, setLegacyDashboardsLoaded, t]); const legacyRows = useMemo(() => { - const data = _.find(legacyDashboards, { name: legacyDashboard })?.data; + const data = _.find(legacyDashboards, { name: urlBoard })?.data; return data?.rows?.length ? data.rows @@ -101,17 +95,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { } return acc; }, []) ?? []; - }, [legacyDashboard, legacyDashboards]); - - useEffect(() => { - // Dashboard query argument is only set in dev perspective, so skip for admin - if (perspective !== 'dev') { - return; - } - const allVariables = getAllVariables(legacyDashboards, legacyDashboard, namespace); - dispatch(dashboardsPatchAllVariables(allVariables)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [namespace, legacyDashboard]); + }, [urlBoard, legacyDashboards]); // Homogenize data needed for dashboards dropdown between legacy and perses dashboards // to enable both to use the same component @@ -140,10 +124,9 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { const queryArguments = getAllQueryArguments(); const params = new URLSearchParams(queryArguments); - let url = getLegacyDashboardsUrl(perspective, newBoard, namespace); - url = `${url}${perspective === 'dev' ? '&' : '?'}${params.toString()}`; + const url = `${getLegacyDashboardsUrl(perspective, newBoard)}?${params.toString()}`; - if (newBoard !== legacyDashboard || initialLoad) { + if (newBoard !== urlBoard || initialLoad) { if (params.get(QueryParams.Dashboard) !== newBoard) { navigate(url, { replace: true }); } @@ -165,7 +148,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { }, [ perspective, - legacyDashboard, + urlBoard, dispatch, navigate, namespace, @@ -177,15 +160,24 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { useEffect(() => { if ( - (!legacyDashboard || - !legacyDashboards.some((legacyBoard) => legacyBoard.name === legacyDashboard) || + (!urlBoard || + !legacyDashboards.some((legacyBoard) => legacyBoard.name === urlBoard) || initialLoad) && !_.isEmpty(legacyDashboards) ) { - changeLegacyDashboard(legacyDashboard || legacyDashboards?.[0]?.name); + changeLegacyDashboard(urlBoard || legacyDashboards?.[0]?.name); setInitialLoaded(); } - }, [legacyDashboards, changeLegacyDashboard, initialLoad, setInitialLoaded, legacyDashboard]); + }, [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]); // Clear variables on unmount useEffect(() => { @@ -201,7 +193,7 @@ export const useLegacyDashboards = (namespace: string, urlBoard: string) => { legacyRows, legacyDashboardsMetadata, changeLegacyDashboard, - legacyDashboard, + legacyDashboard: urlBoard, }; }; @@ -227,11 +219,14 @@ const getAllVariables = (boards: Board[], newBoardName: string, namespace: strin allVariables[v.name] = { datasource: v.datasource, includeAll: !!v.includeAll, - isHidden: namespace && v.name === 'namespace' ? true : v.hide !== 0, - isLoading: namespace ? v.type === 'query' && !namespace : v.type === 'query', + isHidden: v.name === 'namespace' && namespace !== ALL_NAMESPACES_KEY ? true : v.hide !== 0, + isLoading: v.name === 'namespace' ? false : v.type === 'query', options: _.map(v.options, 'value'), query: v.type === 'query' ? v.query : undefined, - value: namespace && v.name === 'namespace' ? namespace : value || v.options?.[0]?.value, + value: + v.name === 'namespace' && namespace !== ALL_NAMESPACES_KEY + ? namespace + : value || v.options?.[0]?.value, }; } }); diff --git a/web/src/components/dashboards/legacy/useOpenshiftProject.ts b/web/src/components/dashboards/legacy/useOpenshiftProject.ts new file mode 100644 index 00000000..10a8cfb6 --- /dev/null +++ b/web/src/components/dashboards/legacy/useOpenshiftProject.ts @@ -0,0 +1,74 @@ +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/dashboards/perses/dashboard-page.tsx b/web/src/components/dashboards/perses/dashboard-page.tsx index d8e3be2d..d2d28a03 100644 --- a/web/src/components/dashboards/perses/dashboard-page.tsx +++ b/web/src/components/dashboards/perses/dashboard-page.tsx @@ -1,8 +1,6 @@ import { Overview } from '@openshift-console/dynamic-plugin-sdk'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { FC } from 'react'; -import { QueryParamProvider } from 'use-query-params'; -import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; import { LoadingInline } from '../../console/console-shared/src/components/loading/LoadingInline'; import { PersesWrapper } from './PersesWrapper'; import { DashboardSkeleton } from './dashboard-skeleton'; @@ -65,11 +63,9 @@ const MonitoringDashboardsPage_: FC = () => { const MonitoringDashboardsPageWrapper: FC = () => { return ( - - - - - + + + ); }; diff --git a/web/src/components/dashboards/shared/dashboard-dropdown.tsx b/web/src/components/dashboards/shared/dashboard-dropdown.tsx index d00ca5ba..bbaa5e40 100644 --- a/web/src/components/dashboards/shared/dashboard-dropdown.tsx +++ b/web/src/components/dashboards/shared/dashboard-dropdown.tsx @@ -9,7 +9,7 @@ import { StackItem, } from '@patternfly/react-core'; import type { FC } from 'react'; -import { memo } from 'react'; +import { memo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { SingleTypeaheadDropdown } from '../../console/utils/single-typeahead-dropdown'; @@ -61,6 +61,12 @@ export const DashboardDropdown: FC = ({ items, onChange, children: item.title, })); + useEffect(() => { + if (items.filter((item) => item.name === selectedKey).length === 0) { + onChange(items.at(0)?.name); + } + }, [items, selectedKey, onChange]); + return ( diff --git a/web/src/components/hooks/usePerspective.tsx b/web/src/components/hooks/usePerspective.tsx index e609b4cf..c090bb72 100644 --- a/web/src/components/hooks/usePerspective.tsx +++ b/web/src/components/hooks/usePerspective.tsx @@ -5,7 +5,6 @@ import * as _ from 'lodash-es'; import { ALERTMANAGER_BASE_PATH, ALERTMANAGER_PROXY_PATH, - ALERTMANAGER_TENANCY_BASE_PATH, AlertResource, labelsToParams, MonitoringPlugins, @@ -62,161 +61,124 @@ export const usePerspective = (): usePerspectiveReturn => { } }; -export const getAlertsUrl = (perspective: Perspective, namespace?: string) => { +export const getAlertsUrl = (perspective: Perspective) => { switch (perspective) { case 'acm': return `/multicloud${AlertResource.url}`; - case 'admin': - return AlertResource.url; case 'virtualization-perspective': return `/virt-monitoring/alerts`; - case 'dev': + case 'admin': default: - return `/dev-monitoring/ns/${namespace}/alerts`; + return AlertResource.url; } }; // There is no equivalent rules list page in the developer perspective -export const getAlertRulesUrl = (perspective: Perspective, namespace?: string) => { +export const getAlertRulesUrl = (perspective: Perspective) => { switch (perspective) { case 'acm': return `/multicloud${RuleResource.url}`; case 'virtualization-perspective': return `/virt-monitoring/alertrules`; - case 'dev': - return `/dev-monitoring/ns/${namespace}/alertrules`; case 'admin': default: return RuleResource.url; } }; -export const getSilencesUrl = (perspective: Perspective, namespace?: string) => { +export const getSilencesUrl = (perspective: Perspective) => { switch (perspective) { case 'acm': return `/multicloud${SilenceResource.url}`; - case 'admin': - return SilenceResource.url; case 'virtualization-perspective': return `/virt-monitoring/silences`; - case 'dev': + case 'admin': default: - return `/dev-monitoring/ns/${namespace}/silences`; + return SilenceResource.url; } }; -export const getNewSilenceAlertUrl = ( - perspective: Perspective, - alert: PrometheusAlert, - namespace?: string, -) => { +export const getNewSilenceAlertUrl = (perspective: Perspective, alert: PrometheusAlert) => { switch (perspective) { case 'acm': return `/multicloud${SilenceResource.url}/~new?${labelsToParams(alert.labels)}`; - case 'admin': - return `${SilenceResource.url}/~new?${labelsToParams(alert.labels)}`; case 'virtualization-perspective': return `/virt-monitoring/silences/~new?${labelsToParams(alert.labels)}`; - case 'dev': + case 'admin': default: - return `/dev-monitoring/ns/${namespace}/silences/~new?${labelsToParams(alert.labels)}`; + return `${SilenceResource.url}/~new?${labelsToParams(alert.labels)}`; } }; -export const getNewSilenceUrl = (perspective: Perspective, namespace?: string) => { +export const getNewSilenceUrl = (perspective: Perspective) => { switch (perspective) { case 'acm': return `/multicloud${SilenceResource.url}/~new`; case 'virtualization-perspective': return `/virt-monitoring/silences/~new`; case 'admin': - return `${SilenceResource.url}/~new`; - case 'dev': default: - return `/dev-monitoring/ns/${namespace}/silences/~new`; + return `${SilenceResource.url}/~new`; } }; -export const getRuleUrl = (perspective: Perspective, rule: Rule, namespace?: string) => { +export const getRuleUrl = (perspective: Perspective, rule: Rule) => { switch (perspective) { case 'acm': return `/multicloud${RuleResource.url}/${_.get(rule, 'id')}`; case 'virtualization-perspective': return `/virt-monitoring/alertrules/${rule?.id}`; case 'admin': - return `${RuleResource.url}/${_.get(rule, 'id')}`; - case 'dev': default: - return `/dev-monitoring/ns/${namespace}/rules/${rule?.id}`; + return `${RuleResource.url}/${_.get(rule, 'id')}`; } }; -export const getSilenceAlertUrl = (perspective: Perspective, id: string, namespace?: string) => { +export const getSilenceAlertUrl = (perspective: Perspective, id: string) => { switch (perspective) { case 'acm': return `/multicloud${SilenceResource.url}/${id}`; case 'virtualization-perspective': return `/virt-monitoring/silences/${id}`; case 'admin': - return `${SilenceResource.url}/${id}`; - case 'dev': default: - return `/dev-monitoring/ns/${namespace}/silences/${id}`; + return `${SilenceResource.url}/${id}`; } }; -export const getEditSilenceAlertUrl = ( - perspective: Perspective, - id: string, - namespace?: string, -) => { +export const getEditSilenceAlertUrl = (perspective: Perspective, id: string) => { switch (perspective) { case 'acm': return `/multicloud${SilenceResource.url}/${id}/edit`; - case 'admin': - return `${SilenceResource.url}/${id}/edit`; case 'virtualization-perspective': return `/virt-monitoring/silences/${id}/edit`; - case 'dev': + case 'admin': default: - return `/dev-monitoring/ns/${namespace}/silences/${id}/edit`; + return `${SilenceResource.url}/${id}/edit`; } }; -export const getAlertUrl = ( - perspective: Perspective, - alert: PrometheusAlert, - ruleID: string, - namespace?: string, -) => { +export const getAlertUrl = (perspective: Perspective, alert: PrometheusAlert, ruleID: string) => { switch (perspective) { case 'acm': return `/multicloud${AlertResource.url}/${ruleID}?${labelsToParams(alert.labels)}`; case 'virtualization-perspective': return `/virt-monitoring/alerts/${ruleID}?${labelsToParams(alert.labels)}`; case 'admin': - return `${AlertResource.url}/${ruleID}?${labelsToParams(alert.labels)}`; - case 'dev': default: - return `/dev-monitoring/ns/${namespace}/alerts/${ruleID}?${labelsToParams(alert.labels)}`; + return `${AlertResource.url}/${ruleID}?${labelsToParams(alert.labels)}`; } }; -export const getFetchSilenceUrl = ( - perspective: Perspective, - silenceID: string, - namespace?: string, -) => { +export const getFetchSilenceUrl = (perspective: Perspective, silenceID: string) => { switch (perspective) { case 'acm': return `${ALERTMANAGER_PROXY_PATH}/api/v2/silence/${silenceID}`; - case 'admin': - return `${ALERTMANAGER_BASE_PATH}/api/v2/silence/${silenceID}`; case 'virtualization-perspective': return `${ALERTMANAGER_BASE_PATH}/api/v2/silence/${silenceID}`; - case 'dev': default: - return `${ALERTMANAGER_TENANCY_BASE_PATH}/api/v2/silence/${silenceID}?namespace=${namespace}`; + case 'admin': + return `${ALERTMANAGER_BASE_PATH}/api/v2/silence/${silenceID}`; } }; @@ -234,63 +196,45 @@ 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 'admin': - return `/monitoring/query-browser?query0=${encodeURIComponent(query)}${unitsQueryParam}`; - 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 'acm': - default: return ''; + case 'admin': + default: + return `/monitoring/query-browser?query0=${encodeURIComponent(query)}${unitsQueryParam}`; } }; -export const getMutlipleQueryBrowserUrl = ( - perspective: Perspective, - params: URLSearchParams, - namespace?: string, -) => { +export const getMutlipleQueryBrowserUrl = (perspective: Perspective, params: URLSearchParams) => { switch (perspective) { - case 'admin': - return `/monitoring/query-browser?${params.toString()}`; - case 'dev': - return `/dev-monitoring/ns/${namespace}/metrics?${params.toString()}`; case 'virtualization-perspective': return `/virt-monitoring/query-browser?${params.toString()}`; case 'acm': - default: return ''; + case 'admin': + default: + return `/monitoring/query-browser?${params.toString()}`; } }; -export const getLegacyDashboardsUrl = ( - perspective: Perspective, - boardName: string, - namespace?: string, -) => { +export const getLegacyDashboardsUrl = (perspective: Perspective, boardName: string) => { switch (perspective) { case 'virtualization-perspective': return `/virt-monitoring/dashboards/${boardName}`; - case 'admin': - return `/monitoring/dashboards/${boardName}`; - case 'dev': - return `/dev-monitoring/ns/${namespace}?dashboard=${boardName}`; case 'acm': - default: return ''; + case 'admin': + default: + return `/monitoring/dashboards/${boardName}`; } }; diff --git a/web/src/components/hooks/useQueryNamespace.ts b/web/src/components/hooks/useQueryNamespace.ts new file mode 100644 index 00000000..5ad85f8c --- /dev/null +++ b/web/src/components/hooks/useQueryNamespace.ts @@ -0,0 +1,25 @@ +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 +export const useQueryNamespace = () => { + const [queryNamespace, setQueryNamespace] = useQueryParam(QueryParams.Namespace, StringParam); + const [activeNamespace, setActiveNamespace] = useActiveNamespace(); + + useEffect(() => { + if (queryNamespace && activeNamespace !== queryNamespace) { + setActiveNamespace(queryNamespace); + } + if (!queryNamespace) { + setQueryNamespace(activeNamespace); + } + }, [queryNamespace, activeNamespace, setActiveNamespace]); + + return { + namespace: queryNamespace, + setNamespace: setQueryNamespace, + }; +}; diff --git a/web/src/components/query-params.ts b/web/src/components/query-params.ts index 0255bf2c..7a43ce4b 100644 --- a/web/src/components/query-params.ts +++ b/web/src/components/query-params.ts @@ -7,4 +7,7 @@ export enum QueryParams { Project = 'project', Namespace = 'namespace', Units = 'units', + // Use openshift-namespace query parameter for dashboards page since grafana variables cannot have + // a `-` character in their name + OpenshiftProject = 'openshift-project', } diff --git a/web/src/components/redirects/dev-redirects.tsx b/web/src/components/redirects/dev-redirects.tsx new file mode 100644 index 00000000..db2c8bd1 --- /dev/null +++ b/web/src/components/redirects/dev-redirects.tsx @@ -0,0 +1,85 @@ +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/components/prometheus-redirect-page.tsx b/web/src/components/redirects/prometheus-redirect-page.tsx similarity index 84% rename from web/src/components/prometheus-redirect-page.tsx rename to web/src/components/redirects/prometheus-redirect-page.tsx index d423acfe..b09d8c11 100644 --- a/web/src/components/prometheus-redirect-page.tsx +++ b/web/src/components/redirects/prometheus-redirect-page.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import { Navigate } from 'react-router-dom-v5-compat'; -import { getAllQueryArguments } from './console/utils/router'; -import { usePerspective } from './hooks/usePerspective'; +import { getAllQueryArguments } from '../console/utils/router'; +import { usePerspective } from '../hooks/usePerspective'; // Handles links that have the Prometheus UI's URL format (expected for links in alerts sent by // Alertmanager). The Prometheus UI specifies the PromQL query with the GET param `g0.expr`, so we diff --git a/web/src/contexts/MonitoringContext.tsx b/web/src/contexts/MonitoringContext.tsx index 66858757..7f7d7a62 100644 --- a/web/src/contexts/MonitoringContext.tsx +++ b/web/src/contexts/MonitoringContext.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { MonitoringPlugins, Prometheus } from '../components/utils'; +import { QueryParamProvider } from 'use-query-params'; +import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; type MonitoringContextType = { /** Dictates which plugin this code is being run in */ @@ -16,4 +18,8 @@ export const MonitoringContext = React.createContext({ export const MonitoringProvider: React.FC<{ monitoringContext: MonitoringContextType }> = ({ children, monitoringContext, -}) => {children}; +}) => ( + + {children} + +); diff --git a/web/src/e2e-tests-app.tsx b/web/src/e2e-tests-app.tsx index c5a06ae3..ddb60590 100644 --- a/web/src/e2e-tests-app.tsx +++ b/web/src/e2e-tests-app.tsx @@ -3,8 +3,6 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter, Link, Route, Routes } from 'react-router-dom-v5-compat'; import { combineReducers, createStore } from 'redux'; -import { QueryParamProvider } from 'use-query-params'; -import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; import { MpCmoAlertingPage } from './components/alerting/AlertingPage'; import { MpCmoAlertRulesDetailsPage } from './components/alerting/AlertRulesDetailsPage'; import { MpCmoAlertRulesPage } from './components/alerting/AlertRulesPage'; @@ -16,7 +14,7 @@ import { MpCmoSilencesDetailsPage } from './components/alerting/SilencesDetailsP import { MpCmoSilencesPage } from './components/alerting/SilencesPage'; import { MpCmoLegacyDashboardsPage } from './components/dashboards/legacy/legacy-dashboard-page'; import { MpCmoMetricsPage } from './components/MetricsPage'; -import PrometheusRedirectPage from './components/prometheus-redirect-page'; +import PrometheusRedirectPage from './components/redirects/prometheus-redirect-page'; import { MpCmoTargetsPage } from './components/targets-page'; import i18n from './i18n'; import ObserveReducers from './store/reducers'; @@ -37,31 +35,29 @@ const App = () => ( Dashboards Targets - - - } /> + + } /> - } /> - } /> + } /> + } /> - } /> - } /> + } /> + } /> - } /> - } /> + } /> + } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> - }> - } /> - } /> - } /> - - - + }> + } /> + } /> + } /> + + ); diff --git a/web/src/hooks/useAlerts.ts b/web/src/hooks/useAlerts.ts index f48f8e15..1a3ec6c7 100644 --- a/web/src/hooks/useAlerts.ts +++ b/web/src/hooks/useAlerts.ts @@ -150,7 +150,12 @@ const useAlertsPoller = ({ const fetchDispatch = () => dispatch(fetchAlertingData(prometheus, namespace, rulesUrl, alertsSource, silencesUrl)); - usePoll(fetchDispatch, POLLING_INTERVAL_MS); + const dependencies = useMemo( + () => [namespace, rulesUrl, silencesUrl], + [namespace, rulesUrl, silencesUrl], + ); + + usePoll(fetchDispatch, POLLING_INTERVAL_MS, dependencies); return { trigger: fetchDispatch }; };