From 1b322340622ded4c47f1e94cfbcd7281bd134b54 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Mon, 25 Aug 2025 10:27:42 -0400 Subject: [PATCH 1/4] fix: [M3-10527] - Maintenance banner showing redundant links --- .../MaintenanceBannerV2.test.tsx | 46 ++++++++++++++++ .../MaintenanceBanner/MaintenanceBannerV2.tsx | 10 ++-- .../PlatformMaintenanceBanner.test.tsx | 54 +++++++++++++++++++ .../PlatformMaintenanceBanner.tsx | 10 ++-- 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx index a29cb207b90..e1665b81ef3 100644 --- a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx +++ b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx @@ -81,4 +81,50 @@ describe('MaintenanceBannerV2', () => { ) ).not.toBeInTheDocument(); }); + + it('does not show Account Maintenance link when pathname is /maintenance', () => { + const mockMaintenance = [ + accountMaintenanceFactory.build({ + type: 'reboot', + entity: { type: 'linode', id: 123 }, + reason: 'Scheduled maintenance', + status: 'pending', + description: 'scheduled', + }), + ]; + + queryMocks.useAllAccountMaintenanceQuery.mockReturnValue({ + data: mockMaintenance, + }); + + const { queryByTestId } = renderWithTheme( + + ); + + // Should show the maintenance banner but not the maintenance link section + expect(queryByTestId('maintenance-link-section')).not.toBeInTheDocument(); + }); + + it('shows Account Maintenance link when pathname is not /maintenance', () => { + const mockMaintenance = [ + accountMaintenanceFactory.build({ + type: 'reboot', + entity: { type: 'linode', id: 123 }, + reason: 'Scheduled maintenance', + status: 'pending', + description: 'scheduled', + }), + ]; + + queryMocks.useAllAccountMaintenanceQuery.mockReturnValue({ + data: mockMaintenance, + }); + + const { getByTestId } = renderWithTheme( + + ); + + // Should show the maintenance banner AND the maintenance link section + getByTestId('maintenance-link-section'); + }); }); diff --git a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx index fb10e4e65cf..d0c7ee504c2 100644 --- a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx +++ b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx @@ -14,7 +14,7 @@ export const MaintenanceBannerV2 = ({ pathname }: { pathname?: string }) => { PENDING_MAINTENANCE_FILTER ); - const hideAccountMaintenanceLink = pathname === '/account/maintenance'; + const hideAccountMaintenanceLink = pathname === '/maintenance'; // Filter out platform maintenance, since that is handled separately const linodeMaintenance = @@ -30,7 +30,7 @@ export const MaintenanceBannerV2 = ({ pathname }: { pathname?: string }) => { return ( maintenanceLinodes.size > 0 && ( - + {pluralize('Linode', 'Linodes', maintenanceLinodes.size)} @@ -38,11 +38,11 @@ export const MaintenanceBannerV2 = ({ pathname }: { pathname?: string }) => { {maintenanceLinodes.size === 1 ? 'has' : 'have'} upcoming{' '} scheduled maintenance. {!hideAccountMaintenanceLink && ( - <> + {' '} For more details, view{' '} - Account Maintenance. - + Account Maintenance. + )} diff --git a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx index e692f09bbeb..2bf0a9bdc9d 100644 --- a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx +++ b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx @@ -104,4 +104,58 @@ describe('PlatformMaintenanceBanner', () => { ) ).toBeVisible(); }); + + it('does not show Account Maintenance link when pathname is /maintenance', () => { + const mockPlatformMaintenance = accountMaintenanceFactory.buildList(1, { + type: 'reboot', + entity: { type: 'linode' }, + reason: 'Your Linode needs a critical security update', + }); + + queryMocks.useAllAccountMaintenanceQuery.mockReturnValue({ + data: mockPlatformMaintenance, + }); + + queryMocks.useNotificationsQuery.mockReturnValue({ + data: notificationFactory.buildList(1, { + type: 'security_reboot_maintenance_scheduled', + label: 'Platform Maintenance Scheduled', + }), + }); + + const { queryByTestId } = renderWithTheme( + + ); + + // Should show the platform maintenance banner but not the maintenance link section + expect( + queryByTestId('platform-maintenance-link-section') + ).not.toBeInTheDocument(); + }); + + it('shows Account Maintenance link when pathname is not /maintenance', () => { + const mockPlatformMaintenance = accountMaintenanceFactory.buildList(1, { + type: 'reboot', + entity: { type: 'linode' }, + reason: 'Your Linode needs a critical security update', + }); + + queryMocks.useAllAccountMaintenanceQuery.mockReturnValue({ + data: mockPlatformMaintenance, + }); + + queryMocks.useNotificationsQuery.mockReturnValue({ + data: notificationFactory.buildList(1, { + type: 'security_reboot_maintenance_scheduled', + label: 'Platform Maintenance Scheduled', + }), + }); + + const { getByTestId } = renderWithTheme( + + ); + + // Should show the platform maintenance banner AND the maintenance link section + getByTestId('platform-maintenance-link-section'); + }); }); diff --git a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx index 2ee52c46423..e3ac7ea3dc2 100644 --- a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx +++ b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx @@ -20,12 +20,12 @@ export const PlatformMaintenanceBanner = ({ const { accountHasPlatformMaintenance, linodesWithPlatformMaintenance } = usePlatformMaintenance(); - const hideAccountMaintenanceLink = pathname === '/account/maintenance'; + const hideAccountMaintenanceLink = pathname === '/maintenance'; if (!accountHasPlatformMaintenance) return null; return ( - + {linodesWithPlatformMaintenance.size > 0 @@ -36,11 +36,11 @@ export const PlatformMaintenanceBanner = ({ need{linodesWithPlatformMaintenance.size === 1 && 's'} to be rebooted for critical platform maintenance. {!hideAccountMaintenanceLink && ( - <> + {' '} See which Linodes are scheduled for reboot on the{' '} - Account Maintenance page. - + Account Maintenance page. + )} From 622a746c3880d9a8b3157e412e8d38bbe16a4db7 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Mon, 25 Aug 2025 10:35:07 -0400 Subject: [PATCH 2/4] Added changeset: Prevent maintenance banner links from showing when already on /maintenance route --- packages/manager/.changeset/pr-12763-fixed-1756132506947.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-12763-fixed-1756132506947.md diff --git a/packages/manager/.changeset/pr-12763-fixed-1756132506947.md b/packages/manager/.changeset/pr-12763-fixed-1756132506947.md new file mode 100644 index 00000000000..00e29a5de23 --- /dev/null +++ b/packages/manager/.changeset/pr-12763-fixed-1756132506947.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Prevent maintenance banner links from showing when already on /maintenance route ([#12763](https://github.com/linode/manager/pull/12763)) From 1abf3f0bd836a58acfb174e4667f1dc1d4fb98ef Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Tue, 26 Aug 2025 09:56:02 -0400 Subject: [PATCH 3/4] Remove pathname for useLocation --- .../MaintenanceBanner/MaintenanceBannerV2.test.tsx | 12 ++++++------ .../MaintenanceBanner/MaintenanceBannerV2.tsx | 6 ++++-- .../PlatformMaintenanceBanner.test.tsx | 12 ++++++------ .../PlatformMaintenanceBanner.tsx | 10 ++++------ .../manager/src/features/Account/AccountLanding.tsx | 4 ++-- .../Billing/BillingLanding/BillingLanding.tsx | 4 ++-- .../features/LoginHistory/LoginHistoryLanding.tsx | 4 ++-- .../src/features/Maintenance/MaintenanceLanding.tsx | 4 ++-- .../manager/src/features/Quotas/QuotasLanding.tsx | 4 ++-- .../ServiceTransfers/ServiceTransfersLanding.tsx | 4 ++-- .../src/features/Settings/SettingsLanding.tsx | 4 ++-- .../src/features/UsersAndGrants/UsersAndGrants.tsx | 4 ++-- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx index e1665b81ef3..a8e6cef1d13 100644 --- a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx +++ b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.test.tsx @@ -97,9 +97,9 @@ describe('MaintenanceBannerV2', () => { data: mockMaintenance, }); - const { queryByTestId } = renderWithTheme( - - ); + const { queryByTestId } = renderWithTheme(, { + initialRoute: '/maintenance', + }); // Should show the maintenance banner but not the maintenance link section expect(queryByTestId('maintenance-link-section')).not.toBeInTheDocument(); @@ -120,9 +120,9 @@ describe('MaintenanceBannerV2', () => { data: mockMaintenance, }); - const { getByTestId } = renderWithTheme( - - ); + const { getByTestId } = renderWithTheme(, { + initialRoute: '/dashboard', + }); // Should show the maintenance banner AND the maintenance link section getByTestId('maintenance-link-section'); diff --git a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx index d0c7ee504c2..ae4dafee713 100644 --- a/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx +++ b/packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx @@ -1,6 +1,7 @@ import { useAllAccountMaintenanceQuery } from '@linode/queries'; import { Notice, Typography } from '@linode/ui'; import { pluralize } from '@linode/utilities'; +import { useLocation } from '@tanstack/react-router'; import React from 'react'; import { PENDING_MAINTENANCE_FILTER } from 'src/features/Account/Maintenance/utilities'; @@ -8,13 +9,14 @@ import { isPlatformMaintenance } from 'src/hooks/usePlatformMaintenance'; import { Link } from '../Link'; -export const MaintenanceBannerV2 = ({ pathname }: { pathname?: string }) => { +export const MaintenanceBannerV2 = () => { const { data: allMaintenance } = useAllAccountMaintenanceQuery( {}, PENDING_MAINTENANCE_FILTER ); + const location = useLocation(); - const hideAccountMaintenanceLink = pathname === '/maintenance'; + const hideAccountMaintenanceLink = location.pathname === '/maintenance'; // Filter out platform maintenance, since that is handled separately const linodeMaintenance = diff --git a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx index 2bf0a9bdc9d..306e83e4635 100644 --- a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx +++ b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.test.tsx @@ -123,9 +123,9 @@ describe('PlatformMaintenanceBanner', () => { }), }); - const { queryByTestId } = renderWithTheme( - - ); + const { queryByTestId } = renderWithTheme(, { + initialRoute: '/maintenance', + }); // Should show the platform maintenance banner but not the maintenance link section expect( @@ -151,9 +151,9 @@ describe('PlatformMaintenanceBanner', () => { }), }); - const { getByTestId } = renderWithTheme( - - ); + const { getByTestId } = renderWithTheme(, { + initialRoute: '/dashboard', + }); // Should show the platform maintenance banner AND the maintenance link section getByTestId('platform-maintenance-link-section'); diff --git a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx index e3ac7ea3dc2..27cbfbe5143 100644 --- a/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx +++ b/packages/manager/src/components/PlatformMaintenanceBanner/PlatformMaintenanceBanner.tsx @@ -1,4 +1,5 @@ import { Notice, Typography } from '@linode/ui'; +import { useLocation } from '@tanstack/react-router'; import React from 'react'; import { usePlatformMaintenance } from 'src/hooks/usePlatformMaintenance'; @@ -12,15 +13,12 @@ import { Link } from '../Link'; * them separately from the standard MaintenanceBanner. */ -export const PlatformMaintenanceBanner = ({ - pathname, -}: { - pathname?: string; -}) => { +export const PlatformMaintenanceBanner = () => { const { accountHasPlatformMaintenance, linodesWithPlatformMaintenance } = usePlatformMaintenance(); + const location = useLocation(); - const hideAccountMaintenanceLink = pathname === '/maintenance'; + const hideAccountMaintenanceLink = location.pathname === '/maintenance'; if (!accountHasPlatformMaintenance) return null; diff --git a/packages/manager/src/features/Account/AccountLanding.tsx b/packages/manager/src/features/Account/AccountLanding.tsx index 72fc66c37d4..8a702687d98 100644 --- a/packages/manager/src/features/Account/AccountLanding.tsx +++ b/packages/manager/src/features/Account/AccountLanding.tsx @@ -166,8 +166,8 @@ export const AccountLanding = () => { return ( - - + + diff --git a/packages/manager/src/features/Billing/BillingLanding/BillingLanding.tsx b/packages/manager/src/features/Billing/BillingLanding/BillingLanding.tsx index e6104199068..8cf69732bfd 100644 --- a/packages/manager/src/features/Billing/BillingLanding/BillingLanding.tsx +++ b/packages/manager/src/features/Billing/BillingLanding/BillingLanding.tsx @@ -105,8 +105,8 @@ export const BillingLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/LoginHistory/LoginHistoryLanding.tsx b/packages/manager/src/features/LoginHistory/LoginHistoryLanding.tsx index 69ffc219779..52674d71b35 100644 --- a/packages/manager/src/features/LoginHistory/LoginHistoryLanding.tsx +++ b/packages/manager/src/features/LoginHistory/LoginHistoryLanding.tsx @@ -28,8 +28,8 @@ export const LoginHistoryLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/Maintenance/MaintenanceLanding.tsx b/packages/manager/src/features/Maintenance/MaintenanceLanding.tsx index 7c4b14afe93..99880f510e4 100644 --- a/packages/manager/src/features/Maintenance/MaintenanceLanding.tsx +++ b/packages/manager/src/features/Maintenance/MaintenanceLanding.tsx @@ -28,8 +28,8 @@ export const MaintenanceLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/Quotas/QuotasLanding.tsx b/packages/manager/src/features/Quotas/QuotasLanding.tsx index f0aa7d4f89b..02d89d4c71d 100644 --- a/packages/manager/src/features/Quotas/QuotasLanding.tsx +++ b/packages/manager/src/features/Quotas/QuotasLanding.tsx @@ -34,8 +34,8 @@ export const QuotasLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/ServiceTransfers/ServiceTransfersLanding.tsx b/packages/manager/src/features/ServiceTransfers/ServiceTransfersLanding.tsx index e5dceef7ae3..1dfef2dcfe1 100644 --- a/packages/manager/src/features/ServiceTransfers/ServiceTransfersLanding.tsx +++ b/packages/manager/src/features/ServiceTransfers/ServiceTransfersLanding.tsx @@ -28,8 +28,8 @@ export const ServiceTransfersLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/Settings/SettingsLanding.tsx b/packages/manager/src/features/Settings/SettingsLanding.tsx index 0523eb58e79..3a26acdae10 100644 --- a/packages/manager/src/features/Settings/SettingsLanding.tsx +++ b/packages/manager/src/features/Settings/SettingsLanding.tsx @@ -28,8 +28,8 @@ export const SettingsLanding = () => { return ( <> - - + + diff --git a/packages/manager/src/features/UsersAndGrants/UsersAndGrants.tsx b/packages/manager/src/features/UsersAndGrants/UsersAndGrants.tsx index a118dbca18d..88218fcb8ee 100644 --- a/packages/manager/src/features/UsersAndGrants/UsersAndGrants.tsx +++ b/packages/manager/src/features/UsersAndGrants/UsersAndGrants.tsx @@ -34,8 +34,8 @@ export const UsersAndGrants = () => { return ( <> - - + + From 517d2363b48f9c3a818371ffeab53e0667ebacd4 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Thu, 28 Aug 2025 16:16:53 -0400 Subject: [PATCH 4/4] Fix specs --- .../qemu-reboot-upgrade-notice.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/manager/cypress/e2e/core/notificationsAndEvents/qemu-reboot-upgrade-notice.spec.ts b/packages/manager/cypress/e2e/core/notificationsAndEvents/qemu-reboot-upgrade-notice.spec.ts index d1bbfc963b8..8ceae07c5a0 100644 --- a/packages/manager/cypress/e2e/core/notificationsAndEvents/qemu-reboot-upgrade-notice.spec.ts +++ b/packages/manager/cypress/e2e/core/notificationsAndEvents/qemu-reboot-upgrade-notice.spec.ts @@ -109,7 +109,7 @@ describe('QEMU reboot upgrade notification', () => { // Confirm that the notice is visible and contains the expected message cy.findByText(NOTIFICATION_BANNER_TEXT, { exact: false }) .should('be.visible') - .closest('[data-testid="notice-warning"]') + .closest('[data-testid="platform-maintenance-banner"]') .within(() => { cy.get('p').then(($el) => { const noticeText = $el.text(); @@ -321,7 +321,7 @@ describe('QEMU reboot upgrade notification', () => { // Confirm that the notice is visible and contains the expected message cy.findByText(NOTIFICATION_BANNER_TEXT, { exact: false }) .should('be.visible') - .closest('[data-testid="notice-warning"]') + .closest('[data-testid="platform-maintenance-banner"]') .within(() => { cy.get('p').then(($el) => { const noticeText = $el.text(); @@ -329,7 +329,7 @@ describe('QEMU reboot upgrade notification', () => { }); }); cy.findByText(' upcoming', { exact: false }) - .closest('[data-testid="notice-warning"]') + .closest('[data-testid="maintenance-banner"]') .should('be.visible') .within(() => { cy.get('p').then(($el) => {