src/service/WiqlService.ts (152 lines of code) (raw):

import { TeamSettingsIteration, WorkRestClient } from 'azure-devops-extension-api/Work'; import { TeamContext, WebApiTeam } from 'azure-devops-extension-api/Core'; import { WorkItemTrackingRestClient, WorkItemLink, } from 'azure-devops-extension-api/WorkItemTracking'; import { IVssRestClientOptions, getClient } from 'azure-devops-extension-api'; import { FilterInterface } from 'component/gantt/GanttView'; const clients = { workClient(clientOptions?: IVssRestClientOptions) { return getClient(WorkRestClient, clientOptions); }, workItemsClient(clientOptions?: IVssRestClientOptions) { return getClient(WorkItemTrackingRestClient, clientOptions); } } const queries = { taskHierarchy(areas: string[], workItems?: string[], shift?: number) { return ` SELECT [System.Id], [System.Title], [System.State], [System.IterationPath], [System.WorkItemType], [System.Tags] FROM workitemLinks WHERE ( [Source].[System.AreaPath] IN (${areas}) AND [Source].[System.TeamProject] = @project ${shift ? `AND [Source].[System.IterationPath] = @CurrentIteration ${shift >= 0 ? ` + ${shift}` : ` - ${shift}`} ` : ``} AND [Source].[System.State] <> '' AND [Source].[System.WorkItemType] ${workItems && workItems.length > 0 ? `in (${workItems})` : `<> ''`} ) AND ( [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' ) AND ( [Target].[System.TeamProject] = @project AND [Target].[System.WorkItemType] ${workItems && workItems.length > 0 ? `in (${workItems})` : `<> ''`} ) MODE (Recursive) `; }, currentIterationName() { return ` SELECT [System.Id], [System.Title], [System.State], [System.IterationPath] FROM WorkItems WHERE ( [System.TeamProject] = @project AND [System.IterationPath] = @CurrentIteration ) `; }, get iterationDefinition() { return ` SELECT [Microsoft.VSTS.Scheduling.StartDate], [Microsoft.VSTS.Scheduling.FinishDate] FROM WorkItems WHERE [System.TeamProject] = @project ORDER BY [System.Id] `; } }; export type TeamIteration = { teamId: string, currentIteration?: string, iterations: TeamSettingsIteration[], start: Date, end: Date }; export const fetchIterationDefinition = async (team: WebApiTeam, clientOptions?: IVssRestClientOptions): Promise<TeamIteration> => { const { projectName, projectId, id, name } = team; const iterationName = await clients.workItemsClient(clientOptions).queryByWiql( { query: queries.currentIterationName() }, projectId, id ).then(async value => { const response = await clients.workItemsClient(clientOptions).getWorkItem(value?.workItems?.[0].id, projectName, ["System.IterationPath"]); const path: string = response?.fields?.["System.IterationPath"]; return path?.substring(path.lastIndexOf("\\") + 1); }).handle('', 'No iteration assigned to the team'); const teamContext = { project: projectName, projectId, team: name, teamId: id } as TeamContext; const currentDate = new Date(); const start = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); const end = new Date(currentDate.getFullYear(), currentDate.getMonth(), 14); // Assigned default currentIteration when project miss configuration, to get gannt chart loaded return clients.workClient(clientOptions).getTeamIterations(teamContext, "current") .then((iterations = []) => { return { teamId: id, iterations, currentIteration: iterationName ? iterationName : iterations[0]?.name || "@CurrentIteration", start: iterations[0]?.attributes?.startDate || start, end: iterations[iterations.length - 1]?.attributes?.finishDate || end } as TeamIteration }).handle({ teamId: id, iterations: [ { id: "id_current_iteration", name: "@CurrentIteration", path: "CurrentIteration", attributes: { startDate: start, finishDate: end, timeFrame: 1 }, url: "", _links: {} }], currentIteration: "@CurrentIteration", start, end, } as TeamIteration, 'Error, when getTeamIterations method called'); } export const fetchTeamWorkItems = async (team: WebApiTeam, filter?: FilterInterface, clientOptions?: IVssRestClientOptions): Promise<{ id: string, ids: number[], connections: { [key: string]: WorkItemLink[]; } }> => { const { projectName, projectId, id, name } = team; return clients.workClient(clientOptions).getTeamFieldValues({ project: projectName, projectId, team: name, teamId: id, }).then(({ values }) => { const areas = values.map(({ value }) => `'${value}'`); const _types = filter?.workTypes?.map(({ name }) => `'${name}'`); return clients.workItemsClient(clientOptions) .queryByWiql( { query: queries.taskHierarchy(areas, _types, filter?.shift) }, projectId, id ) .then(({ workItemRelations = [] }) => { const workItems = workItemRelations.filter(({ target }) => typeof target.id === "number"); const rootItems = new Set(workItems.filter(({ source }) => !source).map(({ target: { id } }) => id)); const { connections } = workItems.reduce((acc, next) => { const { target: { id } } = next; if (rootItems.has(id)) { const key = `${id}`; const { connections } = acc; return { ...acc, connections: { ...connections, [key]: [next] }, lastAccessedKey: key }; } const { lastAccessedKey: key, connections } = acc; return { ...acc, connections: { ...connections, [key]: [...connections[key], next] } }; }, { connections: {} } as { lastAccessedKey: string, connections: { [key: string]: WorkItemLink[]; } }); return { id, ids: workItems.map(({ target: { id } }) => id), connections }; }); }).handle({ id: "", ids: [], connections: {} }, 'Error, when getTeamFieldValues method called'); };