export function getWorkItemIterationDuration()

in src/EpicRoadmap/redux/selectors/workItemStartEndIterationSelector.ts [31:182]


export function getWorkItemIterationDuration(
    epicTree: IEpicTree,
    depTree: IDependenciesTree,
    overriddenIterations: SavedOverriddenIteration,
    teamIterations: TeamSettingsIteration[],
    backlogIteration: TeamSettingsIteration,
    pagedWorkItems: IDictionaryNumberTo<WorkItem>): WorkItemStartEndIteration {

    const result: WorkItemStartEndIteration = {};
    teamIterations = teamIterations || [];

    if (teamIterations.length === 0 || Object.keys(pagedWorkItems).length === 0) {
        return {};
    }

    const process = (workItemId: number) => {
        // If already processed return
        if (result[workItemId]) {
            return;
        }

        // visit bottom up
        const children = epicTree.parentToChildrenMap[workItemId] || [];
        children.forEach(process);

        // 1. choose overriddenIteration if provided
        const overriddenIteration = overriddenIterations[workItemId];
        let overridedBy = undefined;
        let kind = undefined;
        let startIteration = undefined;
        let endIteration = undefined;
        let kindMessage = "";
        if (overriddenIteration) {
            kind = IterationDurationKind.UserOverridden;
            startIteration = teamIterations.find(i => i.id === overriddenIteration.startIterationId);
            endIteration = teamIterations.find(i => i.id === overriddenIteration.endIterationId);
            overridedBy = overriddenIteration.user;
            kindMessage = "User specified start and end iteration.";
        } else {
            // 2. If any predecessor choose start iteration = Max(predecessor end iteration) +1

            // process predecessors to ensure we have them sorted out
            const predecessors = depTree.stop[workItemId] || [];
            let startIndexByPredecessors = -1;

            if (predecessors.length > 0) {
                predecessors.forEach(process);
                kind = IterationDurationKind.Predecessors;
                kindMessage = "Start/End iterations are calculated the predecessors of the work item.";
                predecessors
                    .filter(p => result[p].kind !== IterationDurationKind.BacklogIteration)
                    .forEach(p => {
                        const pIndex = teamIterations.findIndex(i => i.id === result[p].endIteration.id) + 1;
                        startIndexByPredecessors = pIndex > startIndexByPredecessors ? pIndex : startIndexByPredecessors;
                    });
            }

            // 3. choose min start , max end date of children
            let startIndexByChildren = startIndexByPredecessors;
            let endIndexByChildren = startIndexByPredecessors;
            children
                .filter(c => result[c].kind !== IterationDurationKind.BacklogIteration)
                .forEach(c => {
                    const childStart = teamIterations.findIndex(i => i.id === result[c].startIteration.id);
                    const childEnd = teamIterations.findIndex(i => i.id === result[c].endIteration.id);
                    // If any child starts before a predecessor most likely that child is not affected by the predecessor
                    // so we can choose child's start as parents start
                    if (childStart < startIndexByChildren || startIndexByChildren === -1) {
                        startIndexByChildren = childStart;
                    }
                    if (childEnd > endIndexByChildren) {
                        endIndexByChildren = childEnd;
                    }
                });

            let startIndex = startIndexByChildren;
            let endIndex = endIndexByChildren;

            if (startIndexByChildren > startIndexByPredecessors) {
                kind = IterationDurationKind.ChildRollup;
                startIndex = startIndexByChildren;
                kindMessage = "Start/End iterations are calculated based on the iteration of the children.";
            }

            startIteration = teamIterations[startIndex];
            endIteration = teamIterations[endIndex];
        }

        const workItem = pagedWorkItems[workItemId];
        let workItemIteration = null;
        if (workItem) {
            let iterationPath = workItem.fields["System.IterationPath"];
            workItemIteration = teamIterations.find(itr => itr.path === iterationPath);
        }

        // Use fall backs if none of the above yield start end iteration
        if (!startIteration || !endIteration) {
            switch (kind) {
                case IterationDurationKind.ChildRollup: {
                    kindMessage = "Children iterations are not subscribed by the team. ";
                    break;
                }
                case IterationDurationKind.UserOverridden: {
                    kindMessage = "User specified iterations are not subscribed by the team. ";
                    break;
                }
                case IterationDurationKind.Predecessors: {
                    debugger;
                    kindMessage = "Predecessors iterations are not subscribed by the team. ";
                    break;
                }
            }

            // Use work item's own iteration as first fallback
            if (workItemIteration) {
                kindMessage = kindMessage + " Using work items own iteration.";
                kind = IterationDurationKind.Self;
                startIteration = endIteration = workItemIteration;
            } else {
                // Use backlog iteration as final fallback
                kindMessage = kindMessage + " Using backlog iteration.";
                startIteration = backlogIteration;
                endIteration = backlogIteration;
                kind = IterationDurationKind.BacklogIteration;
            }
        }

        let childrenAreOutofBounds = false;
        children
            .forEach(c => {
                childrenAreOutofBounds = childrenAreOutofBounds ||
                    areChildrenOutOfBounds(startIteration, endIteration, result[c], teamIterations);
            });


        result[workItemId] = {
            startIteration,
            endIteration,
            kind,
            kindMessage,
            overridedBy: kind === IterationDurationKind.UserOverridden ? overridedBy : undefined,
            childrenAreOutofBounds
        }

        console.log("workItemStartEndIteration", workItemId, workItem, result[workItemId]);

    };

    process(0);

    return result;
}