From 69137bce4dec7148a71ab43a3861f768f5467d23 Mon Sep 17 00:00:00 2001
From: Malik Hadjri <19805673+hadjri@users.noreply.github.com>
Date: Thu, 12 Sep 2024 16:32:34 -0400
Subject: [PATCH] DEVPROD-7444 Add task scheduling warnings (#309)
---
.../Banners/TaskSchedulingWarningBanner.tsx | 20 ++++
.../ScheduleTasks/testData.ts | 2 +
.../components/ScheduleTasksModal/index.tsx | 12 ++
.../VersionRestartModal.tsx | 10 ++
.../spruce/src/constants/externalResources.ts | 2 +
apps/spruce/src/gql/generated/types.ts | 21 ++++
.../build-variants-with-children.graphql | 8 ++
.../src/gql/queries/patch-configure.graphql | 5 +
.../gql/queries/undispatched-tasks.graphql | 4 +
...ePatchCore_ConfigureTasksDefault.storyshot | 9 +-
.../configurePatchCore/index.tsx | 18 +++
.../configurePatchCore/testData.ts | 1 +
.../GithubCommitQueueTab.tsx | 2 +-
.../tasks/estimatedActivatedTasks.test.ts | 108 ++++++++++++++++++
.../utils/tasks/estimatedActivatedTasks.ts | 98 ++++++++++++++++
15 files changed, 316 insertions(+), 4 deletions(-)
create mode 100644 apps/spruce/src/components/Banners/TaskSchedulingWarningBanner.tsx
create mode 100644 apps/spruce/src/utils/tasks/estimatedActivatedTasks.test.ts
create mode 100644 apps/spruce/src/utils/tasks/estimatedActivatedTasks.ts
diff --git a/apps/spruce/src/components/Banners/TaskSchedulingWarningBanner.tsx b/apps/spruce/src/components/Banners/TaskSchedulingWarningBanner.tsx
new file mode 100644
index 000000000..e20c8dd2e
--- /dev/null
+++ b/apps/spruce/src/components/Banners/TaskSchedulingWarningBanner.tsx
@@ -0,0 +1,20 @@
+import Banner from "@leafygreen-ui/banner";
+import { StyledLink } from "components/styles";
+import { taskSchedulingLimitsDocumentationUrl } from "constants/externalResources";
+
+interface TaskSchedulingWarningBannerProps {
+ totalTasks: number;
+}
+const largeNumFinalizedTasksThreshold = 1000;
+
+export const TaskSchedulingWarningBanner: React.FC<
+ TaskSchedulingWarningBannerProps
+> = ({ totalTasks }) =>
+ totalTasks >= largeNumFinalizedTasksThreshold ? (
+
+ This is a large operation, expected to schedule {totalTasks} tasks. Please
+ confirm that this number of tasks is necessary before continuing. For more
+ information, please refer to our{" "}
+ docs.
+
+ ) : null;
diff --git a/apps/spruce/src/components/PatchActionButtons/ScheduleTasks/testData.ts b/apps/spruce/src/components/PatchActionButtons/ScheduleTasks/testData.ts
index bacc57e9f..c2b35c0a8 100644
--- a/apps/spruce/src/components/PatchActionButtons/ScheduleTasks/testData.ts
+++ b/apps/spruce/src/components/PatchActionButtons/ScheduleTasks/testData.ts
@@ -19,6 +19,7 @@ const mocks: ApolloMock<
version: {
__typename: "Version",
id: "version_id",
+ generatedTaskCounts: [],
tasks: {
__typename: "VersionTasks",
data: [
@@ -150,6 +151,7 @@ const mocks: ApolloMock<
version: {
__typename: "Version",
id: "version_empty",
+ generatedTaskCounts: [],
tasks: {
__typename: "VersionTasks",
data: [],
diff --git a/apps/spruce/src/components/ScheduleTasksModal/index.tsx b/apps/spruce/src/components/ScheduleTasksModal/index.tsx
index 6e84c71a3..85b46b1a6 100644
--- a/apps/spruce/src/components/ScheduleTasksModal/index.tsx
+++ b/apps/spruce/src/components/ScheduleTasksModal/index.tsx
@@ -5,6 +5,7 @@ import Checkbox from "@leafygreen-ui/checkbox";
import { Body } from "@leafygreen-ui/typography";
import { Skeleton } from "antd";
import { Accordion } from "components/Accordion";
+import { TaskSchedulingWarningBanner } from "components/Banners/TaskSchedulingWarningBanner";
import { ConfirmationModal } from "components/ConfirmationModal";
import { size } from "constants/tokens";
import { useToastContext } from "context/toast";
@@ -16,6 +17,7 @@ import {
} from "gql/generated/types";
import { SCHEDULE_TASKS } from "gql/mutations";
import { UNSCHEDULED_TASKS } from "gql/queries";
+import { sumActivatedTasksInSet } from "utils/tasks/estimatedActivatedTasks";
import { initialState, reducer } from "./reducer";
interface ScheduleTasksModalProps {
@@ -70,6 +72,13 @@ export const ScheduleTasksModal: React.FC = ({
dispatch({ type: "ingestData", taskData });
}, [taskData]);
+ const { generatedTaskCounts = [] } = taskData?.version ?? {};
+
+ const estimatedActivatedTasksCount = sumActivatedTasksInSet(
+ selectedTasks,
+ generatedTaskCounts,
+ );
+
return (
= ({
);
},
)}
+
>
)}
{!loadingTaskData && !sortedBuildVariantGroups.length && (
diff --git a/apps/spruce/src/components/VersionRestartModal/VersionRestartModal.tsx b/apps/spruce/src/components/VersionRestartModal/VersionRestartModal.tsx
index b18ab817b..636fef741 100644
--- a/apps/spruce/src/components/VersionRestartModal/VersionRestartModal.tsx
+++ b/apps/spruce/src/components/VersionRestartModal/VersionRestartModal.tsx
@@ -7,6 +7,7 @@ import { Skeleton } from "antd";
import { TaskStatus } from "@evg-ui/lib/types/task";
import { useVersionAnalytics } from "analytics";
import { Accordion } from "components/Accordion";
+import { TaskSchedulingWarningBanner } from "components/Banners/TaskSchedulingWarningBanner";
import { ConfirmationModal } from "components/ConfirmationModal";
import { finishedTaskStatuses } from "constants/task";
import { size } from "constants/tokens";
@@ -24,6 +25,7 @@ import {
versionSelectedTasks,
selectedStrings,
} from "hooks/useVersionTaskStatusSelect";
+import { sumActivatedTasksInSelectedTasks } from "utils/tasks/estimatedActivatedTasks";
import VersionTasks from "./VersionTasks";
interface VersionRestartModalProps {
@@ -110,6 +112,11 @@ const VersionRestartModal: React.FC = ({
const selectedTotal = selectTasksTotal(selectedTasks || {});
+ const { generatedTaskCounts = [] } = version ?? {};
+ const estimatedActivatedTasksCount = sumActivatedTasksInSelectedTasks(
+ selectedTasks || {},
+ generatedTaskCounts,
+ );
return (
= ({
)}
+
Are you sure you want to restart the {selectedTotal} selected tasks?
diff --git a/apps/spruce/src/constants/externalResources.ts b/apps/spruce/src/constants/externalResources.ts
index a66b07d1b..09b65153d 100644
--- a/apps/spruce/src/constants/externalResources.ts
+++ b/apps/spruce/src/constants/externalResources.ts
@@ -50,6 +50,8 @@ export const cliDocumentationUrl = `${wikiBaseUrl}/CLI`;
export const containersOnboardingDocumentationUrl = `${wikiBaseUrl}/Containers/Container-Tasks`;
+export const taskSchedulingLimitsDocumentationUrl = `${wikiBaseUrl}/Reference/Limits#task-scheduling-limits`;
+
export const windowsPasswordRulesURL =
"https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc786468(v=ws.10)?redirectedfrom=MSDN";
diff --git a/apps/spruce/src/gql/generated/types.ts b/apps/spruce/src/gql/generated/types.ts
index f5ebe1481..d9e225593 100644
--- a/apps/spruce/src/gql/generated/types.ts
+++ b/apps/spruce/src/gql/generated/types.ts
@@ -5836,7 +5836,17 @@ export type BuildVariantsWithChildrenQuery = {
status: string;
}> | null;
}> | null;
+ generatedTaskCounts: Array<{
+ __typename?: "GeneratedTaskCountResults";
+ estimatedTasks: number;
+ taskId?: string | null;
+ }>;
}> | null;
+ generatedTaskCounts: Array<{
+ __typename?: "GeneratedTaskCountResults";
+ estimatedTasks: number;
+ taskId?: string | null;
+ }>;
};
};
@@ -6845,6 +6855,12 @@ export type ConfigurePatchQuery = {
tasks: Array;
}>;
}> | null;
+ generatedTaskCounts: Array<{
+ __typename?: "GeneratedTaskCountResults";
+ buildVariantName?: string | null;
+ estimatedTasks: number;
+ taskName?: string | null;
+ }>;
patchTriggerAliases: Array<{
__typename?: "PatchTriggerAlias";
alias: string;
@@ -9014,6 +9030,11 @@ export type UndispatchedTasksQuery = {
version: {
__typename?: "Version";
id: string;
+ generatedTaskCounts: Array<{
+ __typename?: "GeneratedTaskCountResults";
+ estimatedTasks: number;
+ taskId?: string | null;
+ }>;
tasks: {
__typename?: "VersionTasks";
data: Array<{
diff --git a/apps/spruce/src/gql/queries/build-variants-with-children.graphql b/apps/spruce/src/gql/queries/build-variants-with-children.graphql
index 50571ffa5..acb7904dc 100644
--- a/apps/spruce/src/gql/queries/build-variants-with-children.graphql
+++ b/apps/spruce/src/gql/queries/build-variants-with-children.graphql
@@ -23,10 +23,18 @@ query BuildVariantsWithChildren($id: String!, $statuses: [String!]!) {
}
variant
}
+ generatedTaskCounts {
+ estimatedTasks
+ taskId
+ }
id
project
projectIdentifier
}
+ generatedTaskCounts {
+ estimatedTasks
+ taskId
+ }
id
}
}
diff --git a/apps/spruce/src/gql/queries/patch-configure.graphql b/apps/spruce/src/gql/queries/patch-configure.graphql
index dbed78cf1..88e03f035 100644
--- a/apps/spruce/src/gql/queries/patch-configure.graphql
+++ b/apps/spruce/src/gql/queries/patch-configure.graphql
@@ -15,6 +15,11 @@ query ConfigurePatch($id: String!) {
tasks
}
}
+ generatedTaskCounts {
+ buildVariantName
+ estimatedTasks
+ taskName
+ }
patchTriggerAliases {
alias
childProjectId
diff --git a/apps/spruce/src/gql/queries/undispatched-tasks.graphql b/apps/spruce/src/gql/queries/undispatched-tasks.graphql
index 6058a5570..e49731497 100644
--- a/apps/spruce/src/gql/queries/undispatched-tasks.graphql
+++ b/apps/spruce/src/gql/queries/undispatched-tasks.graphql
@@ -1,5 +1,9 @@
query UndispatchedTasks($versionId: String!) {
version(versionId: $versionId) {
+ generatedTaskCounts {
+ estimatedTasks
+ taskId
+ }
id
tasks(
options: { statuses: ["unscheduled"], includeNeverActivatedTasks: true }
diff --git a/apps/spruce/src/pages/configurePatch/configurePatchCore/__snapshots__/ConfigurePatchCore_ConfigureTasksDefault.storyshot b/apps/spruce/src/pages/configurePatch/configurePatchCore/__snapshots__/ConfigurePatchCore_ConfigureTasksDefault.storyshot
index 49f7f4173..6fde50e65 100644
--- a/apps/spruce/src/pages/configurePatch/configurePatchCore/__snapshots__/ConfigurePatchCore_ConfigureTasksDefault.storyshot
+++ b/apps/spruce/src/pages/configurePatch/configurePatchCore/__snapshots__/ConfigurePatchCore_ConfigureTasksDefault.storyshot
@@ -1,9 +1,9 @@
+
diff --git a/apps/spruce/src/pages/configurePatch/configurePatchCore/index.tsx b/apps/spruce/src/pages/configurePatch/configurePatchCore/index.tsx
index ceef2ddb5..f7a4f6e5e 100644
--- a/apps/spruce/src/pages/configurePatch/configurePatchCore/index.tsx
+++ b/apps/spruce/src/pages/configurePatch/configurePatchCore/index.tsx
@@ -5,6 +5,7 @@ import Button from "@leafygreen-ui/button";
import { Tab } from "@leafygreen-ui/tabs";
import TextInput from "@leafygreen-ui/text-input";
import { useNavigate } from "react-router-dom";
+import { TaskSchedulingWarningBanner } from "components/Banners/TaskSchedulingWarningBanner";
import { LoadingButton } from "components/Buttons";
import { CodeChanges } from "components/CodeChanges";
import {
@@ -33,6 +34,7 @@ import {
ProjectBuildVariant,
} from "gql/generated/types";
import { SCHEDULE_PATCH } from "gql/mutations";
+import { sumActivatedTasksInVariantsTasks } from "utils/tasks/estimatedActivatedTasks";
import { ConfigureBuildVariants } from "./ConfigureBuildVariants";
import ConfigureTasks from "./ConfigureTasks";
import { ParametersContent } from "./ParametersContent";
@@ -56,6 +58,7 @@ const ConfigurePatchCore: React.FC = ({ patch }) => {
author,
childPatchAliases,
childPatches,
+ generatedTaskCounts,
id,
patchTriggerAliases,
project,
@@ -154,6 +157,12 @@ const ConfigurePatchCore: React.FC = ({ patch }) => {
);
}
+ const estimatedActivatedTasksCount = sumActivatedTasksInVariantsTasks(
+ selectedBuildVariantTasks,
+ generatedTaskCounts,
+ initialPatch.variantsTasks,
+ );
+
return (
<>
@@ -187,6 +196,11 @@ const ConfigurePatchCore: React.FC = ({ patch }) => {
+
+
+
@@ -348,4 +362,8 @@ const FlexRow = styled.div`
gap: ${size.s};
`;
+const BannerContainer = styled.div`
+ margin-bottom: ${size.s};
+`;
+
export default ConfigurePatchCore;
diff --git a/apps/spruce/src/pages/configurePatch/configurePatchCore/testData.ts b/apps/spruce/src/pages/configurePatch/configurePatchCore/testData.ts
index 59d67c095..7f361c615 100644
--- a/apps/spruce/src/pages/configurePatch/configurePatchCore/testData.ts
+++ b/apps/spruce/src/pages/configurePatch/configurePatchCore/testData.ts
@@ -14,6 +14,7 @@ export const patchQuery: ConfigurePatchQuery = {
projectIdentifier: "spruce",
author: "mohamed.khelif",
activated: false,
+ generatedTaskCounts: [],
status: "created",
time: {
submittedAt: "2020-08-28T15:00:17Z",
diff --git a/apps/spruce/src/pages/projectSettings/tabs/GithubCommitQueueTab/GithubCommitQueueTab.tsx b/apps/spruce/src/pages/projectSettings/tabs/GithubCommitQueueTab/GithubCommitQueueTab.tsx
index 539e205bc..0d30ef8c5 100644
--- a/apps/spruce/src/pages/projectSettings/tabs/GithubCommitQueueTab/GithubCommitQueueTab.tsx
+++ b/apps/spruce/src/pages/projectSettings/tabs/GithubCommitQueueTab/GithubCommitQueueTab.tsx
@@ -89,7 +89,7 @@ export const GithubCommitQueueTab: React.FC = ({
return (
<>
{!githubWebhooksEnabled && (
-
+
GitHub features are disabled because the Evergreen GitHub App is not
installed on the saved owner/repo. Contact IT to install the App and
enable GitHub features.
diff --git a/apps/spruce/src/utils/tasks/estimatedActivatedTasks.test.ts b/apps/spruce/src/utils/tasks/estimatedActivatedTasks.test.ts
new file mode 100644
index 000000000..69b98b911
--- /dev/null
+++ b/apps/spruce/src/utils/tasks/estimatedActivatedTasks.test.ts
@@ -0,0 +1,108 @@
+import { GeneratedTaskCountResults, VariantTask } from "gql/generated/types";
+import { versionSelectedTasks } from "hooks/useVersionTaskStatusSelect";
+import { VariantTasksState } from "pages/configurePatch/configurePatchCore/useConfigurePatch/types";
+import {
+ sumActivatedTasksInSelectedTasks,
+ sumActivatedTasksInSet,
+ sumActivatedTasksInVariantsTasks,
+} from "./estimatedActivatedTasks";
+
+describe("getNumEstimatedActivatedTasks", () => {
+ const generatedTaskCounts: GeneratedTaskCountResults[] = [
+ { taskName: "task1", buildVariantName: "variant1", estimatedTasks: 5 },
+ { taskName: "task2", buildVariantName: "variant1", estimatedTasks: 10 },
+ { taskName: "task4", buildVariantName: "variant1", estimatedTasks: 20 },
+ { taskId: "task1-variant2", estimatedTasks: 100 },
+ { taskId: "task2-variant2", estimatedTasks: 50 },
+ { taskId: "task4-variant2", estimatedTasks: 25 },
+ ];
+
+ it("should compute the correct number of activated tasks to be created when configuring a patch where some tasks have already been created", () => {
+ const selectedBuildVariantTasks: VariantTasksState = {
+ variant1: {
+ task1: true,
+ task2: true,
+ task3: true,
+ task4: false,
+ },
+ };
+ const variantsTasks: Array = [
+ { name: "variant1", tasks: ["task1"] },
+ ];
+ expect(
+ sumActivatedTasksInVariantsTasks(
+ selectedBuildVariantTasks,
+ generatedTaskCounts,
+ variantsTasks,
+ ),
+ ).toBe(12);
+ });
+ it("should compute zero when configuring a patch where all selected tasks have already been created", () => {
+ const selectedBuildVariantTasks: VariantTasksState = {
+ variant1: {
+ task1: true,
+ task2: true,
+ task3: true,
+ },
+ };
+ const variantsTasks: Array = [
+ { name: "variant1", tasks: ["task1"] },
+ { name: "variant1", tasks: ["task2"] },
+ { name: "variant1", tasks: ["task3"] },
+ ];
+ expect(
+ sumActivatedTasksInVariantsTasks(
+ selectedBuildVariantTasks,
+ generatedTaskCounts,
+ variantsTasks,
+ ),
+ ).toBe(0);
+ });
+ it("should compute the correct number of activated tasks to be created when configuring a patch where no tasks have already been created", () => {
+ const selectedBuildVariantTasks: VariantTasksState = {
+ variant1: {
+ task1: true,
+ task2: true,
+ task3: true,
+ },
+ };
+ expect(
+ sumActivatedTasksInVariantsTasks(
+ selectedBuildVariantTasks,
+ generatedTaskCounts,
+ [],
+ ),
+ ).toBe(18);
+ });
+ it("should compute the correct number of activated tasks to be created when scheduling multiple unscheduled tasks", () => {
+ const set = new Set(["task1-variant2", "task2-variant2", "task3-variant2"]);
+ expect(sumActivatedTasksInSet(set, generatedTaskCounts)).toBe(153);
+ });
+ it("should compute the correct number of activated tasks to be created when restarting all tasks in a version", () => {
+ const vsts: versionSelectedTasks = {
+ version_id: {
+ "task1-variant2": true,
+ "task2-variant2": true,
+ "task3-variant2": true,
+ },
+ };
+ expect(sumActivatedTasksInSelectedTasks(vsts, generatedTaskCounts)).toBe(
+ 153,
+ );
+ });
+ it("should compute the correct number of activated tasks to be created when restarting some tasks in a version", () => {
+ const vsts: versionSelectedTasks = {
+ version_id: {
+ "task1-variant2": true,
+ "task2-variant2": false,
+ "task3-variant2": true,
+ },
+ };
+ expect(sumActivatedTasksInSelectedTasks(vsts, generatedTaskCounts)).toBe(
+ 102,
+ );
+ });
+ it("should compute zero for empty input", () => {
+ expect(sumActivatedTasksInSelectedTasks({}, [])).toBe(0);
+ });
+});
diff --git a/apps/spruce/src/utils/tasks/estimatedActivatedTasks.ts b/apps/spruce/src/utils/tasks/estimatedActivatedTasks.ts
new file mode 100644
index 000000000..2b0b91fa9
--- /dev/null
+++ b/apps/spruce/src/utils/tasks/estimatedActivatedTasks.ts
@@ -0,0 +1,98 @@
+import { GeneratedTaskCountResults, VariantTask } from "gql/generated/types";
+import { versionSelectedTasks } from "hooks/useVersionTaskStatusSelect";
+import { VariantTasksState } from "pages/configurePatch/configurePatchCore/useConfigurePatch/types";
+
+/**
+ * The following functions compute the number of tasks that are estimated to be scheduled as a result of a scheduling / restart operation,
+ * depending on the type of data structure that is in.
+ */
+
+/**
+ * `sumActivatedTasksInSet` Computes activated tasks for the "Schedule Tasks" modal.
+ * Sums the input set size with the `estimatedTasks` values from the `GeneratedTaskCounts` array for tasks that are present in the provided `Set`.
+ * @param taskIdSet - A set of task ID strings. This set defines which tasks to include in the sum.
+ * @param generatedTaskCounts - An array of objects containing info on the estimated number of tasks that each generator task (a task that calls generate.tasks)
+ will schedule, including the `taskId`, `buildVariantName`, `taskName`, and `estimatedTasks`.
+ * @returns The total sum of `estimatedTasks` for tasks that exist in both the `taskIdSet` and the `generatedTaskCounts` array, plus the size of the set.
+ */
+export const sumActivatedTasksInSet = (
+ taskIdSet: Set,
+ generatedTaskCounts: GeneratedTaskCountResults[],
+): number =>
+ generatedTaskCounts.reduce((total, { estimatedTasks, taskId }) => {
+ if (taskId && taskIdSet.has(taskId)) {
+ return total + estimatedTasks;
+ }
+ return total;
+ }, taskIdSet.size);
+
+/**
+ * `sumActivatedTasksInVariantsTasks` Computes activated tasks for the "Configure Patch" page.
+ * Sums the number tasks that are both present the provided `VariantTasksState` and NOT present
+ * in the provided `VariantTask[], with the total `estimatedTasks` from the `GeneratedTaskCounts` array for those tasks`.
+ * @param selectedTasks - A VariantTasksState object representing the tasks that are currently selected on the page.
+ * @param generatedTaskCounts - An array of objects containing info on the estimated number of tasks that each generator task
+ * (a task that calls generate.tasks) will schedule, including the `taskId`, `buildVariantName`, `taskName`, and `estimatedTasks`.
+ * @param existingVariantsTasks - An array of VariantTask, representing the initial state of selected tasks in the "Configure Patch" page.
+ * @returns The total sum of tasks that are selected in the `selectedTasks` map and their corresponding `estimatedTasks` that exist in
+ * the `generatedTaskCounts` array.
+ */
+export const sumActivatedTasksInVariantsTasks = (
+ selectedTasks: VariantTasksState,
+ generatedTaskCounts: GeneratedTaskCountResults[],
+ existingVariantsTasks: VariantTask[],
+): number => {
+ // Create a set of existing tasks from existingVariantsTasks
+ const existingTasks = new Set(
+ existingVariantsTasks.flatMap((variantTask) =>
+ variantTask.tasks.map((task) => `${variantTask.name}-${task}`),
+ ),
+ );
+
+ // Sum the estimated tasks for selected tasks that are not in existingTasks
+ return Object.entries(selectedTasks).reduce(
+ (total, [variant, tasks]) =>
+ Object.entries(tasks).reduce((innerTotal, [taskName, isSelected]) => {
+ if (isSelected && !existingTasks.has(`${variant}-${taskName}`)) {
+ const generatedTaskCount =
+ generatedTaskCounts.find(
+ (count) =>
+ count.buildVariantName === variant &&
+ count.taskName === taskName,
+ )?.estimatedTasks || 0;
+ return innerTotal + 1 + generatedTaskCount;
+ }
+ return innerTotal;
+ }, total),
+ 0,
+ );
+};
+
+/**
+ * `sumActivatedTasksInSelectedTasks` Computes tasks for the "Restart Version" modal.
+ * Sums the number tasks that are both present the provided `versionSelectedTasks`, with the total `estimatedTasks` from the `GeneratedTaskCounts`
+ * array for those tasks`.
+ * @param selectedTasks - A versionSelectedTasks object representing the tasks that are currently selected on the page.
+ * @param generatedTaskCounts - An array of objects containing info on the estimated number of tasks that each generator task (a task that calls generate.tasks)
+ * will schedule, including the `taskId`, `buildVariantName`, `taskName`, and `estimatedTasks`.
+ * @returns The total sum of tasks that are selected in the `selectedTasks` map and their corresponding `estimatedTasks` that exist in
+ * the `generatedTaskCounts` array.
+ */
+export const sumActivatedTasksInSelectedTasks = (
+ selectedTasks: versionSelectedTasks,
+ generatedTaskCounts: GeneratedTaskCountResults[],
+) =>
+ // Sum the total estimated tasks from the selected tasks
+ Object.entries(selectedTasks).reduce(
+ (total, [, tasks]) =>
+ Object.entries(tasks).reduce((innerTotal, [taskId, isSelected]) => {
+ if (isSelected) {
+ const generatedTaskCount =
+ generatedTaskCounts.find((count) => count.taskId === taskId)
+ ?.estimatedTasks || 0;
+ return innerTotal + 1 + generatedTaskCount;
+ }
+ return innerTotal;
+ }, total),
+ 0,
+ );