Skip to content
Merged
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-12763-fixed-1756132506947.md
Original file line number Diff line number Diff line change
@@ -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))
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -321,15 +321,15 @@ 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();
expect(noticeText).to.include(noticeMessageShort);
});
});
cy.findByText(' upcoming', { exact: false })
.closest('[data-testid="notice-warning"]')
.closest('[data-testid="maintenance-banner"]')
.should('be.visible')
.within(() => {
cy.get('p').then(($el) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<MaintenanceBannerV2 />, {
initialRoute: '/maintenance',
});

// 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(<MaintenanceBannerV2 />, {
initialRoute: '/dashboard',
});

// Should show the maintenance banner AND the maintenance link section
getByTestId('maintenance-link-section');
});
});
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
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';
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 === '/account/maintenance';
const hideAccountMaintenanceLink = location.pathname === '/maintenance';

// Filter out platform maintenance, since that is handled separately
const linodeMaintenance =
Expand All @@ -30,19 +32,19 @@ export const MaintenanceBannerV2 = ({ pathname }: { pathname?: string }) => {

return (
maintenanceLinodes.size > 0 && (
<Notice variant="warning">
<Notice data-testid="maintenance-banner" variant="warning">
<Typography>
<strong>
{pluralize('Linode', 'Linodes', maintenanceLinodes.size)}
</strong>{' '}
{maintenanceLinodes.size === 1 ? 'has' : 'have'} upcoming{' '}
<strong>scheduled</strong> maintenance.
{!hideAccountMaintenanceLink && (
<>
<span data-testid="maintenance-link-section">
{' '}
For more details, view{' '}
<Link to="/account/maintenance">Account Maintenance</Link>.
</>
<Link to="/maintenance">Account Maintenance</Link>.
</span>
)}
</Typography>
</Notice>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<PlatformMaintenanceBanner />, {
initialRoute: '/maintenance',
});

// 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(<PlatformMaintenanceBanner />, {
initialRoute: '/dashboard',
});

// Should show the platform maintenance banner AND the maintenance link section
getByTestId('platform-maintenance-link-section');
});
});
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,20 +13,17 @@ 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 === '/account/maintenance';
const hideAccountMaintenanceLink = location.pathname === '/maintenance';

if (!accountHasPlatformMaintenance) return null;

return (
<Notice variant="warning">
<Notice data-testid="platform-maintenance-banner" variant="warning">
<Typography>
<strong>
{linodesWithPlatformMaintenance.size > 0
Expand All @@ -36,11 +34,11 @@ export const PlatformMaintenanceBanner = ({
need{linodesWithPlatformMaintenance.size === 1 && 's'} to be rebooted
for critical platform maintenance.
{!hideAccountMaintenanceLink && (
<>
<span data-testid="platform-maintenance-link-section">
{' '}
See which Linodes are <strong>scheduled</strong> for reboot on the{' '}
<Link to="/account/maintenance">Account Maintenance</Link> page.
</>
<Link to="/maintenance">Account Maintenance</Link> page.
</span>
)}
</Typography>
</Notice>
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/dev-tools/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ export const LOCAL_STORAGE_MAINTENANCE_FORM_DATA_KEY =
export const LOCAL_STORAGE_NOTIFICATIONS_FORM_DATA_KEY =
'msw-notifications-form-data';

export const LOCAL_STORAGE_GRANTS_FORM_DATA_KEY = 'msw-grants-form-data';
export const LOCAL_STORAGE_GRANTS_FORM_DATA_KEY = 'msw-grants-form-data';
4 changes: 2 additions & 2 deletions packages/manager/src/features/Account/AccountLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export const AccountLanding = () => {

return (
<React.Fragment>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<DocumentTitleSegment segment="Account Settings" />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<Tabs index={tabIndex} onChange={handleTabChange}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const AccountSettingsLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<DocumentTitleSegment segment="Account Settings" />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<GlobalSettings />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ export const BillingLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<DocumentTitleSegment segment="Billing" />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<BillingDetail />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const LoginHistoryLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<AccountLogins />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const MaintenanceLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<AccountMaintenanceLanding />
</>
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/Quotas/QuotasLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const QuotasLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<Quotas />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const ServiceTransfersLanding = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<DocumentTitleSegment segment="Service Transfers" />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<EntityTransfersLanding />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const UsersAndGrants = () => {

return (
<>
<PlatformMaintenanceBanner pathname={location.pathname} />
<MaintenanceBannerV2 pathname={location.pathname} />
<PlatformMaintenanceBanner />
<MaintenanceBannerV2 />
<LandingHeader {...landingHeaderProps} spacingBottom={4} />
<UsersLanding />
</>
Expand Down