From 21270919bc892c06e5b40530411f6fbfc782dec3 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Fri, 3 Nov 2023 15:59:52 -0400 Subject: [PATCH 1/5] DEVPROD-1101: Update previous commits selector (#2120) --- src/gql/generated/types.ts | 4 + .../previousCommits/PreviousCommits.test.tsx | 192 ++++------- .../actionButtons/previousCommits/index.tsx | 312 +++++++----------- .../actionButtons/previousCommits/reducer.ts | 230 ------------- .../actionButtons/previousCommits/utils.ts | 56 ++++ 5 files changed, 252 insertions(+), 542 deletions(-) delete mode 100644 src/pages/task/actionButtons/previousCommits/reducer.ts create mode 100644 src/pages/task/actionButtons/previousCommits/utils.ts diff --git a/src/gql/generated/types.ts b/src/gql/generated/types.ts index 88b1794c22..2a74e58b89 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -936,6 +936,7 @@ export type MainlineCommitsOptions = { limit?: InputMaybe; projectIdentifier: Scalars["String"]["input"]; requesters?: InputMaybe>; + revision?: InputMaybe; shouldCollapse?: InputMaybe; skipOrderNumber?: InputMaybe; }; @@ -1503,6 +1504,7 @@ export type Patches = { */ export type PatchesInput = { includeCommitQueue?: InputMaybe; + includeHidden?: InputMaybe; limit?: Scalars["Int"]["input"]; onlyCommitQueue?: InputMaybe; page?: Scalars["Int"]["input"]; @@ -2105,6 +2107,7 @@ export type RepoRef = { manualPrTestingEnabled: Scalars["Boolean"]["output"]; notifyOnBuildFailure: Scalars["Boolean"]["output"]; owner: Scalars["String"]["output"]; + parsleyFilters?: Maybe>; patchTriggerAliases?: Maybe>; patchingDisabled: Scalars["Boolean"]["output"]; perfEnabled: Scalars["Boolean"]["output"]; @@ -2146,6 +2149,7 @@ export type RepoRefInput = { manualPrTestingEnabled?: InputMaybe; notifyOnBuildFailure?: InputMaybe; owner?: InputMaybe; + parsleyFilters?: InputMaybe>; patchTriggerAliases?: InputMaybe>; patchingDisabled?: InputMaybe; perfEnabled?: InputMaybe; diff --git a/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx b/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx index b4a0601d08..e8dbdfa1dd 100644 --- a/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx +++ b/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx @@ -11,15 +11,12 @@ import { renderWithRouterMatch, screen, userEvent, waitFor } from "test_utils"; import { ApolloMock } from "types/gql"; import { PreviousCommits } from "."; -const goButton = { name: "Go" }; -const select = { name: "Previous commits for this task" }; - describe("previous commits", () => { // Patch and mainline commit behavior only have a significant difference when it comes to determining // the base or previous task. Patch gets the base task directly from BASE_VERSION_AND_TASK, while // mainline commits needs to run another query LAST_MAINLINE_COMMIT to get previous task. describe("patch specific", () => { - it("the GO button is disabled when there is no base task", async () => { + it("the button is disabled when there is no base task", async () => { const { Component } = RenderFakeToastContext( @@ -27,25 +24,15 @@ describe("previous commits", () => { ); renderWithRouterMatch(); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "aria-disabled", - "true" - ); - }); - // Select won't be disabled because baseVersion exists - await waitFor(() => { - expect(screen.getByRole("button", select)).toHaveAttribute( - "aria-disabled", - "false" - ); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "true"); }); - // Should say "base" for patches - expect(screen.getByText("Go to base commit")).toBeInTheDocument(); }); }); describe("mainline commits specific", () => { - it("the GO button is disabled when getParentTask returns null", async () => { + it("the button is disabled when getParentTask returns null", async () => { const { Component } = RenderFakeToastContext( { renderWithRouterMatch(); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "aria-disabled", - "true" - ); - }); - // Select won't be disabled because baseVersion exists - await waitFor(() => { - expect(screen.getByRole("button", select)).toHaveAttribute( - "aria-disabled", - "false" - ); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "true"); }); - // Should say "previous" for versions - expect(screen.getByText("Go to previous commit")).toBeInTheDocument(); }); - it("the GO button is disabled when getParentTask returns an error", async () => { + it("the button is disabled when getParentTask returns an error", async () => { const { Component } = RenderFakeToastContext( { renderWithRouterMatch(); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "aria-disabled", - "true" - ); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "true"); }); - // Select won't be disabled because baseVersion exists - await waitFor(() => { - expect(screen.getByRole("button", select)).toHaveAttribute( - "aria-disabled", - "false" - ); - }); - // Should say "previous" for versions - expect(screen.getByText("Go to previous commit")).toBeInTheDocument(); }); }); - it("the select & GO button are disabled when no base version exists", async () => { + it("the button is disabled when no base version exists", async () => { const { Component } = RenderFakeToastContext( @@ -109,50 +76,45 @@ describe("previous commits", () => { renderWithRouterMatch(); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "aria-disabled", - "true" - ); - }); - await waitFor(() => { - expect(screen.getByRole("button", select)).toHaveAttribute( - "aria-disabled", - "true" - ); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "true"); }); }); - it("when base task is passing, all dropdown items generate the same link.", async () => { + it("when base task is passing, all dropdown items generate the same link", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( - + ); renderWithRouterMatch(); + await screen.findByRole("button", { name: "Previous commits" }); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Previous commits" })); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); + expect(screen.getByRole("menu")).toBeVisible(); }); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last passing version" }) - ); - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last executed version" }) - ); - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); + + expect( + screen.getByRole("menuitem", { name: "Go to base commit" }) + ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to last passing version" }) + ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to last executed version" }) + ).toHaveAttribute("href", baseTaskHref); }); it("when base task is failing, 'Go to base commit' and 'Go to last executed' dropdown items generate the same link and 'Go to last passing version' will be different.", async () => { @@ -166,30 +128,24 @@ describe("previous commits", () => { ); renderWithRouterMatch(); + await screen.findByRole("button", { name: "Previous commits" }); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Previous commits" })); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); - }); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last executed version" }) - ); - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last passing version" }) - ); - await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - "/task/last_passing_task" - ); + expect(screen.getByRole("menu")).toBeVisible(); }); + + expect( + screen.getByRole("menuitem", { name: "Go to base commit" }) + ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to last passing version" }) + ).toHaveAttribute("href", "/task/last_passing_task"); + expect( + screen.getByRole("menuitem", { name: "Go to last executed version" }) + ).toHaveAttribute("href", baseTaskHref); }); it("when base task is not in a finished state, the last executed & passing task is not the same as the base commit", async () => { @@ -207,28 +163,24 @@ describe("previous commits", () => { ); renderWithRouterMatch(); + await screen.findByRole("button", { name: "Previous commits" }); + expect( + screen.getByRole("button", { name: "Previous commits" }) + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Previous commits" })); await waitFor(() => { - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - baseTaskHref - ); + expect(screen.getByRole("menu")).toBeVisible(); }); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last executed version" }) - ); - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - "/task/last_executed_task" - ); - await user.click(screen.getByRole("button", select)); - await user.click( - screen.getByRole("option", { name: "Go to last passing version" }) - ); - expect(screen.getByRole("link", goButton)).toHaveAttribute( - "href", - "/task/last_passing_task" - ); + + expect( + screen.getByRole("menuitem", { name: "Go to base commit" }) + ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to last passing version" }) + ).toHaveAttribute("href", "/task/last_passing_task"); + expect( + screen.getByRole("menuitem", { name: "Go to last executed version" }) + ).toHaveAttribute("href", "/task/last_executed_task"); }); }); diff --git a/src/pages/task/actionButtons/previousCommits/index.tsx b/src/pages/task/actionButtons/previousCommits/index.tsx index bca43f6812..770add33a5 100644 --- a/src/pages/task/actionButtons/previousCommits/index.tsx +++ b/src/pages/task/actionButtons/previousCommits/index.tsx @@ -1,13 +1,12 @@ -import { useReducer, useEffect } from "react"; -import { useLazyQuery, useQuery } from "@apollo/client"; -import styled from "@emotion/styled"; -import { Option, Select } from "@leafygreen-ui/select"; +import { useMemo } from "react"; +import { useQuery } from "@apollo/client"; +import Button, { Size } from "@leafygreen-ui/button"; +import { Menu, MenuItem } from "@leafygreen-ui/menu"; import Tooltip from "@leafygreen-ui/tooltip"; +import { Link } from "react-router-dom"; import { useTaskAnalytics } from "analytics"; -import { LoadingButton } from "components/Buttons"; -import { ConditionalWrapper } from "components/ConditionalWrapper"; +import Icon from "components/Icon"; import { finishedTaskStatuses } from "constants/task"; -import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { BaseVersionAndTaskQuery, @@ -16,32 +15,19 @@ import { LastMainlineCommitQueryVariables, } from "gql/generated/types"; import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; -import { useLGButtonRouterLink } from "hooks/useLGButtonRouterLink"; import { TaskStatus } from "types/task"; -import { string } from "utils"; -import { reportError } from "utils/errorReporting"; -import { initialState, reducer } from "./reducer"; -import { CommitTask, CommitType } from "./types"; +import { statuses, string } from "utils"; +import { CommitType } from "./types"; +import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; const { applyStrictRegex } = string; +const { isFinishedTaskStatus } = statuses; interface PreviousCommitsProps { taskId: string; } export const PreviousCommits: React.FC = ({ taskId }) => { const { sendEvent } = useTaskAnalytics(); - const [ - { - disableButton, - hasFetchedLastExecuted, - hasFetchedLastPassing, - link, - selectState, - shouldFetchLastExecuted, - shouldFetchLastPassing, - }, - dispatch, - ] = useReducer(reducer, initialState); const dispatchToast = useToastContext(); const { data: taskData } = useQuery< @@ -51,44 +37,62 @@ export const PreviousCommits: React.FC = ({ taskId }) => { variables: { taskId }, }); - // We don't error for this query because it is the default query that is run when the page loads. - // If it errors it probably means there is no base version, which is fine. - const [fetchParentTask, { loading: parentLoading }] = useLazyQuery< + const { baseTask, buildVariant, displayName, versionMetadata } = + taskData?.task ?? {}; + const { order: skipOrderNumber, projectIdentifier } = + versionMetadata?.baseVersion ?? {}; + const bvOptionsBase = { + tasks: [applyStrictRegex(displayName)], + variants: [applyStrictRegex(buildVariant)], + }; + + const { data: parentTaskData, loading: parentLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables >(LAST_MAINLINE_COMMIT, { - onCompleted: (data) => { - dispatch({ - type: "setParentTask", - task: getTaskFromMainlineCommitsQuery(data), - }); + skip: !versionMetadata || versionMetadata.isPatch, + variables: { + projectIdentifier, + skipOrderNumber, + buildVariantOptions: { + ...bvOptionsBase, + }, }, }); + const parentTask = + getTaskFromMainlineCommitsQuery(parentTaskData) ?? baseTask; - const [fetchLastPassing, { loading: passingLoading }] = useLazyQuery< + const { data: lastPassingTaskData, loading: passingLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables >(LAST_MAINLINE_COMMIT, { - onCompleted: (data) => { - dispatch({ - type: "setLastPassingTask", - task: getTaskFromMainlineCommitsQuery(data), - }); + skip: !parentTask || parentTask.status === TaskStatus.Succeeded, + variables: { + projectIdentifier, + skipOrderNumber, + buildVariantOptions: { + ...bvOptionsBase, + statuses: [TaskStatus.Succeeded], + }, }, onError: (err) => { dispatchToast.error(`Last passing version unavailable: '${err.message}'`); }, }); + const lastPassingTask = getTaskFromMainlineCommitsQuery(lastPassingTaskData); - const [fetchLastExecuted, { loading: executedLoading }] = useLazyQuery< + const { data: lastExecutedTaskData, loading: executedLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables >(LAST_MAINLINE_COMMIT, { - onCompleted: (data) => { - dispatch({ - type: "setLastExecutedTask", - task: getTaskFromMainlineCommitsQuery(data), - }); + skip: !parentTask || isFinishedTaskStatus(parentTask.status), + variables: { + projectIdentifier, + skipOrderNumber, + buildVariantOptions: { + ...bvOptionsBase, + statuses: finishedTaskStatuses, + }, }, onError: (err) => { dispatchToast.error( @@ -96,159 +100,83 @@ export const PreviousCommits: React.FC = ({ taskId }) => { ); }, }); + const lastExecutedTask = + getTaskFromMainlineCommitsQuery(lastExecutedTaskData); + + const linkObject = useMemo( + () => + getLinks({ + parentTask, + lastPassingTask, + lastExecutedTask, + }), + [parentTask, lastPassingTask, lastExecutedTask] + ); - const { baseTask, buildVariant, displayName, versionMetadata } = - taskData?.task ?? {}; - const { order: skipOrderNumber, projectIdentifier } = - versionMetadata?.baseVersion ?? {}; - const bvOptionsBase = { - tasks: [applyStrictRegex(displayName)], - variants: [applyStrictRegex(buildVariant)], - }; - const loading = parentLoading || passingLoading || executedLoading; + const menuDisabled = !baseTask || !parentTask; - // Hook to determine the parent task. If mainline commit, use fetchParentTask function to get task from - // previous mainline commit. Otherwise, just extract the base task from the task data. - useEffect(() => { - if (versionMetadata) { - if (!versionMetadata.isPatch) { - fetchParentTask({ - variables: { - projectIdentifier, - skipOrderNumber, - buildVariantOptions: { - ...bvOptionsBase, - }, - }, - }); - } else { - dispatch({ type: "setParentTask", task: baseTask }); + return menuDisabled ? ( + } + size={Size.Small} + > + Previous commits + } - } - }, [versionMetadata]); // eslint-disable-line react-hooks/exhaustive-deps - - // Hook that triggers fetching the last passing task if it needs to be fetched. - useEffect(() => { - if (!hasFetchedLastPassing && shouldFetchLastPassing) { - fetchLastPassing({ - variables: { - projectIdentifier, - skipOrderNumber, - buildVariantOptions: { - ...bvOptionsBase, - statuses: [TaskStatus.Succeeded], - }, - }, - }); - } - }, [shouldFetchLastPassing]); // eslint-disable-line react-hooks/exhaustive-deps - - // Hook that triggers fetching the last executed task if it needs to be fetched. - useEffect(() => { - if (!hasFetchedLastExecuted && shouldFetchLastExecuted) { - fetchLastExecuted({ - variables: { - projectIdentifier, - skipOrderNumber, - buildVariantOptions: { - ...bvOptionsBase, - statuses: finishedTaskStatuses, - }, - }, - }); - } - }, [shouldFetchLastExecuted]); // eslint-disable-line react-hooks/exhaustive-deps - - const Link = useLGButtonRouterLink(link); - - return ( - - - dispatch({ type: "setSelectState", selectState: v }) + > + No previous versions available. + + ) : ( + } size={Size.Small}> + Previous commits + + } + > + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.Base, + }) } - value={selectState} - disabled={!versionMetadata?.baseVersion} + to={linkObject[CommitType.Base]} > - - - - - - ( - - {loading - ? `Fetching...` - : `There is no version that satisfies this criteria.`} - - )} + Go to {versionMetadata?.isPatch ? "base" : "previous"} commit + + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.LastPassing, + }) + } + to={linkObject[CommitType.LastPassing]} > - - sendEvent({ - name: "Submit Previous Commit Selector", - type: selectState, - }) - } - size="small" - > - Go - - - + Go to last passing version + + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.LastExecuted, + }) + } + to={linkObject[CommitType.LastExecuted]} + > + Go to last executed version + + ); }; - -// The return value from GetLastMainlineCommitQuery has a lot of nested fields that may or may -// not exist. The logic to extract the task from it is written in this function. -const getTaskFromMainlineCommitsQuery = ( - data: LastMainlineCommitQuery -): CommitTask => { - const buildVariants = - data?.mainlineCommits.versions.find(({ version }) => version)?.version - .buildVariants ?? []; - if (buildVariants.length > 1) { - reportError( - new Error("Multiple build variants matched previous commit search.") - ).warning(); - } - if (buildVariants[0]?.tasks.length > 1) { - reportError( - new Error("Multiple tasks matched previous commit search.") - ).warning(); - } - return buildVariants[0]?.tasks[0]; -}; - -const PreviousCommitsWrapper = styled.div` - display: flex; - align-items: flex-start; -`; - -// @ts-expect-error -const StyledSelect = styled(Select)` - width: 220px; - margin-right: ${size.xs}; - margin-top: -23px; -`; diff --git a/src/pages/task/actionButtons/previousCommits/reducer.ts b/src/pages/task/actionButtons/previousCommits/reducer.ts deleted file mode 100644 index 3c88dcec87..0000000000 --- a/src/pages/task/actionButtons/previousCommits/reducer.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { getTaskRoute } from "constants/routes"; -import { TaskStatus } from "types/task"; -import { statuses } from "utils"; -import { CommitTask, CommitType } from "./types"; - -const { isFinishedTaskStatus } = statuses; - -interface State { - parentTask: CommitTask; - lastPassingTask: CommitTask; - lastExecutedTask: CommitTask; - selectState: CommitType; - disableButton: boolean; - link: string; - shouldFetchLastPassing: boolean; - hasFetchedLastPassing: boolean; - shouldFetchLastExecuted: boolean; - hasFetchedLastExecuted: boolean; -} - -// a link cannot be null, so it's common to use # as a substitute. -const nullLink = "#"; - -// Note that the state doesn't include a field for shouldFetchParent / hasFetchedParent as the parent task -// is always fetched on render. -export const initialState: State = { - parentTask: null, - lastPassingTask: null, - lastExecutedTask: null, - selectState: CommitType.Base, - disableButton: true, - link: nullLink, - shouldFetchLastPassing: false, - hasFetchedLastPassing: false, - shouldFetchLastExecuted: false, - hasFetchedLastExecuted: false, -}; - -type Action = - | { type: "setParentTask"; task: CommitTask } - | { type: "setLastPassingTask"; task: CommitTask } - | { type: "setLastExecutedTask"; task: CommitTask } - | { type: "setSelectState"; selectState: CommitType }; - -export const reducer = (state: State, action: Action): State => { - const { - hasFetchedLastExecuted, - hasFetchedLastPassing, - lastExecutedTask, - lastPassingTask, - parentTask, - } = state; - - switch (action.type) { - case "setParentTask": { - if (action.task) { - return { - ...state, - parentTask: action.task, - link: getTaskRoute(action.task.id), - disableButton: false, - }; - } - return { - ...state, - disableButton: true, - }; - } - case "setLastPassingTask": { - if (action.task) { - return { - ...state, - lastPassingTask: action.task, - hasFetchedLastPassing: true, - shouldFetchLastPassing: false, - link: getTaskRoute(action.task.id), - disableButton: false, - }; - } - return { - ...state, - hasFetchedLastPassing: true, - shouldFetchLastPassing: false, - disableButton: true, - }; - } - case "setLastExecutedTask": { - if (action.task) { - return { - ...state, - lastExecutedTask: action.task, - hasFetchedLastExecuted: true, - shouldFetchLastExecuted: false, - link: getTaskRoute(action.task.id), - disableButton: false, - }; - } - return { - ...state, - hasFetchedLastExecuted: true, - shouldFetchLastExecuted: false, - disableButton: true, - }; - } - case "setSelectState": { - const newSelectState = action.selectState; - - // If the user has selected the Last Passing option, set shouldFetchPassing to true if the task needs - // to be fetched. This will trigger the query in the useEffect hook. - if (newSelectState === CommitType.LastPassing) { - const triggerFetchLastPassing = shouldFetchLastPassingTask( - parentTask, - lastPassingTask - ); - - if (!hasFetchedLastPassing && triggerFetchLastPassing) { - return { - ...state, - selectState: newSelectState, - shouldFetchLastPassing: true, - link: nullLink, - disableButton: true, - }; - } - } - - // If the user has selected the Last Executed option, set shouldFetchExecuted to true if the task - // needs to be fetched. This will trigger the query in the useEffect hook. - if (newSelectState === CommitType.LastExecuted) { - const triggerFetchLastExecuted = shouldFetchLastExecutedTask( - parentTask, - lastExecutedTask - ); - if (!hasFetchedLastExecuted && triggerFetchLastExecuted) { - return { - ...state, - selectState: newSelectState, - shouldFetchLastExecuted: true, - link: nullLink, - disableButton: true, - }; - } - } - - // If the selectState has changed and nothing needs to be fetched, just update the link and determine - // if the GO button should be disabled. - const newLink = determineNewLink( - parentTask, - lastPassingTask, - lastExecutedTask, - newSelectState - ); - - const lastPassingTaskDoesNotExist = - newSelectState === CommitType.LastPassing && - hasFetchedLastPassing && - !lastPassingTask; - - const lastExecutedTaskDoesNotExist = - newSelectState === CommitType.LastExecuted && - hasFetchedLastExecuted && - !lastExecutedTask; - - return { - ...state, - selectState: newSelectState, - link: newLink, - disableButton: - newLink === nullLink || - lastPassingTaskDoesNotExist || - lastExecutedTaskDoesNotExist, - }; - } - default: - throw new Error(`Unknown reducer action ${action}`); - } -}; - -const shouldFetchLastPassingTask = ( - parentTask: CommitTask, - lastPassingTask: CommitTask -): boolean => { - if ( - parentTask && - parentTask.status !== TaskStatus.Succeeded && - !lastPassingTask - ) { - return true; - } - return false; -}; - -const shouldFetchLastExecutedTask = ( - parentTask: CommitTask, - lastExecutedTask: CommitTask -): boolean => { - if ( - parentTask && - !isFinishedTaskStatus(parentTask.status) && - !lastExecutedTask - ) { - return true; - } - return false; -}; - -// Determine the value of the link which will be used to redirect the user when they -// press the GO button. -const determineNewLink = ( - parentTask: CommitTask, - lastPassingTask: CommitTask, - lastExecutedTask: CommitTask, - selectState: CommitType -): string => { - if (parentTask) { - switch (selectState) { - case CommitType.Base: - return getTaskRoute(parentTask.id); - // If a parent task succeeded, the last passing commit is the parent task. - case CommitType.LastPassing: - return getTaskRoute(lastPassingTask?.id || parentTask.id); - // If a parent task has finished, the last executed commit is the parent task. - case CommitType.LastExecuted: - return getTaskRoute(lastExecutedTask?.id || parentTask.id); - default: - break; - } - } - return nullLink; -}; diff --git a/src/pages/task/actionButtons/previousCommits/utils.ts b/src/pages/task/actionButtons/previousCommits/utils.ts new file mode 100644 index 0000000000..118c8facbd --- /dev/null +++ b/src/pages/task/actionButtons/previousCommits/utils.ts @@ -0,0 +1,56 @@ +import { getTaskRoute } from "constants/routes"; +import { LastMainlineCommitQuery } from "gql/generated/types"; +import { reportError } from "utils/errorReporting"; +import { CommitTask, CommitType } from "./types"; + +// a link cannot be null, so it's common to use # as a substitute. +const nullLink = "#"; + +export const getLinks = ({ + lastExecutedTask, + lastPassingTask, + parentTask, +}: { + lastExecutedTask: CommitTask; + lastPassingTask: CommitTask; + parentTask: CommitTask; +}) => { + if (!parentTask) { + return { + [CommitType.Base]: nullLink, + [CommitType.LastPassing]: nullLink, + [CommitType.LastExecuted]: nullLink, + }; + } + + return { + [CommitType.Base]: getTaskRoute(parentTask.id), + [CommitType.LastPassing]: getTaskRoute( + lastPassingTask?.id || parentTask.id + ), + [CommitType.LastExecuted]: getTaskRoute( + lastExecutedTask?.id || parentTask.id + ), + }; +}; + +// The return value from GetLastMainlineCommitQuery has a lot of nested fields that may or may +// not exist. The logic to extract the task from it is written in this function. +export const getTaskFromMainlineCommitsQuery = ( + data: LastMainlineCommitQuery +): CommitTask => { + const buildVariants = + data?.mainlineCommits.versions.find(({ version }) => version)?.version + .buildVariants ?? []; + if (buildVariants.length > 1) { + reportError( + new Error("Multiple build variants matched previous commit search.") + ).warning(); + } + if (buildVariants[0]?.tasks.length > 1) { + reportError( + new Error("Multiple tasks matched previous commit search.") + ).warning(); + } + return buildVariants[0]?.tasks[0]; +}; From 03fc694a20b7ec529b46dd980375d6108508b511 Mon Sep 17 00:00:00 2001 From: Mohamed Khelif Date: Mon, 6 Nov 2023 10:16:36 -0500 Subject: [PATCH 2/5] Revert "DEVPROD-1614 Happy Halloween! (#2119)" (#2138) --- src/components/Header/Navbar.tsx | 13 +------------ src/components/Icon/icons/Halloween.svg | 26 ------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 src/components/Icon/icons/Halloween.svg diff --git a/src/components/Header/Navbar.tsx b/src/components/Header/Navbar.tsx index cf492e53d1..730ac0a9f9 100644 --- a/src/components/Header/Navbar.tsx +++ b/src/components/Header/Navbar.tsx @@ -7,7 +7,6 @@ import Cookies from "js-cookie"; import { Link, useParams } from "react-router-dom"; import { useNavbarAnalytics } from "analytics"; import Icon from "components/Icon"; -import HalloweenTree from "components/Icon/icons/Halloween.svg"; import { CURRENT_PROJECT } from "constants/cookies"; import { wikiUrl } from "constants/externalResources"; import { getCommitsRoute, getUserPatchesRoute, routes } from "constants/routes"; @@ -61,10 +60,7 @@ export const Navbar: React.FC = () => { to={routes.myPatches} onClick={() => sendEvent({ name: "Click Logo Link" })} > - + - - - - - - - - - - - - - - - - - - - - - - - - - From 079f786d2a15826af53ecfa5387b13b39fc05bb3 Mon Sep 17 00:00:00 2001 From: Jonathan Brill Date: Mon, 6 Nov 2023 10:22:19 -0500 Subject: [PATCH 3/5] DEVPROD-1717 update mongo to 7.0 (#2137) --- .evergreen.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen.yml b/.evergreen.yml index 15cd9c6e4f..ee1f648cd6 100644 --- a/.evergreen.yml +++ b/.evergreen.yml @@ -493,8 +493,8 @@ buildvariants: - ubuntu2204-large expansions: goroot: /opt/golang/go1.20 - mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.6.tgz - mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-1.9.0-linux-x64.tgz + mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.2.tgz + mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-2.0.2-linux-x64.tgz node_version: 16.17.0 modules: - evergreen From 5f3fdaeb1d0029ff5d419b7609e63e6a1ac214be Mon Sep 17 00:00:00 2001 From: Mohamed Khelif Date: Mon, 6 Nov 2023 12:46:57 -0500 Subject: [PATCH 4/5] v3.0.167 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f956b8de1a..f73b1d6db0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spruce", - "version": "3.0.166", + "version": "3.0.167", "private": true, "scripts": { "bootstrap-logkeeper": "./scripts/bootstrap-logkeeper.sh", From 1fd137f52fcb852fe42250abb75ff18ae88a167d Mon Sep 17 00:00:00 2001 From: SupaJoon Date: Mon, 6 Nov 2023 15:27:44 -0500 Subject: [PATCH 5/5] DEVPROD-359: Fix task_table sort button flake (#2134) --- cypress/integration/version/task_table.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cypress/integration/version/task_table.ts b/cypress/integration/version/task_table.ts index 07f28d7103..76c8fa8f48 100644 --- a/cypress/integration/version/task_table.ts +++ b/cypress/integration/version/task_table.ts @@ -18,6 +18,9 @@ describe("Task table", () => { it("Updates the url when column headers are clicked", () => { cy.visit(pathTasks); waitForTaskTable(); + // TODO: Remove wait in DEVPROD-597. + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(200); cy.location("search").should( "contain", "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC" @@ -83,7 +86,8 @@ describe("Task table", () => { }); ["NAME", "STATUS", "BASE_STATUS", "VARIANT"].forEach((sortBy) => { - // TODO: This test doesn't work bc of issues with assertCorrectRequestVariables + // TODO: This test doesn't work bc of issues with assertCorrectRequestVariables. + // Remove skip in DEVPROD-597. it.skip(`Fetches tasks sorted by ${sortBy} when ${sortBy} header is clicked`, () => { // clickSorterAndAssertTasksAreFetched(sortBy); });