public async AddWorkItemsToPlan()

in src/PortfolioPlanning/Common/Services/PortfolioPlanningDataService.ts [512:773]


    public async AddWorkItemsToPlan(
        planId: string,
        workItemIds: number[],
        telemetryService?: PortfolioTelemetry
    ): Promise<PortfolioPlanning> {
        if (!telemetryService) {
            telemetryService = PortfolioTelemetry.getInstance();
        }

        try {
            console.log(`AddWorkItemsToPlan. WorkItemIds: ${workItemIds.join(", ")}. Plan: ${planId}`);

            const telemetryData = {
                ["PlanId"]: planId,
                ["WorkItemCount"]: workItemIds.length
            };
            telemetryService.TrackAction("PortfolioPlanningDataService/AddWorkItemsToPlan", telemetryData);

            if (workItemIds.length === 0) {
                //  No-op.
                const props = {
                    ["PlanId"]: planId
                };

                telemetryService.TrackAction("PortfolioPlanningDataService/AddWorkItemsToPlan/NoOp", props);

                return null;
            }

            const plan = await this.GetPortfolioPlanById(planId);
            if (!plan) {
                //  Couldn't find plan to update. Ignoring.
                const props = {
                    ["PlanId"]: planId,
                    ["WorkItemIds"]: workItemIds
                };

                telemetryService.TrackAction("PortfolioPlanningDataService/AddWorkItemsToPlan/NoPlanFound", props);

                return null;
            }

            const workItemIdsSet: { [workItemId: number]: true } = {};
            workItemIds.forEach(id => (workItemIdsSet[id.toString()] = true));

            const projectMapping = await this.getWorkItemProjectIds(
                Object.keys(workItemIdsSet).map(id => Number(id)),
                telemetryService
            );

            if (projectMapping && projectMapping.exceptionMessage && projectMapping.exceptionMessage.length > 0) {
                throw new Error(
                    `Error querying project information for work item ids. Details: ${projectMapping.exceptionMessage}`
                );
            }

            if (!projectMapping || !projectMapping.Results || projectMapping.Results.length === 0) {
                //  Work item project ids query returned no results.
                const props = {
                    ["WorkItemIds"]: workItemIds
                };

                telemetryService.TrackAction(
                    "PortfolioPlanningDataService/AddWorkItemsToPlan/WorkItemProjectIdsNoResults",
                    props
                );

                return null;
            }

            const allProjectConfigPromises: Promise<IProjectConfiguration>[] = [];
            const allProjectConfigPromisesProjectIds: { [projectIdKey: string]: boolean } = {};
            const newProjectConfigsByProjectId: { [projectIdKey: string]: IProjectConfiguration } = {};
            const byWorkItemId: { [workItemId: number]: WorkItemProjectId } = {};

            //  Do we need to get configuration for projects we are seeing for the first time?
            projectMapping.Results.forEach(map => {
                const projectIdKey = map.ProjectSK.toLowerCase();
                const workItemId = map.WorkItemId;

                byWorkItemId[workItemId.toString()] = map;

                if (!plan.projects[projectIdKey] && !allProjectConfigPromisesProjectIds[projectIdKey]) {
                    //  New project, need to get project configuration.
                    allProjectConfigPromises.push(
                        ProjectConfigurationDataService.getInstance().getProjectConfiguration(projectIdKey)
                    );

                    allProjectConfigPromisesProjectIds[projectIdKey] = true;
                }
            });

            //  Are we missing work item type data for new work item ids?
            const projectIdsNoTypes: { [projectIdKey: string]: boolean } = {};
            Object.keys(byWorkItemId).forEach(newWorkItemId => {
                const newWorkItem: WorkItemProjectId = byWorkItemId[newWorkItemId];
                const projectIdKey = newWorkItem.ProjectSK.toLowerCase();
                const workItemTypeKey = newWorkItem.WorkItemType.toLowerCase();

                if (
                    plan.projects[projectIdKey] &&
                    (!plan.projects[projectIdKey].WorkItemTypeData ||
                        !plan.projects[projectIdKey].WorkItemTypeData[workItemTypeKey]) &&
                    !allProjectConfigPromisesProjectIds[projectIdKey]
                ) {
                    projectIdsNoTypes[projectIdKey] = true;
                }
            });

            Object.keys(projectIdsNoTypes).forEach(projectIdKey => {
                allProjectConfigPromises.push(
                    ProjectConfigurationDataService.getInstance().getProjectConfiguration(projectIdKey)
                );
            });

            //  New projects?
            if (allProjectConfigPromises.length > 0) {
                const newProjectConfigs = await Promise.all(allProjectConfigPromises);
                newProjectConfigs.forEach(config => {
                    const projectIdKey = config.id.toLowerCase();
                    newProjectConfigsByProjectId[projectIdKey] = config;
                });
            }

            //  Merge new data.
            Object.keys(workItemIdsSet).forEach(newWorkItemId => {
                if (byWorkItemId[newWorkItemId]) {
                    const workItemInfo: WorkItemProjectId = byWorkItemId[newWorkItemId];
                    const projectIdKey = workItemInfo.ProjectSK.toLowerCase();
                    const workItemTypeKey = workItemInfo.WorkItemType.toLowerCase();

                    if (plan.projects[projectIdKey]) {
                        if (!plan.projects[projectIdKey].Items) {
                            plan.projects[projectIdKey].Items = {};
                        }

                        if (!plan.projects[projectIdKey].Items[newWorkItemId]) {
                            if (!plan.projects[projectIdKey].WorkItemTypeData) {
                                plan.projects[projectIdKey].WorkItemTypeData = {};
                            }

                            if (!plan.projects[projectIdKey].WorkItemTypeData[workItemTypeKey]) {
                                //  Did we just query for this project's config?
                                if (newProjectConfigsByProjectId[projectIdKey]) {
                                    const projectConfig = newProjectConfigsByProjectId[projectIdKey];

                                    //  Does the project config support this work item type?
                                    if (projectConfig.iconInfoByWorkItemType[workItemTypeKey]) {
                                        //  Ok, we can add the work item id to the project. It's type is
                                        //  supported based on the project configuration.
                                        plan.projects[projectIdKey].Items[newWorkItemId] = {
                                            workItemId: newWorkItemId,
                                            workItemType: workItemInfo.WorkItemType
                                        };

                                        plan.projects[projectIdKey].WorkItemTypeData[workItemTypeKey] = {
                                            workItemType: workItemInfo.WorkItemType,
                                            backlogLevelName:
                                                projectConfig.backlogLevelNamesByWorkItemType[workItemTypeKey],
                                            iconProps: projectConfig.iconInfoByWorkItemType[workItemTypeKey]
                                        };
                                    } else {
                                        //  Work item type is not supported.
                                        const props = {
                                            ["WorkItemId"]: newWorkItemId,
                                            ["WorkItemType"]: workItemTypeKey
                                        };

                                        telemetryService.TrackAction(
                                            "PortfolioPlanningDataService/AddWorkItemsToPlan/WorkItemTypeNotSupported",
                                            props
                                        );
                                    }
                                } else {
                                    const errorMessage = `Cannot add work item id '${newWorkItemId}' to plan id '${
                                        plan.id
                                    }'. Couldn't retrieve configuration for project '${projectIdKey}'`;
                                    const error = new Error(errorMessage);
                                    PortfolioTelemetry.getInstance().TrackException(error);
                                    throw error;
                                }
                            } else {
                                //  Work item type was already supported in the project, just add the work item id.
                                plan.projects[projectIdKey].Items[newWorkItemId] = {
                                    workItemId: newWorkItemId,
                                    workItemType: workItemInfo.WorkItemType
                                };
                            }
                        }
                    } else if (newProjectConfigsByProjectId[projectIdKey]) {
                        const newProjectInfo: IProjectConfiguration = newProjectConfigsByProjectId[projectIdKey];

                        //  Does the project config support this work item type?
                        if (newProjectInfo.iconInfoByWorkItemType[workItemTypeKey]) {
                            const Items: { [workItemId: number]: PortfolioItem } = {};
                            const WorkItemTypeData: { [workItemTypeKey: string]: WorkItemType } = {};

                            Items[newWorkItemId] = {
                                workItemId: newWorkItemId,
                                workItemType: workItemInfo.WorkItemType
                            };

                            WorkItemTypeData[workItemTypeKey] = {
                                workItemType: workItemInfo.WorkItemType,
                                backlogLevelName: newProjectInfo.backlogLevelNamesByWorkItemType[workItemTypeKey],
                                iconProps: newProjectInfo.iconInfoByWorkItemType[workItemTypeKey]
                            };

                            plan.projects[projectIdKey] = {
                                ProjectId: projectIdKey,
                                RequirementWorkItemType: newProjectInfo.defaultRequirementWorkItemType,
                                EffortODataColumnName: newProjectInfo.effortODataColumnName,
                                EffortWorkItemFieldRefName: newProjectInfo.effortWorkItemFieldRefName,
                                Items,
                                WorkItemTypeData
                            };
                        } else {
                            //  Work item type is not supported.
                            const props = {
                                ["WorkItemId"]: newWorkItemId,
                                ["WorkItemType"]: workItemTypeKey
                            };

                            telemetryService.TrackAction(
                                "PortfolioPlanningDataService/AddWorkItemsToPlan/WorkItemTypeNotSupported",
                                props
                            );
                        }
                    } else {
                        //  Couldn't find project configuration for work item's project id. Ignoring.
                        const props = {
                            ["WorkItemId"]: newWorkItemId,
                            ["ProjectId"]: projectIdKey
                        };

                        telemetryService.TrackAction(
                            "PortfolioPlanningDataService/AddWorkItemsToPlan/MissingProjectConfiguration",
                            props
                        );
                    }
                } else {
                    //  Couldn't find project mapping for work item id. Ignoring.
                    const props = {
                        ["WorkItemId"]: newWorkItemId
                    };

                    telemetryService.TrackAction(
                        "PortfolioPlanningDataService/AddWorkItemsToPlan/MissingProjectIdForNewWorkItemId",
                        props
                    );
                }
            });

            //  Persist plan changes
            return await this.UpdatePortfolioPlan(plan);
        } catch (error) {
            telemetryService.TrackException(error);
            console.log(error);
            const exceptionMessage: string = (error as Error).message;
            return Promise.reject(exceptionMessage);
        }
    }