From 39f97ad15a3098442b4f6b8e46efd1ea1685dc13 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 9 Nov 2026 13:34:38 -0300 Subject: [PATCH 1/5] fix(Scheduling): Only count change requests that are still scheduled The Scheduling badge and page pinned live_from_after to mount time, so change requests published immediately after mount counted as scheduled. A dedicated getScheduledChangeRequests endpoint reads the time on every request, so the refetch after a commit uses the current time. Closes #8774 Co-Authored-By: Claude Opus 5.5 --- frontend/common/services/useChangeRequest.ts | 18 ++++++++++++++++ frontend/common/types/requests.ts | 3 +++ .../navigation/navbars/EnvironmentNavbar.tsx | 13 ++++++------ .../components/pages/ScheduledChangesPage.tsx | 21 ++++++++----------- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/frontend/common/services/useChangeRequest.ts b/frontend/common/services/useChangeRequest.ts index 472965928441..905b86aa3866 100644 --- a/frontend/common/services/useChangeRequest.ts +++ b/frontend/common/services/useChangeRequest.ts @@ -69,6 +69,23 @@ export const changeRequestService = service }), transformResponse: (res, _, req) => transformCorePaging(req, res), }), + getScheduledChangeRequests: builder.query< + Res['changeRequests'], + Req['getScheduledChangeRequests'] + >({ + providesTags: [{ id: 'LIST', type: 'ChangeRequest' }], + // Read the time per request, so a refetch after a commit uses the current time + query: ({ environmentId, ...rest }) => ({ + url: `environments/${environmentId}/list-change-requests/?${Utils.toParam( + { + ...rest, + committed: true, + live_from_after: new Date().toISOString(), + }, + )}`, + }), + transformResponse: (res, _, req) => transformCorePaging(req, res), + }), updateChangeRequest: builder.mutation< Res['updateChangeRequest'], Req['updateChangeRequest'] @@ -112,6 +129,7 @@ export const { useCreateEnvironmentChangeRequestMutation, useDeleteChangeRequestMutation, useGetChangeRequestsQuery, + useGetScheduledChangeRequestsQuery, useUpdateChangeRequestMutation, // END OF EXPORTS } = changeRequestService diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index a99864c3f2e8..860e21713e29 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -700,6 +700,9 @@ export type Req = { live_from_after?: string committed?: boolean }> + getScheduledChangeRequests: PagedRequest<{ + environmentId: string + }> updateChangeRequest: ChangeRequest getGroupSummaries: { orgId: number diff --git a/frontend/web/components/navigation/navbars/EnvironmentNavbar.tsx b/frontend/web/components/navigation/navbars/EnvironmentNavbar.tsx index bd1a4022e13f..cd1e35e0fe40 100644 --- a/frontend/web/components/navigation/navbars/EnvironmentNavbar.tsx +++ b/frontend/web/components/navigation/navbars/EnvironmentNavbar.tsx @@ -1,10 +1,13 @@ -import React, { FC, useRef } from 'react' +import React, { FC } from 'react' import Utils from 'common/utils/utils' import Permission from 'common/providers/Permission' import classNames from 'classnames' import { useGetEnvironmentsQuery } from 'common/services/useEnvironment' import SidebarLink from 'components/navigation/SidebarLink' -import { useGetChangeRequestsQuery } from 'common/services/useChangeRequest' +import { + useGetChangeRequestsQuery, + useGetScheduledChangeRequestsQuery, +} from 'common/services/useChangeRequest' import { useExperimentationEnabled } from 'common/hooks/useExperimentationEnabled' type EnvironmentNavType = { @@ -20,8 +23,6 @@ const EnvironmentNavbar: FC = ({ mobile, projectId, }) => { - const date = useRef(moment().toISOString()) - const { data: environments } = useGetEnvironmentsQuery( { projectId: `${projectId}`, @@ -29,10 +30,8 @@ const EnvironmentNavbar: FC = ({ { skip: !projectId }, ) - const { data: scheduledData } = useGetChangeRequestsQuery({ - committed: true, + const { data: scheduledData } = useGetScheduledChangeRequestsQuery({ environmentId, - live_from_after: date.current, page_size: 1, }) diff --git a/frontend/web/components/pages/ScheduledChangesPage.tsx b/frontend/web/components/pages/ScheduledChangesPage.tsx index 9a4ea1a2c9a2..cbe0f859249a 100644 --- a/frontend/web/components/pages/ScheduledChangesPage.tsx +++ b/frontend/web/components/pages/ScheduledChangesPage.tsx @@ -1,6 +1,6 @@ -import { FC, useEffect, useMemo, useState } from 'react' +import { FC, useEffect, useState } from 'react' import moment from 'moment/moment' -import { useGetChangeRequestsQuery } from 'common/services/useChangeRequest' +import { useGetScheduledChangeRequestsQuery } from 'common/services/useChangeRequest' import { RouterChildContext } from 'react-router' import AccountStore from 'common/stores/account-store' import { ChangeRequestSummary, User } from 'common/types/responses' @@ -37,16 +37,13 @@ const ScheduledChangesPage: FC = ({ match }) => { const organisation = OrganisationStore.model as any const users = organisation?.users as User[] - const live_from_after = useMemo(() => { - return new Date().toISOString() - }, []) - const { data: dataScheduled, isLoading } = useGetChangeRequestsQuery({ - committed: true, - environmentId, - live_from_after, - page, - page_size: 50, - }) + const { data: dataScheduled, isLoading } = useGetScheduledChangeRequestsQuery( + { + environmentId, + page, + page_size: 50, + }, + ) useEffect(() => { AppActions.getOrganisation(organisationId) }, [organisationId]) From 1c9bfca2f4247695105ff040e703c3643406c607 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 9 Nov 2026 13:39:44 -0300 Subject: [PATCH 2/5] test(Scheduling): Check the Scheduling badge stays empty after an immediate publish Co-Authored-By: Claude Opus 5.5 --- frontend/e2e/tests/change-request-test.pw.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/e2e/tests/change-request-test.pw.ts b/frontend/e2e/tests/change-request-test.pw.ts index a4274d3b44b4..a010b9fbb930 100644 --- a/frontend/e2e/tests/change-request-test.pw.ts +++ b/frontend/e2e/tests/change-request-test.pw.ts @@ -121,6 +121,11 @@ test.describe('Change Request Tests', () => { log('Close modal') await closeModal() + log('Verify the Scheduling link has no badge') + await expect(page.getByRole('link', { name: 'Scheduling' })).toHaveText( + 'Scheduling', + ) + log('Verify value has NOW changed (change request published)') await gotoFeatures() await page.reload({ waitUntil: 'domcontentloaded' }) From 3278a211eac06987918d3a5ee3815078620b3ff3 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 9 Nov 2026 14:43:49 -0300 Subject: [PATCH 3/5] fix(Scheduling): Refetch scheduled changes each time the page opens Co-Authored-By: Claude Opus 5.5 --- frontend/web/components/pages/ScheduledChangesPage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/web/components/pages/ScheduledChangesPage.tsx b/frontend/web/components/pages/ScheduledChangesPage.tsx index cbe0f859249a..60100298c199 100644 --- a/frontend/web/components/pages/ScheduledChangesPage.tsx +++ b/frontend/web/components/pages/ScheduledChangesPage.tsx @@ -43,6 +43,8 @@ const ScheduledChangesPage: FC = ({ match }) => { page, page_size: 50, }, + // Same args on every visit, so refetch to drop changes that went live meanwhile + { refetchOnMountOrArgChange: true }, ) useEffect(() => { AppActions.getOrganisation(organisationId) From 11a3cfeb282274db56f086bc95f5d4e9ac889415 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 9 Nov 2026 15:42:24 -0300 Subject: [PATCH 4/5] test(Scheduling): Wait for the refetch after publishing before checking the badge Co-Authored-By: Claude Opus 5.5 --- frontend/e2e/tests/change-request-test.pw.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/e2e/tests/change-request-test.pw.ts b/frontend/e2e/tests/change-request-test.pw.ts index a010b9fbb930..89710bbfe4c1 100644 --- a/frontend/e2e/tests/change-request-test.pw.ts +++ b/frontend/e2e/tests/change-request-test.pw.ts @@ -116,12 +116,21 @@ test.describe('Change Request Tests', () => { await approveChangeRequest() log('Publish change request') + const publishStartedAt = Date.now() + // Approving also refetches the badge, so only take a request sent after publishing + const scheduledRefetch = page.waitForResponse( + (res) => + res.url().includes('list-change-requests') && + res.url().includes('live_from_after') && + res.request().timing().startTime >= publishStartedAt, + ) await publishChangeRequest() log('Close modal') await closeModal() log('Verify the Scheduling link has no badge') + await scheduledRefetch await expect(page.getByRole('link', { name: 'Scheduling' })).toHaveText( 'Scheduling', ) From 6f8200ad7b75de6fa95074807d57139eec21a9a2 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 9 Nov 2026 16:12:31 -0300 Subject: [PATCH 5/5] Revert "test(Scheduling): Wait for the refetch after publishing before checking the badge" This reverts commit 11a3cfeb2. The timing code made a one-line regression check hard to read. The plain check already fails on main and passes with the fix. Co-Authored-By: Claude Opus 5.5 --- frontend/e2e/tests/change-request-test.pw.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/frontend/e2e/tests/change-request-test.pw.ts b/frontend/e2e/tests/change-request-test.pw.ts index 89710bbfe4c1..a010b9fbb930 100644 --- a/frontend/e2e/tests/change-request-test.pw.ts +++ b/frontend/e2e/tests/change-request-test.pw.ts @@ -116,21 +116,12 @@ test.describe('Change Request Tests', () => { await approveChangeRequest() log('Publish change request') - const publishStartedAt = Date.now() - // Approving also refetches the badge, so only take a request sent after publishing - const scheduledRefetch = page.waitForResponse( - (res) => - res.url().includes('list-change-requests') && - res.url().includes('live_from_after') && - res.request().timing().startTime >= publishStartedAt, - ) await publishChangeRequest() log('Close modal') await closeModal() log('Verify the Scheduling link has no badge') - await scheduledRefetch await expect(page.getByRole('link', { name: 'Scheduling' })).toHaveText( 'Scheduling', )