From 0e412abb55da5b295c54744da0270292528dbe07 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:38:54 -0500 Subject: [PATCH 01/22] build: add to gql query --- src/gql/generated/types.ts | 2 ++ src/gql/queries/base-version-and-task.graphql | 1 + src/gql/queries/last-mainline-commit.graphql | 1 + .../previousCommits/PreviousCommits.test.tsx | 8 ++++++++ 4 files changed, 12 insertions(+) diff --git a/src/gql/generated/types.ts b/src/gql/generated/types.ts index 53fdab5136..e9555b70e3 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -5406,6 +5406,7 @@ export type BaseVersionAndTaskQuery = { execution: number; id: string; projectIdentifier?: string | null; + status: string; baseTask?: { __typename?: "Task"; execution: number; @@ -6164,6 +6165,7 @@ export type LastMainlineCommitQuery = { version?: { __typename?: "Version"; id: string; + order: number; buildVariants?: Array<{ __typename?: "GroupedBuildVariant"; tasks?: Array<{ diff --git a/src/gql/queries/base-version-and-task.graphql b/src/gql/queries/base-version-and-task.graphql index 3452491b5e..1371d48913 100644 --- a/src/gql/queries/base-version-and-task.graphql +++ b/src/gql/queries/base-version-and-task.graphql @@ -10,6 +10,7 @@ query BaseVersionAndTask($taskId: String!) { execution id projectIdentifier + status versionMetadata { baseVersion { id diff --git a/src/gql/queries/last-mainline-commit.graphql b/src/gql/queries/last-mainline-commit.graphql index ef6daa28ac..30c11b38ba 100644 --- a/src/gql/queries/last-mainline-commit.graphql +++ b/src/gql/queries/last-mainline-commit.graphql @@ -22,6 +22,7 @@ query LastMainlineCommit( } } id + order } } } diff --git a/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx b/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx index eeca443354..5021d81a93 100644 --- a/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx +++ b/src/pages/task/actionButtons/previousCommits/PreviousCommits.test.tsx @@ -206,6 +206,7 @@ const getPatchTaskWithSuccessfulBaseTask: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "success", versionMetadata: { baseVersion: { id: "baseVersion", @@ -246,6 +247,7 @@ const getPatchTaskWithRunningBaseTask: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "started", versionMetadata: { baseVersion: { id: "baseVersion", @@ -286,6 +288,7 @@ const getPatchTaskWithFailingBaseTask: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "success", versionMetadata: { baseVersion: { id: "baseVersion", @@ -326,6 +329,7 @@ const getPatchTaskWithNoBaseVersion: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "success", versionMetadata: { baseVersion: null, id: "versionMetadataId", @@ -362,6 +366,7 @@ const getLastPassingVersion: ApolloMock< { version: { id: "evergreen_44110b57c6977bf3557009193628c9389772163f", + order: 3676, buildVariants: [ { tasks: [ @@ -419,6 +424,7 @@ const getLastExecutedVersion: ApolloMock< { version: { id: "evergreen_44110b57c6977bf3557009193628c9389772163f", + order: 3676, buildVariants: [ { tasks: [ @@ -462,6 +468,7 @@ const getPatchTaskWithNoBaseTask: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "success", versionMetadata: { baseVersion: { id: "baseVersion", @@ -498,6 +505,7 @@ const getMainlineTaskWithBaseVersion: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", + status: "success", versionMetadata: { baseVersion: { id: "baseVersion", From 1dfaa64f167dcb87760bfb3430912d6b795da650 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:41:40 -0500 Subject: [PATCH 02/22] feat: add breaking commit in to selector --- .../actionButtons/previousCommits/index.tsx | 48 ++++++++++++++++++- .../actionButtons/previousCommits/types.ts | 1 + .../actionButtons/previousCommits/utils.ts | 12 +++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/pages/task/actionButtons/previousCommits/index.tsx b/src/pages/task/actionButtons/previousCommits/index.tsx index 00cba9d0b3..0ac4778478 100644 --- a/src/pages/task/actionButtons/previousCommits/index.tsx +++ b/src/pages/task/actionButtons/previousCommits/index.tsx @@ -18,7 +18,11 @@ import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; import { TaskStatus } from "types/task"; import { statuses, string } from "utils"; import { CommitType } from "./types"; -import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; +import { + getLinks, + getOrderFromMainlineCommitsQuery, + getTaskFromMainlineCommitsQuery, +} from "./utils"; const { applyStrictRegex } = string; const { isFinishedTaskStatus } = statuses; @@ -42,6 +46,7 @@ export const PreviousCommits: React.FC = ({ taskId }) => { buildVariant, displayName, projectIdentifier, + status: baseStatus, versionMetadata, } = taskData?.task ?? {}; const { order: skipOrderNumber } = versionMetadata?.baseVersion ?? {}; @@ -85,6 +90,31 @@ export const PreviousCommits: React.FC = ({ taskId }) => { }, }); const lastPassingTask = getTaskFromMainlineCommitsQuery(lastPassingTaskData); + const passingOrderNumber = + getOrderFromMainlineCommitsQuery(lastPassingTaskData); + + const { data: breakingTaskData, loading: breakingLoading } = useQuery< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables + >(LAST_MAINLINE_COMMIT, { + skip: + !parentTask || + !lastPassingTask || + baseStatus === undefined || + baseStatus === TaskStatus.Succeeded, + variables: { + projectIdentifier, + skipOrderNumber: passingOrderNumber + 2, + buildVariantOptions: { + ...bvOptionsBase, + statuses: [TaskStatus.Failed], + }, + }, + onError: (err) => { + dispatchToast.error(`Breaking commit unavailable: '${err.message}'`); + }, + }); + const breakingTask = getTaskFromMainlineCommitsQuery(breakingTaskData); const { data: lastExecutedTaskData, loading: executedLoading } = useQuery< LastMainlineCommitQuery, @@ -112,10 +142,11 @@ export const PreviousCommits: React.FC = ({ taskId }) => { () => getLinks({ parentTask, + breakingTask, lastPassingTask, lastExecutedTask, }), - [parentTask, lastPassingTask, lastExecutedTask], + [parentTask, breakingTask, lastPassingTask, lastExecutedTask], ); const menuDisabled = !baseTask || !parentTask; @@ -156,6 +187,19 @@ export const PreviousCommits: React.FC = ({ taskId }) => { > Go to {versionMetadata?.isPatch ? "base" : "previous"} commit + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.Breaking, + }) + } + to={linkObject[CommitType.Breaking]} + > + Go to breaking commit + + data?.mainlineCommits.versions.find(({ version }) => version)?.version + .order ?? -1; From e66d46e19a367a4444e30c9cb51733ce484df14d Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:42:06 -0500 Subject: [PATCH 03/22] fix: add last passing task condition --- src/pages/task/actionButtons/previousCommits/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/task/actionButtons/previousCommits/index.tsx b/src/pages/task/actionButtons/previousCommits/index.tsx index 0ac4778478..fad7f56f2c 100644 --- a/src/pages/task/actionButtons/previousCommits/index.tsx +++ b/src/pages/task/actionButtons/previousCommits/index.tsx @@ -202,7 +202,7 @@ export const PreviousCommits: React.FC = ({ taskId }) => { sendEvent({ name: "Submit Previous Commit Selector", From 347ca0dd6c95cb1af87cc0593b3af27a8bce08fe Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:44:47 -0500 Subject: [PATCH 04/22] refactor: rename previous commits to relevant commits --- src/analytics/task/useTaskAnalytics.ts | 2 +- src/pages/task/ActionButtons.tsx | 4 +- .../RelevantCommits.test.tsx} | 44 +++++++++---------- .../index.tsx | 10 ++--- .../types.ts | 0 .../utils.ts | 0 6 files changed, 30 insertions(+), 30 deletions(-) rename src/pages/task/actionButtons/{previousCommits/PreviousCommits.test.tsx => relevantCommits/RelevantCommits.test.tsx} (92%) rename src/pages/task/actionButtons/{previousCommits => relevantCommits}/index.tsx (97%) rename src/pages/task/actionButtons/{previousCommits => relevantCommits}/types.ts (100%) rename src/pages/task/actionButtons/{previousCommits => relevantCommits}/utils.ts (100%) diff --git a/src/analytics/task/useTaskAnalytics.ts b/src/analytics/task/useTaskAnalytics.ts index 29011dd6d3..2b15408ac5 100644 --- a/src/analytics/task/useTaskAnalytics.ts +++ b/src/analytics/task/useTaskAnalytics.ts @@ -10,7 +10,7 @@ import { } from "gql/generated/types"; import { TASK } from "gql/queries"; import { useQueryParam } from "hooks/useQueryParam"; -import { CommitType } from "pages/task/actionButtons/previousCommits/types"; +import { CommitType } from "pages/task/actionButtons/relevantCommits/types"; import { RequiredQueryParams, LogTypes } from "types/task"; type LogViewer = "raw" | "html" | "parsley"; diff --git a/src/pages/task/ActionButtons.tsx b/src/pages/task/ActionButtons.tsx index 776fb9848b..4f860826ad 100644 --- a/src/pages/task/ActionButtons.tsx +++ b/src/pages/task/ActionButtons.tsx @@ -38,7 +38,7 @@ import { import { useLGButtonRouterLink } from "hooks/useLGButtonRouterLink"; import { useQueryParam } from "hooks/useQueryParam"; import { TaskStatus } from "types/task"; -import { PreviousCommits } from "./actionButtons/previousCommits"; +import { RelevantCommits } from "./actionButtons/relevantCommits"; import { TaskNotificationModal } from "./actionButtons/TaskNotificationModal"; interface Props { @@ -254,7 +254,7 @@ export const ActionButtons: React.FC = ({ {!isExecutionTask && ( <> - + } > - No previous versions available. + No relevant versions available. ) : ( } size={Size.Small}> - Previous commits + Relevant commits } > diff --git a/src/pages/task/actionButtons/previousCommits/types.ts b/src/pages/task/actionButtons/relevantCommits/types.ts similarity index 100% rename from src/pages/task/actionButtons/previousCommits/types.ts rename to src/pages/task/actionButtons/relevantCommits/types.ts diff --git a/src/pages/task/actionButtons/previousCommits/utils.ts b/src/pages/task/actionButtons/relevantCommits/utils.ts similarity index 100% rename from src/pages/task/actionButtons/previousCommits/utils.ts rename to src/pages/task/actionButtons/relevantCommits/utils.ts From 86d208036ceed03b279755861e702ef3d5cce911 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:06:38 -0500 Subject: [PATCH 05/22] fix: revert update --- src/pages/task/actionButtons/relevantCommits/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index c4a06102cb..76a40f0d24 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -202,7 +202,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { sendEvent({ name: "Submit Previous Commit Selector", From 4fa8c5da0627df3ddad57b24162f12d9f17863ac Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:06:05 -0500 Subject: [PATCH 06/22] refactor: baseStatus -> status --- src/pages/task/actionButtons/relevantCommits/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index 76a40f0d24..e44b5ef765 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -46,7 +46,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { buildVariant, displayName, projectIdentifier, - status: baseStatus, + status, versionMetadata, } = taskData?.task ?? {}; const { order: skipOrderNumber } = versionMetadata?.baseVersion ?? {}; @@ -100,8 +100,8 @@ export const RelevantCommits: React.FC = ({ taskId }) => { skip: !parentTask || !lastPassingTask || - baseStatus === undefined || - baseStatus === TaskStatus.Succeeded, + status === undefined || + status === TaskStatus.Succeeded, variables: { projectIdentifier, skipOrderNumber: passingOrderNumber + 2, From c1d95e0e9fa9b43f191decdf867cd9e5034b6549 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:18:30 -0500 Subject: [PATCH 07/22] doc: last breaking task query --- src/pages/task/actionButtons/relevantCommits/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index e44b5ef765..c54fde1f38 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -93,6 +93,9 @@ export const RelevantCommits: React.FC = ({ taskId }) => { const passingOrderNumber = getOrderFromMainlineCommitsQuery(lastPassingTaskData); + // The breaking commit is the first failing commit after the last passing commit. + // The skip order number should be the last passing commit's order number + 1. + // We use + 2 because internally the query does a less than comparison. const { data: breakingTaskData, loading: breakingLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables From 190bbe957ab87b30667df0dcd2e5d527a39515da Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:30:42 -0500 Subject: [PATCH 08/22] fix: use task order rather than version --- src/gql/generated/types.ts | 2 +- src/gql/queries/last-mainline-commit.graphql | 2 +- .../task/actionButtons/relevantCommits/index.tsx | 9 ++------- .../task/actionButtons/relevantCommits/types.ts | 10 ++++++++-- .../task/actionButtons/relevantCommits/utils.ts | 16 +++++----------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/gql/generated/types.ts b/src/gql/generated/types.ts index e9555b70e3..0012cbbb9f 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -6165,13 +6165,13 @@ export type LastMainlineCommitQuery = { version?: { __typename?: "Version"; id: string; - order: number; buildVariants?: Array<{ __typename?: "GroupedBuildVariant"; tasks?: Array<{ __typename?: "Task"; execution: number; id: string; + order: number; status: string; } | null> | null; } | null> | null; diff --git a/src/gql/queries/last-mainline-commit.graphql b/src/gql/queries/last-mainline-commit.graphql index 30c11b38ba..098e78d78f 100644 --- a/src/gql/queries/last-mainline-commit.graphql +++ b/src/gql/queries/last-mainline-commit.graphql @@ -18,11 +18,11 @@ query LastMainlineCommit( tasks { execution id + order status } } id - order } } } diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index c54fde1f38..5e2404a786 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -18,11 +18,7 @@ import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; import { TaskStatus } from "types/task"; import { statuses, string } from "utils"; import { CommitType } from "./types"; -import { - getLinks, - getOrderFromMainlineCommitsQuery, - getTaskFromMainlineCommitsQuery, -} from "./utils"; +import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; const { applyStrictRegex } = string; const { isFinishedTaskStatus } = statuses; @@ -90,8 +86,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { }, }); const lastPassingTask = getTaskFromMainlineCommitsQuery(lastPassingTaskData); - const passingOrderNumber = - getOrderFromMainlineCommitsQuery(lastPassingTaskData); + const passingOrderNumber = lastPassingTask?.order; // The breaking commit is the first failing commit after the last passing commit. // The skip order number should be the last passing commit's order number + 1. diff --git a/src/pages/task/actionButtons/relevantCommits/types.ts b/src/pages/task/actionButtons/relevantCommits/types.ts index 3879cc369b..15e5cce2a5 100644 --- a/src/pages/task/actionButtons/relevantCommits/types.ts +++ b/src/pages/task/actionButtons/relevantCommits/types.ts @@ -1,4 +1,7 @@ -import { BaseVersionAndTaskQuery } from "gql/generated/types"; +import { + BaseVersionAndTaskQuery, + LastMainlineCommitQuery, +} from "gql/generated/types"; export enum CommitType { Base = "base", @@ -7,4 +10,7 @@ export enum CommitType { LastExecuted = "lastExecuted", } -export type CommitTask = BaseVersionAndTaskQuery["task"]["baseTask"]; +export type BaseTask = BaseVersionAndTaskQuery["task"]["baseTask"]; + +export type CommitTask = + LastMainlineCommitQuery["mainlineCommits"]["versions"][number]["version"]["buildVariants"][number]["tasks"][number]; diff --git a/src/pages/task/actionButtons/relevantCommits/utils.ts b/src/pages/task/actionButtons/relevantCommits/utils.ts index bc504afe4c..a18f0770c3 100644 --- a/src/pages/task/actionButtons/relevantCommits/utils.ts +++ b/src/pages/task/actionButtons/relevantCommits/utils.ts @@ -1,7 +1,7 @@ import { getTaskRoute } from "constants/routes"; import { LastMainlineCommitQuery } from "gql/generated/types"; import { reportError } from "utils/errorReporting"; -import { CommitTask, CommitType } from "./types"; +import { BaseTask, CommitTask, CommitType } from "./types"; // a link cannot be null, so it's common to use # as a substitute. const nullLink = "#"; @@ -12,10 +12,10 @@ export const getLinks = ({ lastPassingTask, parentTask, }: { - breakingTask: CommitTask; - lastExecutedTask: CommitTask; - lastPassingTask: CommitTask; - parentTask: CommitTask; + breakingTask: BaseTask; + lastExecutedTask: BaseTask; + lastPassingTask: BaseTask; + parentTask: BaseTask; }) => { if (!parentTask) { return { @@ -60,9 +60,3 @@ export const getTaskFromMainlineCommitsQuery = ( } return buildVariants[0]?.tasks[0]; }; - -export const getOrderFromMainlineCommitsQuery = ( - data: LastMainlineCommitQuery, -): number => - data?.mainlineCommits.versions.find(({ version }) => version)?.version - .order ?? -1; From 743561141842cb88beffdb1d40beac21c73de26f Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:32:04 -0500 Subject: [PATCH 09/22] fix: tests --- .../actionButtons/relevantCommits/RelevantCommits.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx index a9dabe0f41..452a13e4cc 100644 --- a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx +++ b/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx @@ -366,13 +366,13 @@ const getLastPassingVersion: ApolloMock< { version: { id: "evergreen_44110b57c6977bf3557009193628c9389772163f", - order: 3676, buildVariants: [ { tasks: [ { id: "last_passing_task", execution: 0, + order: 3676, status: "success", __typename: "Task", }, @@ -424,13 +424,13 @@ const getLastExecutedVersion: ApolloMock< { version: { id: "evergreen_44110b57c6977bf3557009193628c9389772163f", - order: 3676, buildVariants: [ { tasks: [ { id: "last_executed_task", execution: 0, + order: 3676, status: "failed", __typename: "Task", }, From 81abcc05ced753d1485c55dba92152271d815a53 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:30:05 -0500 Subject: [PATCH 10/22] feat: add some tests --- .../relevantCommits/RelevantCommits.test.tsx | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx index 452a13e4cc..bf751c6a6a 100644 --- a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx +++ b/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx @@ -82,7 +82,7 @@ describe("relevant commits", () => { }); }); - it("when base task is passing, all dropdown items generate the same link", async () => { + it("when base task is passing, last passing, base commit, and last executed dropdown items generate the same link and breaking commit is disabled", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( { expect( screen.getByRole("menuitem", { name: "Go to last executed version" }), ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("aria-disabled", "true"); }); - 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 () => { + 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 and 'Go to breaking commit' will be disabled", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( { expect( screen.getByRole("menuitem", { name: "Go to last executed version" }), ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("aria-disabled", "true"); }); - it("when base task is not in a finished state, the last executed & passing task is not the same as the base commit", async () => { + it("when base task is not in a finished state, the last executed, passing task, and breaking task is not the same as the base commit", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( { getPatchTaskWithRunningBaseTask, getLastPassingVersion, getLastExecutedVersion, + getBreakingCommit, ]} > @@ -181,6 +188,9 @@ describe("relevant commits", () => { expect( screen.getByRole("menuitem", { name: "Go to last executed version" }), ).toHaveAttribute("href", "/task/last_executed_task"); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("href", "/task/breaking_commit"); }); }); @@ -343,6 +353,54 @@ const getPatchTaskWithNoBaseVersion: ApolloMock< }, }; +const getBreakingCommit: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3678, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: ["failed"], + }, + }, + }, + result: { + data: { + mainlineCommits: { + versions: [ + { + version: { + id: "evergreen_44110b57c6977bf3557009193628c9389772163f2", + buildVariants: [ + { + tasks: [ + { + id: "breaking_commit", + execution: 0, + order: 3677, + status: "failed", + __typename: "Task", + }, + ], + __typename: "GroupedBuildVariant", + }, + ], + __typename: "Version", + }, + __typename: "MainlineCommitVersion", + }, + ], + __typename: "MainlineCommits", + }, + }, + }, +}; + const getLastPassingVersion: ApolloMock< LastMainlineCommitQuery, LastMainlineCommitQueryVariables From 7018fcbce0be9db310d17c40fe948aff597fcd15 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:11:49 -0500 Subject: [PATCH 11/22] fix: switch status check to be not failed --- src/pages/task/actionButtons/relevantCommits/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index 5e2404a786..a1ca72e888 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -21,7 +21,7 @@ import { CommitType } from "./types"; import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; const { applyStrictRegex } = string; -const { isFinishedTaskStatus } = statuses; +const { isFailedTaskStatus, isFinishedTaskStatus } = statuses; interface RelevantCommitsProps { taskId: string; @@ -95,11 +95,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { LastMainlineCommitQuery, LastMainlineCommitQueryVariables >(LAST_MAINLINE_COMMIT, { - skip: - !parentTask || - !lastPassingTask || - status === undefined || - status === TaskStatus.Succeeded, + skip: !parentTask || !lastPassingTask || !isFailedTaskStatus(status), variables: { projectIdentifier, skipOrderNumber: passingOrderNumber + 2, From a5460582c67077e5114f2a7d052de189556827a2 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 09:54:28 -0500 Subject: [PATCH 12/22] refactor: add permalink to comment --- src/pages/task/actionButtons/relevantCommits/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index a1ca72e888..57d0126158 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -91,6 +91,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { // The breaking commit is the first failing commit after the last passing commit. // The skip order number should be the last passing commit's order number + 1. // We use + 2 because internally the query does a less than comparison. + // https://github.com/ZackarySantana/spruce/blob/7018fcbce0be9db310d17c40fe948aff597fcd15/src/pages/task/actionButtons/relevantCommits/index.tsx#L93 const { data: breakingTaskData, loading: breakingLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables From b4ee498b0a840414f70c7e138652791dc066d352 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:55:11 -0500 Subject: [PATCH 13/22] refactor: capitalize relevant commits --- src/analytics/task/useTaskAnalytics.ts | 2 +- src/pages/task/ActionButtons.tsx | 2 +- .../RelevantCommits/RelevantCommits.test.tsx | 620 ++++++++++++++++++ .../actionButtons/RelevantCommits/index.tsx | 226 +++++++ .../actionButtons/RelevantCommits/types.ts | 16 + .../actionButtons/RelevantCommits/utils.ts | 62 ++ 6 files changed, 926 insertions(+), 2 deletions(-) create mode 100644 src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx create mode 100644 src/pages/task/actionButtons/RelevantCommits/index.tsx create mode 100644 src/pages/task/actionButtons/RelevantCommits/types.ts create mode 100644 src/pages/task/actionButtons/RelevantCommits/utils.ts diff --git a/src/analytics/task/useTaskAnalytics.ts b/src/analytics/task/useTaskAnalytics.ts index 2b15408ac5..2a8d693958 100644 --- a/src/analytics/task/useTaskAnalytics.ts +++ b/src/analytics/task/useTaskAnalytics.ts @@ -10,7 +10,7 @@ import { } from "gql/generated/types"; import { TASK } from "gql/queries"; import { useQueryParam } from "hooks/useQueryParam"; -import { CommitType } from "pages/task/actionButtons/relevantCommits/types"; +import { CommitType } from "pages/task/actionButtons/RelevantCommits/types"; import { RequiredQueryParams, LogTypes } from "types/task"; type LogViewer = "raw" | "html" | "parsley"; diff --git a/src/pages/task/ActionButtons.tsx b/src/pages/task/ActionButtons.tsx index 4f860826ad..48b7592925 100644 --- a/src/pages/task/ActionButtons.tsx +++ b/src/pages/task/ActionButtons.tsx @@ -38,7 +38,7 @@ import { import { useLGButtonRouterLink } from "hooks/useLGButtonRouterLink"; import { useQueryParam } from "hooks/useQueryParam"; import { TaskStatus } from "types/task"; -import { RelevantCommits } from "./actionButtons/relevantCommits"; +import { RelevantCommits } from "./actionButtons/RelevantCommits"; import { TaskNotificationModal } from "./actionButtons/TaskNotificationModal"; interface Props { diff --git a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx new file mode 100644 index 0000000000..bf751c6a6a --- /dev/null +++ b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx @@ -0,0 +1,620 @@ +import { MockedProvider } from "@apollo/client/testing"; +import { RenderFakeToastContext } from "context/toast/__mocks__"; +import { + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables, + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables, +} from "gql/generated/types"; +import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; +import { renderWithRouterMatch, screen, userEvent, waitFor } from "test_utils"; +import { ApolloMock } from "types/gql"; +import { RelevantCommits } from "."; + +describe("relevant 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 button is disabled when there is no base task", async () => { + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + await waitFor(() => { + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + }); + }); + + describe("mainline commits specific", () => { + it("the button is disabled when getParentTask returns null", async () => { + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await waitFor(() => { + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + }); + + it("the button is disabled when getParentTask returns an error", async () => { + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await waitFor(() => { + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + }); + }); + + it("the button is disabled when no base version exists", async () => { + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await waitFor(() => { + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + }); + + it("when base task is passing, last passing, base commit, and last executed dropdown items generate the same link and breaking commit is disabled", async () => { + const user = userEvent.setup(); + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await screen.findByRole("button", { name: "Relevant commits" }); + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Relevant commits" })); + await waitFor(() => { + 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", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to last executed version" }), + ).toHaveAttribute("href", baseTaskHref); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + + 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 and 'Go to breaking commit' will be disabled", async () => { + const user = userEvent.setup(); + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await screen.findByRole("button", { name: "Relevant commits" }); + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Relevant commits" })); + await waitFor(() => { + 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); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("aria-disabled", "true"); + }); + + it("when base task is not in a finished state, the last executed, passing task, and breaking task is not the same as the base commit", async () => { + const user = userEvent.setup(); + const { Component } = RenderFakeToastContext( + + + , + ); + renderWithRouterMatch(); + + await screen.findByRole("button", { name: "Relevant commits" }); + expect( + screen.getByRole("button", { name: "Relevant commits" }), + ).toHaveAttribute("aria-disabled", "false"); + await user.click(screen.getByRole("button", { name: "Relevant commits" })); + await waitFor(() => { + 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", "/task/last_executed_task"); + expect( + screen.getByRole("menuitem", { name: "Go to breaking commit" }), + ).toHaveAttribute("href", "/task/breaking_commit"); + }); +}); + +const baseTaskId = + "evergreen_lint_lint_agent_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_21_11_29_17_55_27"; +const baseTaskHref = `/task/${baseTaskId}`; + +const getPatchTaskWithSuccessfulBaseTask: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t1", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "success", + versionMetadata: { + baseVersion: { + id: "baseVersion", + order: 3676, + __typename: "Version", + }, + isPatch: true, + id: "versionMetadataId", + __typename: "Version", + }, + baseTask: { + id: baseTaskId, + execution: 0, + status: "success", + __typename: "Task", + }, + __typename: "Task", + }, + }, + }, +}; + +const getPatchTaskWithRunningBaseTask: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t3", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "started", + versionMetadata: { + baseVersion: { + id: "baseVersion", + order: 3676, + __typename: "Version", + }, + isPatch: true, + id: "versionMetadataId", + __typename: "Version", + }, + baseTask: { + id: baseTaskId, + execution: 0, + status: "started", + __typename: "Task", + }, + __typename: "Task", + }, + }, + }, +}; + +const getPatchTaskWithFailingBaseTask: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t1", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "success", + versionMetadata: { + baseVersion: { + id: "baseVersion", + order: 3676, + __typename: "Version", + }, + isPatch: true, + id: "versionMetadataId", + __typename: "Version", + }, + baseTask: { + id: baseTaskId, + execution: 0, + status: "failed", + __typename: "Task", + }, + __typename: "Task", + }, + }, + }, +}; + +const getPatchTaskWithNoBaseVersion: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t3", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "success", + versionMetadata: { + baseVersion: null, + id: "versionMetadataId", + isPatch: true, + __typename: "Version", + }, + baseTask: null, + __typename: "Task", + }, + }, + }, +}; + +const getBreakingCommit: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3678, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: ["failed"], + }, + }, + }, + result: { + data: { + mainlineCommits: { + versions: [ + { + version: { + id: "evergreen_44110b57c6977bf3557009193628c9389772163f2", + buildVariants: [ + { + tasks: [ + { + id: "breaking_commit", + execution: 0, + order: 3677, + status: "failed", + __typename: "Task", + }, + ], + __typename: "GroupedBuildVariant", + }, + ], + __typename: "Version", + }, + __typename: "MainlineCommitVersion", + }, + ], + __typename: "MainlineCommits", + }, + }, + }, +}; + +const getLastPassingVersion: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3676, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: ["success"], + }, + }, + }, + result: { + data: { + mainlineCommits: { + versions: [ + { + version: { + id: "evergreen_44110b57c6977bf3557009193628c9389772163f", + buildVariants: [ + { + tasks: [ + { + id: "last_passing_task", + execution: 0, + order: 3676, + status: "success", + __typename: "Task", + }, + ], + __typename: "GroupedBuildVariant", + }, + ], + __typename: "Version", + }, + __typename: "MainlineCommitVersion", + }, + ], + __typename: "MainlineCommits", + }, + }, + }, +}; + +const getLastExecutedVersion: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3676, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: [ + "failed", + "setup-failed", + "system-failed", + "task-timed-out", + "test-timed-out", + "known-issue", + "system-unresponsive", + "system-timed-out", + "success", + ], + }, + }, + }, + result: { + data: { + mainlineCommits: { + versions: [ + { + version: { + id: "evergreen_44110b57c6977bf3557009193628c9389772163f", + buildVariants: [ + { + tasks: [ + { + id: "last_executed_task", + execution: 0, + order: 3676, + status: "failed", + __typename: "Task", + }, + ], + __typename: "GroupedBuildVariant", + }, + ], + __typename: "Version", + }, + __typename: "MainlineCommitVersion", + }, + ], + __typename: "MainlineCommits", + }, + }, + }, +}; + +// patch specific +const getPatchTaskWithNoBaseTask: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t1", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "success", + versionMetadata: { + baseVersion: { + id: "baseVersion", + order: 3676, + __typename: "Version", + }, + isPatch: true, + id: "versionMetadataId", + __typename: "Version", + }, + baseTask: null, + __typename: "Task", + }, + }, + }, +}; + +// Mainline commits specific +const getMainlineTaskWithBaseVersion: ApolloMock< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables +> = { + request: { + query: BASE_VERSION_AND_TASK, + variables: { + taskId: "t4", + }, + }, + result: { + data: { + task: { + id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", + execution: 0, + displayName: "lint-agent", + buildVariant: "lint", + projectIdentifier: "evergreen", + status: "success", + versionMetadata: { + baseVersion: { + id: "baseVersion", + order: 3676, + __typename: "Version", + }, + isPatch: false, + id: "versionMetadataId", + __typename: "Version", + }, + baseTask: null, + __typename: "Task", + }, + }, + }, +}; + +const getNullParentTask: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3676, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: ["success"], + }, + }, + }, + error: new Error("Matching version not found in 300 most recent versions"), +}; + +const getParentTaskWithError: ApolloMock< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables +> = { + request: { + query: LAST_MAINLINE_COMMIT, + variables: { + projectIdentifier: "evergreen", + skipOrderNumber: 3676, + buildVariantOptions: { + tasks: ["^lint-agent$"], + variants: ["^lint$"], + statuses: ["success"], + }, + }, + }, + error: new Error("Matching version not found in 300 most recent versions"), +}; diff --git a/src/pages/task/actionButtons/RelevantCommits/index.tsx b/src/pages/task/actionButtons/RelevantCommits/index.tsx new file mode 100644 index 0000000000..57d0126158 --- /dev/null +++ b/src/pages/task/actionButtons/RelevantCommits/index.tsx @@ -0,0 +1,226 @@ +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 Icon from "components/Icon"; +import { finishedTaskStatuses } from "constants/task"; +import { useToastContext } from "context/toast"; +import { + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables, + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables, +} from "gql/generated/types"; +import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; +import { TaskStatus } from "types/task"; +import { statuses, string } from "utils"; +import { CommitType } from "./types"; +import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; + +const { applyStrictRegex } = string; +const { isFailedTaskStatus, isFinishedTaskStatus } = statuses; + +interface RelevantCommitsProps { + taskId: string; +} +export const RelevantCommits: React.FC = ({ taskId }) => { + const { sendEvent } = useTaskAnalytics(); + const dispatchToast = useToastContext(); + + const { data: taskData } = useQuery< + BaseVersionAndTaskQuery, + BaseVersionAndTaskQueryVariables + >(BASE_VERSION_AND_TASK, { + variables: { taskId }, + }); + + const { + baseTask, + buildVariant, + displayName, + projectIdentifier, + status, + versionMetadata, + } = taskData?.task ?? {}; + const { order: skipOrderNumber } = versionMetadata?.baseVersion ?? {}; + + const bvOptionsBase = { + tasks: [applyStrictRegex(displayName)], + variants: [applyStrictRegex(buildVariant)], + }; + + const { data: parentTaskData, loading: parentLoading } = useQuery< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables + >(LAST_MAINLINE_COMMIT, { + skip: !versionMetadata || versionMetadata.isPatch, + variables: { + projectIdentifier, + skipOrderNumber, + buildVariantOptions: { + ...bvOptionsBase, + }, + }, + }); + const parentTask = + getTaskFromMainlineCommitsQuery(parentTaskData) ?? baseTask; + + const { data: lastPassingTaskData, loading: passingLoading } = useQuery< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables + >(LAST_MAINLINE_COMMIT, { + 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 passingOrderNumber = lastPassingTask?.order; + + // The breaking commit is the first failing commit after the last passing commit. + // The skip order number should be the last passing commit's order number + 1. + // We use + 2 because internally the query does a less than comparison. + // https://github.com/ZackarySantana/spruce/blob/7018fcbce0be9db310d17c40fe948aff597fcd15/src/pages/task/actionButtons/relevantCommits/index.tsx#L93 + const { data: breakingTaskData, loading: breakingLoading } = useQuery< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables + >(LAST_MAINLINE_COMMIT, { + skip: !parentTask || !lastPassingTask || !isFailedTaskStatus(status), + variables: { + projectIdentifier, + skipOrderNumber: passingOrderNumber + 2, + buildVariantOptions: { + ...bvOptionsBase, + statuses: [TaskStatus.Failed], + }, + }, + onError: (err) => { + dispatchToast.error(`Breaking commit unavailable: '${err.message}'`); + }, + }); + const breakingTask = getTaskFromMainlineCommitsQuery(breakingTaskData); + + const { data: lastExecutedTaskData, loading: executedLoading } = useQuery< + LastMainlineCommitQuery, + LastMainlineCommitQueryVariables + >(LAST_MAINLINE_COMMIT, { + skip: !parentTask || isFinishedTaskStatus(parentTask.status), + variables: { + projectIdentifier, + skipOrderNumber, + buildVariantOptions: { + ...bvOptionsBase, + statuses: finishedTaskStatuses, + }, + }, + onError: (err) => { + dispatchToast.error( + `Could not fetch last task execution: '${err.message}'`, + ); + }, + }); + const lastExecutedTask = + getTaskFromMainlineCommitsQuery(lastExecutedTaskData); + + const linkObject = useMemo( + () => + getLinks({ + parentTask, + breakingTask, + lastPassingTask, + lastExecutedTask, + }), + [parentTask, breakingTask, lastPassingTask, lastExecutedTask], + ); + + const menuDisabled = !baseTask || !parentTask; + + return menuDisabled ? ( + } + size={Size.Small} + > + Relevant commits + + } + > + No relevant versions available. + + ) : ( + } size={Size.Small}> + Relevant commits + + } + > + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.Base, + }) + } + to={linkObject[CommitType.Base]} + > + Go to {versionMetadata?.isPatch ? "base" : "previous"} commit + + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.Breaking, + }) + } + to={linkObject[CommitType.Breaking]} + > + Go to breaking commit + + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.LastPassing, + }) + } + to={linkObject[CommitType.LastPassing]} + > + Go to last passing version + + + sendEvent({ + name: "Submit Previous Commit Selector", + type: CommitType.LastExecuted, + }) + } + to={linkObject[CommitType.LastExecuted]} + > + Go to last executed version + + + ); +}; diff --git a/src/pages/task/actionButtons/RelevantCommits/types.ts b/src/pages/task/actionButtons/RelevantCommits/types.ts new file mode 100644 index 0000000000..15e5cce2a5 --- /dev/null +++ b/src/pages/task/actionButtons/RelevantCommits/types.ts @@ -0,0 +1,16 @@ +import { + BaseVersionAndTaskQuery, + LastMainlineCommitQuery, +} from "gql/generated/types"; + +export enum CommitType { + Base = "base", + LastPassing = "lastPassing", + Breaking = "breaking", + LastExecuted = "lastExecuted", +} + +export type BaseTask = BaseVersionAndTaskQuery["task"]["baseTask"]; + +export type CommitTask = + LastMainlineCommitQuery["mainlineCommits"]["versions"][number]["version"]["buildVariants"][number]["tasks"][number]; diff --git a/src/pages/task/actionButtons/RelevantCommits/utils.ts b/src/pages/task/actionButtons/RelevantCommits/utils.ts new file mode 100644 index 0000000000..a18f0770c3 --- /dev/null +++ b/src/pages/task/actionButtons/RelevantCommits/utils.ts @@ -0,0 +1,62 @@ +import { getTaskRoute } from "constants/routes"; +import { LastMainlineCommitQuery } from "gql/generated/types"; +import { reportError } from "utils/errorReporting"; +import { BaseTask, CommitTask, CommitType } from "./types"; + +// a link cannot be null, so it's common to use # as a substitute. +const nullLink = "#"; + +export const getLinks = ({ + breakingTask, + lastExecutedTask, + lastPassingTask, + parentTask, +}: { + breakingTask: BaseTask; + lastExecutedTask: BaseTask; + lastPassingTask: BaseTask; + parentTask: BaseTask; +}) => { + if (!parentTask) { + return { + [CommitType.Base]: nullLink, + [CommitType.Breaking]: nullLink, + [CommitType.LastPassing]: nullLink, + [CommitType.LastExecuted]: nullLink, + }; + } + + return { + [CommitType.Base]: getTaskRoute(parentTask.id), + [CommitType.Breaking]: breakingTask + ? getTaskRoute(breakingTask.id) + : nullLink, + [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 3edc9bde42fc06b6b5467fcd74817ac2cf551409 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:56:16 -0500 Subject: [PATCH 14/22] fix: github link to backend code --- .husky/pre-commit | 4 ---- src/pages/task/actionButtons/RelevantCommits/index.tsx | 2 +- src/pages/task/actionButtons/relevantCommits/index.tsx | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) delete mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 5a182ef106..0000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -yarn lint-staged diff --git a/src/pages/task/actionButtons/RelevantCommits/index.tsx b/src/pages/task/actionButtons/RelevantCommits/index.tsx index 57d0126158..739eef2c0d 100644 --- a/src/pages/task/actionButtons/RelevantCommits/index.tsx +++ b/src/pages/task/actionButtons/RelevantCommits/index.tsx @@ -91,7 +91,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { // The breaking commit is the first failing commit after the last passing commit. // The skip order number should be the last passing commit's order number + 1. // We use + 2 because internally the query does a less than comparison. - // https://github.com/ZackarySantana/spruce/blob/7018fcbce0be9db310d17c40fe948aff597fcd15/src/pages/task/actionButtons/relevantCommits/index.tsx#L93 + // https://github.com/evergreen-ci/evergreen/blob/f6751ac3194452d457c0a6fe1a9f9b30dd674c60/model/version.go#L518 const { data: breakingTaskData, loading: breakingLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index 57d0126158..739eef2c0d 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -91,7 +91,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { // The breaking commit is the first failing commit after the last passing commit. // The skip order number should be the last passing commit's order number + 1. // We use + 2 because internally the query does a less than comparison. - // https://github.com/ZackarySantana/spruce/blob/7018fcbce0be9db310d17c40fe948aff597fcd15/src/pages/task/actionButtons/relevantCommits/index.tsx#L93 + // https://github.com/evergreen-ci/evergreen/blob/f6751ac3194452d457c0a6fe1a9f9b30dd674c60/model/version.go#L518 const { data: breakingTaskData, loading: breakingLoading } = useQuery< LastMainlineCommitQuery, LastMainlineCommitQueryVariables From e0885c3471564d953acfc8da0f6fac9f12aba3af Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:57:58 -0500 Subject: [PATCH 15/22] refactor: previous commit data tag --- src/analytics/task/useTaskAnalytics.ts | 2 +- src/pages/task/actionButtons/RelevantCommits/index.tsx | 8 ++++---- src/pages/task/actionButtons/relevantCommits/index.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/analytics/task/useTaskAnalytics.ts b/src/analytics/task/useTaskAnalytics.ts index 2a8d693958..a68225c558 100644 --- a/src/analytics/task/useTaskAnalytics.ts +++ b/src/analytics/task/useTaskAnalytics.ts @@ -70,7 +70,7 @@ type Action = | { name: "Click Last Failing Stepback Task Link" } | { name: "Click Previous Stepback Task Link" } | { name: "Click Next Stepback Task Link" } - | { name: "Submit Previous Commit Selector"; type: CommitType }; + | { name: "Submit Relevant Commit Selector"; type: CommitType }; export const useTaskAnalytics = () => { const { id } = useParams<{ id: string }>(); diff --git a/src/pages/task/actionButtons/RelevantCommits/index.tsx b/src/pages/task/actionButtons/RelevantCommits/index.tsx index 739eef2c0d..4311fbfda8 100644 --- a/src/pages/task/actionButtons/RelevantCommits/index.tsx +++ b/src/pages/task/actionButtons/RelevantCommits/index.tsx @@ -174,7 +174,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={parentLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.Base, }) } @@ -187,7 +187,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={breakingLoading || breakingTask === undefined} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.Breaking, }) } @@ -200,7 +200,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={passingLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.LastPassing, }) } @@ -213,7 +213,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={executedLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.LastExecuted, }) } diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx index 739eef2c0d..4311fbfda8 100644 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ b/src/pages/task/actionButtons/relevantCommits/index.tsx @@ -174,7 +174,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={parentLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.Base, }) } @@ -187,7 +187,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={breakingLoading || breakingTask === undefined} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.Breaking, }) } @@ -200,7 +200,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={passingLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.LastPassing, }) } @@ -213,7 +213,7 @@ export const RelevantCommits: React.FC = ({ taskId }) => { disabled={executedLoading} onClick={() => sendEvent({ - name: "Submit Previous Commit Selector", + name: "Submit Relevant Commit Selector", type: CommitType.LastExecuted, }) } From 77d27e5d2b8a917bd51f3ee9a95f6b082596841b Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:59:15 -0500 Subject: [PATCH 16/22] fix: redo pre commit hook --- .husky/pre-commit | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..b946a2c596 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint-staged \ No newline at end of file From f1865c92304de5923949fbf43ef4d6e0e72b5b20 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:00:44 -0500 Subject: [PATCH 17/22] fix: revert precommit --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index b946a2c596..5a182ef106 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn lint-staged \ No newline at end of file +yarn lint-staged From a34965f4c01b9410faacecc10e54c9755d5d80e9 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:31:16 -0500 Subject: [PATCH 18/22] fix: revert it no history --- .husky/pre-commit | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 From 718962d4ca4345fe5dad9340be3a01405ad52758 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:33:45 -0500 Subject: [PATCH 19/22] refactor: rename files --- .../relevantCommits/RelevantCommits.test.tsx | 620 ------------------ .../actionButtons/relevantCommits/index.tsx | 226 ------- .../actionButtons/relevantCommits/types.ts | 16 - .../actionButtons/relevantCommits/utils.ts | 62 -- 4 files changed, 924 deletions(-) delete mode 100644 src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx delete mode 100644 src/pages/task/actionButtons/relevantCommits/index.tsx delete mode 100644 src/pages/task/actionButtons/relevantCommits/types.ts delete mode 100644 src/pages/task/actionButtons/relevantCommits/utils.ts diff --git a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx deleted file mode 100644 index bf751c6a6a..0000000000 --- a/src/pages/task/actionButtons/relevantCommits/RelevantCommits.test.tsx +++ /dev/null @@ -1,620 +0,0 @@ -import { MockedProvider } from "@apollo/client/testing"; -import { RenderFakeToastContext } from "context/toast/__mocks__"; -import { - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables, - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables, -} from "gql/generated/types"; -import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; -import { renderWithRouterMatch, screen, userEvent, waitFor } from "test_utils"; -import { ApolloMock } from "types/gql"; -import { RelevantCommits } from "."; - -describe("relevant 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 button is disabled when there is no base task", async () => { - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - }); - }); - - describe("mainline commits specific", () => { - it("the button is disabled when getParentTask returns null", async () => { - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - }); - - it("the button is disabled when getParentTask returns an error", async () => { - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - }); - }); - - it("the button is disabled when no base version exists", async () => { - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - }); - - it("when base task is passing, last passing, base commit, and last executed dropdown items generate the same link and breaking commit is disabled", async () => { - const user = userEvent.setup(); - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await screen.findByRole("button", { name: "Relevant commits" }); - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "false"); - await user.click(screen.getByRole("button", { name: "Relevant commits" })); - await waitFor(() => { - 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", baseTaskHref); - expect( - screen.getByRole("menuitem", { name: "Go to last executed version" }), - ).toHaveAttribute("href", baseTaskHref); - expect( - screen.getByRole("menuitem", { name: "Go to breaking commit" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - - 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 and 'Go to breaking commit' will be disabled", async () => { - const user = userEvent.setup(); - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await screen.findByRole("button", { name: "Relevant commits" }); - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "false"); - await user.click(screen.getByRole("button", { name: "Relevant commits" })); - await waitFor(() => { - 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); - expect( - screen.getByRole("menuitem", { name: "Go to breaking commit" }), - ).toHaveAttribute("aria-disabled", "true"); - }); - - it("when base task is not in a finished state, the last executed, passing task, and breaking task is not the same as the base commit", async () => { - const user = userEvent.setup(); - const { Component } = RenderFakeToastContext( - - - , - ); - renderWithRouterMatch(); - - await screen.findByRole("button", { name: "Relevant commits" }); - expect( - screen.getByRole("button", { name: "Relevant commits" }), - ).toHaveAttribute("aria-disabled", "false"); - await user.click(screen.getByRole("button", { name: "Relevant commits" })); - await waitFor(() => { - 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", "/task/last_executed_task"); - expect( - screen.getByRole("menuitem", { name: "Go to breaking commit" }), - ).toHaveAttribute("href", "/task/breaking_commit"); - }); -}); - -const baseTaskId = - "evergreen_lint_lint_agent_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_21_11_29_17_55_27"; -const baseTaskHref = `/task/${baseTaskId}`; - -const getPatchTaskWithSuccessfulBaseTask: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t1", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "success", - versionMetadata: { - baseVersion: { - id: "baseVersion", - order: 3676, - __typename: "Version", - }, - isPatch: true, - id: "versionMetadataId", - __typename: "Version", - }, - baseTask: { - id: baseTaskId, - execution: 0, - status: "success", - __typename: "Task", - }, - __typename: "Task", - }, - }, - }, -}; - -const getPatchTaskWithRunningBaseTask: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t3", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "started", - versionMetadata: { - baseVersion: { - id: "baseVersion", - order: 3676, - __typename: "Version", - }, - isPatch: true, - id: "versionMetadataId", - __typename: "Version", - }, - baseTask: { - id: baseTaskId, - execution: 0, - status: "started", - __typename: "Task", - }, - __typename: "Task", - }, - }, - }, -}; - -const getPatchTaskWithFailingBaseTask: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t1", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "success", - versionMetadata: { - baseVersion: { - id: "baseVersion", - order: 3676, - __typename: "Version", - }, - isPatch: true, - id: "versionMetadataId", - __typename: "Version", - }, - baseTask: { - id: baseTaskId, - execution: 0, - status: "failed", - __typename: "Task", - }, - __typename: "Task", - }, - }, - }, -}; - -const getPatchTaskWithNoBaseVersion: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t3", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "success", - versionMetadata: { - baseVersion: null, - id: "versionMetadataId", - isPatch: true, - __typename: "Version", - }, - baseTask: null, - __typename: "Task", - }, - }, - }, -}; - -const getBreakingCommit: ApolloMock< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables -> = { - request: { - query: LAST_MAINLINE_COMMIT, - variables: { - projectIdentifier: "evergreen", - skipOrderNumber: 3678, - buildVariantOptions: { - tasks: ["^lint-agent$"], - variants: ["^lint$"], - statuses: ["failed"], - }, - }, - }, - result: { - data: { - mainlineCommits: { - versions: [ - { - version: { - id: "evergreen_44110b57c6977bf3557009193628c9389772163f2", - buildVariants: [ - { - tasks: [ - { - id: "breaking_commit", - execution: 0, - order: 3677, - status: "failed", - __typename: "Task", - }, - ], - __typename: "GroupedBuildVariant", - }, - ], - __typename: "Version", - }, - __typename: "MainlineCommitVersion", - }, - ], - __typename: "MainlineCommits", - }, - }, - }, -}; - -const getLastPassingVersion: ApolloMock< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables -> = { - request: { - query: LAST_MAINLINE_COMMIT, - variables: { - projectIdentifier: "evergreen", - skipOrderNumber: 3676, - buildVariantOptions: { - tasks: ["^lint-agent$"], - variants: ["^lint$"], - statuses: ["success"], - }, - }, - }, - result: { - data: { - mainlineCommits: { - versions: [ - { - version: { - id: "evergreen_44110b57c6977bf3557009193628c9389772163f", - buildVariants: [ - { - tasks: [ - { - id: "last_passing_task", - execution: 0, - order: 3676, - status: "success", - __typename: "Task", - }, - ], - __typename: "GroupedBuildVariant", - }, - ], - __typename: "Version", - }, - __typename: "MainlineCommitVersion", - }, - ], - __typename: "MainlineCommits", - }, - }, - }, -}; - -const getLastExecutedVersion: ApolloMock< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables -> = { - request: { - query: LAST_MAINLINE_COMMIT, - variables: { - projectIdentifier: "evergreen", - skipOrderNumber: 3676, - buildVariantOptions: { - tasks: ["^lint-agent$"], - variants: ["^lint$"], - statuses: [ - "failed", - "setup-failed", - "system-failed", - "task-timed-out", - "test-timed-out", - "known-issue", - "system-unresponsive", - "system-timed-out", - "success", - ], - }, - }, - }, - result: { - data: { - mainlineCommits: { - versions: [ - { - version: { - id: "evergreen_44110b57c6977bf3557009193628c9389772163f", - buildVariants: [ - { - tasks: [ - { - id: "last_executed_task", - execution: 0, - order: 3676, - status: "failed", - __typename: "Task", - }, - ], - __typename: "GroupedBuildVariant", - }, - ], - __typename: "Version", - }, - __typename: "MainlineCommitVersion", - }, - ], - __typename: "MainlineCommits", - }, - }, - }, -}; - -// patch specific -const getPatchTaskWithNoBaseTask: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t1", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "success", - versionMetadata: { - baseVersion: { - id: "baseVersion", - order: 3676, - __typename: "Version", - }, - isPatch: true, - id: "versionMetadataId", - __typename: "Version", - }, - baseTask: null, - __typename: "Task", - }, - }, - }, -}; - -// Mainline commits specific -const getMainlineTaskWithBaseVersion: ApolloMock< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables -> = { - request: { - query: BASE_VERSION_AND_TASK, - variables: { - taskId: "t4", - }, - }, - result: { - data: { - task: { - id: "evergreen_lint_lint_agent_patch_f4fe4814088e13b8ef423a73d65a6e0a5579cf93_61a8edf132f41750ab47bc72_21_12_02_16_01_54", - execution: 0, - displayName: "lint-agent", - buildVariant: "lint", - projectIdentifier: "evergreen", - status: "success", - versionMetadata: { - baseVersion: { - id: "baseVersion", - order: 3676, - __typename: "Version", - }, - isPatch: false, - id: "versionMetadataId", - __typename: "Version", - }, - baseTask: null, - __typename: "Task", - }, - }, - }, -}; - -const getNullParentTask: ApolloMock< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables -> = { - request: { - query: LAST_MAINLINE_COMMIT, - variables: { - projectIdentifier: "evergreen", - skipOrderNumber: 3676, - buildVariantOptions: { - tasks: ["^lint-agent$"], - variants: ["^lint$"], - statuses: ["success"], - }, - }, - }, - error: new Error("Matching version not found in 300 most recent versions"), -}; - -const getParentTaskWithError: ApolloMock< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables -> = { - request: { - query: LAST_MAINLINE_COMMIT, - variables: { - projectIdentifier: "evergreen", - skipOrderNumber: 3676, - buildVariantOptions: { - tasks: ["^lint-agent$"], - variants: ["^lint$"], - statuses: ["success"], - }, - }, - }, - error: new Error("Matching version not found in 300 most recent versions"), -}; diff --git a/src/pages/task/actionButtons/relevantCommits/index.tsx b/src/pages/task/actionButtons/relevantCommits/index.tsx deleted file mode 100644 index 4311fbfda8..0000000000 --- a/src/pages/task/actionButtons/relevantCommits/index.tsx +++ /dev/null @@ -1,226 +0,0 @@ -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 Icon from "components/Icon"; -import { finishedTaskStatuses } from "constants/task"; -import { useToastContext } from "context/toast"; -import { - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables, - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables, -} from "gql/generated/types"; -import { BASE_VERSION_AND_TASK, LAST_MAINLINE_COMMIT } from "gql/queries"; -import { TaskStatus } from "types/task"; -import { statuses, string } from "utils"; -import { CommitType } from "./types"; -import { getLinks, getTaskFromMainlineCommitsQuery } from "./utils"; - -const { applyStrictRegex } = string; -const { isFailedTaskStatus, isFinishedTaskStatus } = statuses; - -interface RelevantCommitsProps { - taskId: string; -} -export const RelevantCommits: React.FC = ({ taskId }) => { - const { sendEvent } = useTaskAnalytics(); - const dispatchToast = useToastContext(); - - const { data: taskData } = useQuery< - BaseVersionAndTaskQuery, - BaseVersionAndTaskQueryVariables - >(BASE_VERSION_AND_TASK, { - variables: { taskId }, - }); - - const { - baseTask, - buildVariant, - displayName, - projectIdentifier, - status, - versionMetadata, - } = taskData?.task ?? {}; - const { order: skipOrderNumber } = versionMetadata?.baseVersion ?? {}; - - const bvOptionsBase = { - tasks: [applyStrictRegex(displayName)], - variants: [applyStrictRegex(buildVariant)], - }; - - const { data: parentTaskData, loading: parentLoading } = useQuery< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables - >(LAST_MAINLINE_COMMIT, { - skip: !versionMetadata || versionMetadata.isPatch, - variables: { - projectIdentifier, - skipOrderNumber, - buildVariantOptions: { - ...bvOptionsBase, - }, - }, - }); - const parentTask = - getTaskFromMainlineCommitsQuery(parentTaskData) ?? baseTask; - - const { data: lastPassingTaskData, loading: passingLoading } = useQuery< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables - >(LAST_MAINLINE_COMMIT, { - 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 passingOrderNumber = lastPassingTask?.order; - - // The breaking commit is the first failing commit after the last passing commit. - // The skip order number should be the last passing commit's order number + 1. - // We use + 2 because internally the query does a less than comparison. - // https://github.com/evergreen-ci/evergreen/blob/f6751ac3194452d457c0a6fe1a9f9b30dd674c60/model/version.go#L518 - const { data: breakingTaskData, loading: breakingLoading } = useQuery< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables - >(LAST_MAINLINE_COMMIT, { - skip: !parentTask || !lastPassingTask || !isFailedTaskStatus(status), - variables: { - projectIdentifier, - skipOrderNumber: passingOrderNumber + 2, - buildVariantOptions: { - ...bvOptionsBase, - statuses: [TaskStatus.Failed], - }, - }, - onError: (err) => { - dispatchToast.error(`Breaking commit unavailable: '${err.message}'`); - }, - }); - const breakingTask = getTaskFromMainlineCommitsQuery(breakingTaskData); - - const { data: lastExecutedTaskData, loading: executedLoading } = useQuery< - LastMainlineCommitQuery, - LastMainlineCommitQueryVariables - >(LAST_MAINLINE_COMMIT, { - skip: !parentTask || isFinishedTaskStatus(parentTask.status), - variables: { - projectIdentifier, - skipOrderNumber, - buildVariantOptions: { - ...bvOptionsBase, - statuses: finishedTaskStatuses, - }, - }, - onError: (err) => { - dispatchToast.error( - `Could not fetch last task execution: '${err.message}'`, - ); - }, - }); - const lastExecutedTask = - getTaskFromMainlineCommitsQuery(lastExecutedTaskData); - - const linkObject = useMemo( - () => - getLinks({ - parentTask, - breakingTask, - lastPassingTask, - lastExecutedTask, - }), - [parentTask, breakingTask, lastPassingTask, lastExecutedTask], - ); - - const menuDisabled = !baseTask || !parentTask; - - return menuDisabled ? ( - } - size={Size.Small} - > - Relevant commits - - } - > - No relevant versions available. - - ) : ( - } size={Size.Small}> - Relevant commits - - } - > - - sendEvent({ - name: "Submit Relevant Commit Selector", - type: CommitType.Base, - }) - } - to={linkObject[CommitType.Base]} - > - Go to {versionMetadata?.isPatch ? "base" : "previous"} commit - - - sendEvent({ - name: "Submit Relevant Commit Selector", - type: CommitType.Breaking, - }) - } - to={linkObject[CommitType.Breaking]} - > - Go to breaking commit - - - sendEvent({ - name: "Submit Relevant Commit Selector", - type: CommitType.LastPassing, - }) - } - to={linkObject[CommitType.LastPassing]} - > - Go to last passing version - - - sendEvent({ - name: "Submit Relevant Commit Selector", - type: CommitType.LastExecuted, - }) - } - to={linkObject[CommitType.LastExecuted]} - > - Go to last executed version - - - ); -}; diff --git a/src/pages/task/actionButtons/relevantCommits/types.ts b/src/pages/task/actionButtons/relevantCommits/types.ts deleted file mode 100644 index 15e5cce2a5..0000000000 --- a/src/pages/task/actionButtons/relevantCommits/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - BaseVersionAndTaskQuery, - LastMainlineCommitQuery, -} from "gql/generated/types"; - -export enum CommitType { - Base = "base", - LastPassing = "lastPassing", - Breaking = "breaking", - LastExecuted = "lastExecuted", -} - -export type BaseTask = BaseVersionAndTaskQuery["task"]["baseTask"]; - -export type CommitTask = - LastMainlineCommitQuery["mainlineCommits"]["versions"][number]["version"]["buildVariants"][number]["tasks"][number]; diff --git a/src/pages/task/actionButtons/relevantCommits/utils.ts b/src/pages/task/actionButtons/relevantCommits/utils.ts deleted file mode 100644 index a18f0770c3..0000000000 --- a/src/pages/task/actionButtons/relevantCommits/utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { getTaskRoute } from "constants/routes"; -import { LastMainlineCommitQuery } from "gql/generated/types"; -import { reportError } from "utils/errorReporting"; -import { BaseTask, CommitTask, CommitType } from "./types"; - -// a link cannot be null, so it's common to use # as a substitute. -const nullLink = "#"; - -export const getLinks = ({ - breakingTask, - lastExecutedTask, - lastPassingTask, - parentTask, -}: { - breakingTask: BaseTask; - lastExecutedTask: BaseTask; - lastPassingTask: BaseTask; - parentTask: BaseTask; -}) => { - if (!parentTask) { - return { - [CommitType.Base]: nullLink, - [CommitType.Breaking]: nullLink, - [CommitType.LastPassing]: nullLink, - [CommitType.LastExecuted]: nullLink, - }; - } - - return { - [CommitType.Base]: getTaskRoute(parentTask.id), - [CommitType.Breaking]: breakingTask - ? getTaskRoute(breakingTask.id) - : nullLink, - [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 e4af0689f44e22e1cb2334b55389db6eaee0c87c Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:25:42 -0500 Subject: [PATCH 20/22] fix: skip order number in test --- .../task/actionButtons/RelevantCommits/RelevantCommits.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx index bf751c6a6a..d483daf2d3 100644 --- a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx +++ b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx @@ -361,7 +361,7 @@ const getBreakingCommit: ApolloMock< query: LAST_MAINLINE_COMMIT, variables: { projectIdentifier: "evergreen", - skipOrderNumber: 3678, + skipOrderNumber: 3679, buildVariantOptions: { tasks: ["^lint-agent$"], variants: ["^lint$"], From a13877b43d3df40935fff07c805d288231d29234 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:09:13 -0500 Subject: [PATCH 21/22] fix: test cases --- .../RelevantCommits/RelevantCommits.test.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx index d483daf2d3..10edc1b38f 100644 --- a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx +++ b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx @@ -120,11 +120,15 @@ describe("relevant commits", () => { ).toHaveAttribute("aria-disabled", "true"); }); - 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 and 'Go to breaking commit' will be disabled", async () => { + 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 and 'Go to breaking commit' will be populated", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( , @@ -151,10 +155,10 @@ describe("relevant commits", () => { ).toHaveAttribute("href", baseTaskHref); expect( screen.getByRole("menuitem", { name: "Go to breaking commit" }), - ).toHaveAttribute("aria-disabled", "true"); + ).toHaveAttribute("href", "/task/breaking_commit"); }); - it("when base task is not in a finished state, the last executed, passing task, and breaking task is not the same as the base commit", async () => { + it("when base task is not in a finished state, the last executed, and passing task are not the same as the base commit and breaking commit is empty", async () => { const user = userEvent.setup(); const { Component } = RenderFakeToastContext( { ).toHaveAttribute("href", "/task/last_executed_task"); expect( screen.getByRole("menuitem", { name: "Go to breaking commit" }), - ).toHaveAttribute("href", "/task/breaking_commit"); + ).toHaveAttribute("aria-disabled", "true"); }); }); @@ -361,7 +365,7 @@ const getBreakingCommit: ApolloMock< query: LAST_MAINLINE_COMMIT, variables: { projectIdentifier: "evergreen", - skipOrderNumber: 3679, + skipOrderNumber: 3678, buildVariantOptions: { tasks: ["^lint-agent$"], variants: ["^lint$"], @@ -382,7 +386,7 @@ const getBreakingCommit: ApolloMock< { id: "breaking_commit", execution: 0, - order: 3677, + order: 3676, status: "failed", __typename: "Task", }, From ef76d29bbf7de2de697e0ad813383a2d59c974a7 Mon Sep 17 00:00:00 2001 From: Zackary Santana <64446617+ZackarySantana@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:43:45 -0500 Subject: [PATCH 22/22] fix: test --- .../actionButtons/RelevantCommits/RelevantCommits.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx index 10edc1b38f..e47606ed0f 100644 --- a/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx +++ b/src/pages/task/actionButtons/RelevantCommits/RelevantCommits.test.tsx @@ -302,7 +302,7 @@ const getPatchTaskWithFailingBaseTask: ApolloMock< displayName: "lint-agent", buildVariant: "lint", projectIdentifier: "evergreen", - status: "success", + status: "failed", versionMetadata: { baseVersion: { id: "baseVersion", @@ -365,7 +365,7 @@ const getBreakingCommit: ApolloMock< query: LAST_MAINLINE_COMMIT, variables: { projectIdentifier: "evergreen", - skipOrderNumber: 3678, + skipOrderNumber: 3676, buildVariantOptions: { tasks: ["^lint-agent$"], variants: ["^lint$"], @@ -434,7 +434,7 @@ const getLastPassingVersion: ApolloMock< { id: "last_passing_task", execution: 0, - order: 3676, + order: 3674, status: "success", __typename: "Task", },