Skip to content

Commit 3a4c912

Browse files
authored
Move slipped items to the backlog iteration (#377)
Contributes to #368 When an issue / work item pair is only assigned to iterations in the past, assume it has been slipped. Move that item to the `Future` iteration. That requires using a new API to retrieve iterations. Instead of retrieve active iterations, retrieve all classification nodes, and parse that packet to build all iterations. That API includes "parent" or "container" iteration, so filter those out. The classification nodes API has a different path structure for the iteration nodes, including the "Iteration" classifier. Therefore, remove that element from the path for use assigning the work item to the iteration.
1 parent b2e8333 commit 3a4c912

File tree

7 files changed

+134
-66
lines changed

7 files changed

+134
-66
lines changed

DotNet.DocsTools/GitHubObjects/StoryPointSize.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@ namespace DotNet.DocsTools.GitHubObjects;
88
/// </summary>
99
public record StoryPointSize
1010
{
11+
private static readonly Dictionary<string, int> s_months = new()
12+
{
13+
["Jan"] = 1, // 3
14+
["Feb"] = 2, // 3
15+
["Mar"] = 3, // 3
16+
["Apr"] = 4, // 4
17+
["May"] = 5, // 4
18+
["Jun"] = 6, // 4
19+
["Jul"] = 7, // 1
20+
["Aug"] = 8, // 1
21+
["Sep"] = 9, // 1
22+
["Oct"] = 10, // 2
23+
["Nov"] = 11, // 2
24+
["Dec"] = 12 // 2
25+
};
26+
27+
public static int MonthOrdinal(string month) => s_months[month];
28+
29+
public static bool TryGetMonthOrdinal(string month, out int ordinal)
30+
=> s_months.TryGetValue(month, out ordinal);
31+
1132
public static StoryPointSize? OptionalFromJsonElement(JsonElement projectItem)
1233
{
1334
StoryPointSize? sz = default;
@@ -59,5 +80,23 @@ private StoryPointSize(int CalendarYear, string Month, string Size)
5980
public int CalendarYear { get; }
6081
public string Month { get; }
6182
public string Size { get; }
83+
84+
public bool IsPastIteration
85+
{
86+
get
87+
{
88+
var currentYear = int.Parse(DateTime.Now.ToString("yyyy"));
89+
var currentMonth = MonthOrdinal(DateTime.Now.ToString("MMM"));
90+
91+
if (CalendarYear < currentYear)
92+
{
93+
return true;
94+
} else if (CalendarYear == currentYear)
95+
{
96+
return MonthOrdinal(Month) < currentMonth;
97+
}
98+
return false;
99+
}
100+
}
62101
}
63102

actions/sequester/Quest2GitHub.Tests/GetIterationTests.cs

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,42 @@ public class GetIterationTests
55
{
66
private static QuestIteration[] _allIterations =
77
[
8-
new QuestIteration { Id = Guid.Parse("d7470997-4db4-4146-95bd-1b02b8be20d2"), Name = "04 Apr", Path = """Content\CY_2022\04 Apr""" },
9-
new QuestIteration { Id = Guid.Parse("ade387a7-c9d1-4ef1-8130-dd4d3553efa9"), Name = " 05 May", Path = """Content\CY_2022\05 May""" },
10-
new QuestIteration { Id = Guid.Parse("8320be23-4cc6-45a1-b238-c5e076622870"), Name = " 06 Jun", Path = """Content\CY_2022\06 Jun""" },
11-
new QuestIteration { Id = Guid.Parse("57e35043-1d58-45cd-938f-6d4a124454d2"), Name = " 07 Jul", Path = """Content\CY_2022\07 Jul""" },
12-
new QuestIteration { Id = Guid.Parse("6c6263e9-8996-462b-8f81-a0f05630a282"), Name = " 08 Aug", Path = """Content\CY_2022\08 Aug""" },
13-
new QuestIteration { Id = Guid.Parse("e809d008-f2db-4c0e-9fb0-22eee3ae1831"), Name = " 09 Sep", Path = """Content\CY_2022\09 Sep""" },
14-
new QuestIteration { Id = Guid.Parse("ca46ea6b-3da8-4786-95b0-8367aaf1d942"), Name = " 10 Oct", Path = """Content\CY_2022\10 Oct""" },
15-
new QuestIteration { Id = Guid.Parse("d9da519b-ab94-4bb7-a280-c0c4e65fe60b"), Name = " 11 Nov", Path = """Content\CY_2022\11 Nov""" },
16-
new QuestIteration { Id = Guid.Parse("c622ef37-44ad-43e2-a128-9ab4ca6b6ee9"), Name = " 12 Dec", Path = """Content\CY_2022\12 Dec""" },
17-
new QuestIteration { Id = Guid.Parse("f6fdba55-fbe6-4250-8285-ebabc94145d3"), Name = " 01 Jan", Path = """Content\CY_2023\01 Jan""" },
18-
new QuestIteration { Id = Guid.Parse("d6bca9e6-1658-4fb1-8f41-9d7b5fcb27a6"), Name = " 02 Feb", Path = """Content\CY_2023\02 Feb""" },
19-
new QuestIteration { Id = Guid.Parse("3b2e41e9-6e1d-4492-b531-c584143e9ceb"), Name = " 03 Mar", Path = """Content\CY_2023\03 Mar""" },
20-
new QuestIteration { Id = Guid.Parse("03976f7e-3f34-4791-95f3-9081c7377716"), Name = " 04 Apr", Path = """Content\Gallium\FY23Q4\04 Apr""" },
21-
new QuestIteration { Id = Guid.Parse("0c948fc6-aa82-4c75-bede-e69cab231264"), Name = " 05 May", Path = """Content\Gallium\FY23Q4\05 May""" },
22-
new QuestIteration { Id = Guid.Parse("870ebccc-535e-48ff-beee-d90cfe1ecb34"), Name = " 06 Jun", Path = """Content\Gallium\FY23Q4\06 Jun""" },
23-
new QuestIteration { Id = Guid.Parse("4ff5a0b7-ef2c-49e6-9e9a-5e6e759e327e"), Name = " 07 July", Path = """Content\Gallium\FY24Q1\07 July""" },
24-
new QuestIteration { Id = Guid.Parse("8d6aa2e1-1236-485f-abb0-a8aaf3732751"), Name = " 08 Aug", Path = """Content\Gallium\FY24Q1\08 Aug""" },
25-
new QuestIteration { Id = Guid.Parse("a1068cfe-bb6d-47ec-aa67-4fb130d78193"), Name = " 09 Sep", Path = """Content\Gallium\FY24Q1\09 Sep""" },
26-
new QuestIteration { Id = Guid.Parse("ec694f64-abbd-45a5-b540-236fb3771338"), Name = " 10 Oct", Path = """Content\Germanium\FY24Q2\10 Oct""" },
27-
new QuestIteration { Id = Guid.Parse("8800d3bf-9478-4efa-b735-0c5fb685d515"), Name = " 11 Nov", Path = """Content\Germanium\FY24Q2\11 Nov""" },
28-
new QuestIteration { Id = Guid.Parse("b278fb17-8a93-4838-9224-cc1326501e89"), Name = " 12 Dec", Path = """Content\Germanium\FY24Q2\12 Dec""" },
29-
new QuestIteration { Id = Guid.Parse("bb02a733-b297-44e5-b326-b893700f0ea8"), Name = " 01 Jan", Path = """Content\Germanium\FY24Q3\01 Jan""" },
30-
new QuestIteration { Id = Guid.Parse("4ba3be79-97f3-4325-8845-b290ec447ba8"), Name = " 02 Feb", Path = """Content\Germanium\FY24Q3\02 Feb""" },
31-
new QuestIteration { Id = Guid.Parse("8add98b0-b760-4448-a315-6848563663fc"), Name = " 03 Mar", Path = """Content\Germanium\FY24Q3\03 Mar""" },
32-
new QuestIteration { Id = Guid.Parse("0ee548e5-7c70-44c0-afa9-d1a0be0441f2"), Name = " 04 Apr", Path = """Content\Dilithium\FY24Q4\04 Apr""" },
33-
new QuestIteration { Id = Guid.Parse("9c2ae4cd-47c3-41ca-a2e4-8a9301a9e291"), Name = " 05 May", Path = """Content\Dilithium\FY24Q4\05 May""" },
34-
new QuestIteration { Id = Guid.Parse("304e8a66-a53c-4997-b6fe-97071690434b"), Name = " 06 Jun", Path = """Content\Dilithium\FY24Q4\06 Jun""" },
35-
new QuestIteration { Id = Guid.Parse("410768bf-e1c3-46c1-bca2-73d917f010a2"), Name = " 07 Jul", Path = """Content\Dilithium\FY25Q1\07 Jul""" },
36-
new QuestIteration { Id = Guid.Parse("0b530b25-0970-4fd5-aeb2-891dff587264"), Name = " 08 Aug", Path = """Content\Dilithium\FY25Q1\08 Aug""" },
37-
new QuestIteration { Id = Guid.Parse("85d7fc73-4977-49b9-a84f-ea2211032b9e"), Name = " 09 Sep", Path = """Content\Dilithium\FY25Q1\09 Sep""" },
38-
new QuestIteration { Id = Guid.Parse("2cc96e64-21e5-4c76-a073-80df5945c199"), Name = " 10 Oct", Path = """Content\Selenium\FY25Q2\10 Oct""" },
39-
new QuestIteration { Id = Guid.Parse("b7591952-acee-400a-a51f-fc1215ca031d"), Name = " 11 Nov", Path = """Content\Selenium\FY25Q2\11 Nov""" },
40-
new QuestIteration { Id = Guid.Parse("b1e7d3b8-ab9a-4f98-a506-9a67ea3fd728"), Name = " 12 Dec", Path = """Content\Selenium\FY25Q2\12 Dec""" },
41-
new QuestIteration { Id = Guid.Parse("66727215-2522-4d69-b6e0-cb22bf22f4c2"), Name = " 01 Jan", Path = """Content\Selenium\FY25Q3\01 Jan""" },
42-
new QuestIteration { Id = Guid.Parse("0594f104-9d78-4b92-93db-f47610a9c515"), Name = " 02 Feb", Path = """Content\Selenium\FY25Q3\02 Feb""" },
43-
new QuestIteration { Id = Guid.Parse("08468117-ec32-4a80-af0b-763e84acf032"), Name = " 03 Mar", Path = """Content\Selenium\FY25Q3\03 Mar""" }
8+
new QuestIteration { Id = 1, Identifier = Guid.Parse("d7470997-4db4-4146-95bd-1b02b8be20d2"), Name = "04 Apr", Path = """Content\CY_2022\04 Apr""" },
9+
new QuestIteration { Id = 2, Identifier = Guid.Parse("ade387a7-c9d1-4ef1-8130-dd4d3553efa9"), Name = " 05 May", Path = """Content\CY_2022\05 May""" },
10+
new QuestIteration { Id = 3, Identifier = Guid.Parse("8320be23-4cc6-45a1-b238-c5e076622870"), Name = " 06 Jun", Path = """Content\CY_2022\06 Jun""" },
11+
new QuestIteration { Id = 4, Identifier = Guid.Parse("57e35043-1d58-45cd-938f-6d4a124454d2"), Name = " 07 Jul", Path = """Content\CY_2022\07 Jul""" },
12+
new QuestIteration { Id = 5, Identifier = Guid.Parse("6c6263e9-8996-462b-8f81-a0f05630a282"), Name = " 08 Aug", Path = """Content\CY_2022\08 Aug""" },
13+
new QuestIteration { Id = 6, Identifier = Guid.Parse("e809d008-f2db-4c0e-9fb0-22eee3ae1831"), Name = " 09 Sep", Path = """Content\CY_2022\09 Sep""" },
14+
new QuestIteration { Id = 7, Identifier = Guid.Parse("ca46ea6b-3da8-4786-95b0-8367aaf1d942"), Name = " 10 Oct", Path = """Content\CY_2022\10 Oct""" },
15+
new QuestIteration { Id = 8, Identifier = Guid.Parse("d9da519b-ab94-4bb7-a280-c0c4e65fe60b"), Name = " 11 Nov", Path = """Content\CY_2022\11 Nov""" },
16+
new QuestIteration { Id = 9, Identifier = Guid.Parse("c622ef37-44ad-43e2-a128-9ab4ca6b6ee9"), Name = " 12 Dec", Path = """Content\CY_2022\12 Dec""" },
17+
new QuestIteration { Id = 10, Identifier = Guid.Parse("f6fdba55-fbe6-4250-8285-ebabc94145d3"), Name = " 01 Jan", Path = """Content\CY_2023\01 Jan""" },
18+
new QuestIteration { Id = 11, Identifier = Guid.Parse("d6bca9e6-1658-4fb1-8f41-9d7b5fcb27a6"), Name = " 02 Feb", Path = """Content\CY_2023\02 Feb""" },
19+
new QuestIteration { Id = 12, Identifier = Guid.Parse("3b2e41e9-6e1d-4492-b531-c584143e9ceb"), Name = " 03 Mar", Path = """Content\CY_2023\03 Mar""" },
20+
new QuestIteration { Id = 13, Identifier = Guid.Parse("03976f7e-3f34-4791-95f3-9081c7377716"), Name = " 04 Apr", Path = """Content\Gallium\FY23Q4\04 Apr""" },
21+
new QuestIteration { Id = 14, Identifier = Guid.Parse("0c948fc6-aa82-4c75-bede-e69cab231264"), Name = " 05 May", Path = """Content\Gallium\FY23Q4\05 May""" },
22+
new QuestIteration { Id = 15, Identifier = Guid.Parse("870ebccc-535e-48ff-beee-d90cfe1ecb34"), Name = " 06 Jun", Path = """Content\Gallium\FY23Q4\06 Jun""" },
23+
new QuestIteration { Id = 16, Identifier = Guid.Parse("4ff5a0b7-ef2c-49e6-9e9a-5e6e759e327e"), Name = " 07 July", Path = """Content\Gallium\FY24Q1\07 July""" },
24+
new QuestIteration { Id = 17, Identifier = Guid.Parse("8d6aa2e1-1236-485f-abb0-a8aaf3732751"), Name = " 08 Aug", Path = """Content\Gallium\FY24Q1\08 Aug""" },
25+
new QuestIteration { Id = 18, Identifier = Guid.Parse("a1068cfe-bb6d-47ec-aa67-4fb130d78193"), Name = " 09 Sep", Path = """Content\Gallium\FY24Q1\09 Sep""" },
26+
new QuestIteration { Id = 19, Identifier = Guid.Parse("ec694f64-abbd-45a5-b540-236fb3771338"), Name = " 10 Oct", Path = """Content\Germanium\FY24Q2\10 Oct""" },
27+
new QuestIteration { Id = 20, Identifier = Guid.Parse("8800d3bf-9478-4efa-b735-0c5fb685d515"), Name = " 11 Nov", Path = """Content\Germanium\FY24Q2\11 Nov""" },
28+
new QuestIteration { Id = 21, Identifier = Guid.Parse("b278fb17-8a93-4838-9224-cc1326501e89"), Name = " 12 Dec", Path = """Content\Germanium\FY24Q2\12 Dec""" },
29+
new QuestIteration { Id = 22, Identifier = Guid.Parse("bb02a733-b297-44e5-b326-b893700f0ea8"), Name = " 01 Jan", Path = """Content\Germanium\FY24Q3\01 Jan""" },
30+
new QuestIteration { Id = 23, Identifier = Guid.Parse("4ba3be79-97f3-4325-8845-b290ec447ba8"), Name = " 02 Feb", Path = """Content\Germanium\FY24Q3\02 Feb""" },
31+
new QuestIteration { Id = 24, Identifier = Guid.Parse("8add98b0-b760-4448-a315-6848563663fc"), Name = " 03 Mar", Path = """Content\Germanium\FY24Q3\03 Mar""" },
32+
new QuestIteration { Id = 25, Identifier = Guid.Parse("0ee548e5-7c70-44c0-afa9-d1a0be0441f2"), Name = " 04 Apr", Path = """Content\Dilithium\FY24Q4\04 Apr""" },
33+
new QuestIteration { Id = 26, Identifier = Guid.Parse("9c2ae4cd-47c3-41ca-a2e4-8a9301a9e291"), Name = " 05 May", Path = """Content\Dilithium\FY24Q4\05 May""" },
34+
new QuestIteration { Id = 27, Identifier = Guid.Parse("304e8a66-a53c-4997-b6fe-97071690434b"), Name = " 06 Jun", Path = """Content\Dilithium\FY24Q4\06 Jun""" },
35+
new QuestIteration { Id = 28, Identifier = Guid.Parse("410768bf-e1c3-46c1-bca2-73d917f010a2"), Name = " 07 Jul", Path = """Content\Dilithium\FY25Q1\07 Jul""" },
36+
new QuestIteration { Id = 29, Identifier = Guid.Parse("0b530b25-0970-4fd5-aeb2-891dff587264"), Name = " 08 Aug", Path = """Content\Dilithium\FY25Q1\08 Aug""" },
37+
new QuestIteration { Id = 30, Identifier = Guid.Parse("85d7fc73-4977-49b9-a84f-ea2211032b9e"), Name = " 09 Sep", Path = """Content\Dilithium\FY25Q1\09 Sep""" },
38+
new QuestIteration { Id = 31, Identifier = Guid.Parse("2cc96e64-21e5-4c76-a073-80df5945c199"), Name = " 10 Oct", Path = """Content\Selenium\FY25Q2\10 Oct""" },
39+
new QuestIteration { Id = 32, Identifier = Guid.Parse("b7591952-acee-400a-a51f-fc1215ca031d"), Name = " 11 Nov", Path = """Content\Selenium\FY25Q2\11 Nov""" },
40+
new QuestIteration { Id = 33, Identifier = Guid.Parse("b1e7d3b8-ab9a-4f98-a506-9a67ea3fd728"), Name = " 12 Dec", Path = """Content\Selenium\FY25Q2\12 Dec""" },
41+
new QuestIteration { Id = 34, Identifier = Guid.Parse("66727215-2522-4d69-b6e0-cb22bf22f4c2"), Name = " 01 Jan", Path = """Content\Selenium\FY25Q3\01 Jan""" },
42+
new QuestIteration { Id = 35, Identifier = Guid.Parse("0594f104-9d78-4b92-93db-f47610a9c515"), Name = " 02 Feb", Path = """Content\Selenium\FY25Q3\02 Feb""" },
43+
new QuestIteration { Id = 36, Identifier = Guid.Parse("08468117-ec32-4a80-af0b-763e84acf032"), Name = " 03 Mar", Path = """Content\Selenium\FY25Q3\03 Mar""" }
4444
];
4545

4646

actions/sequester/Quest2GitHub/AzDoClientServices/QuestClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public QuestClient(string token, string org, string project)
6464
public async Task<JsonElement> RetrieveAllIterations()
6565
{
6666
string getIterationsUrl =
67-
$"https://dev.azure.com/{QuestOrg}/{QuestProject}/_apis/work/teamsettings/iterations?api-version=7.1-preview.1";
67+
$"https://dev.azure.com/{QuestOrg}/{QuestProject}/_apis/wit/classificationnodes?$depth=3&api-version=7.1";
6868

6969
using HttpResponseMessage response = await InitiateRequestAsync(
7070
client => client.GetAsync(getIterationsUrl));

actions/sequester/Quest2GitHub/Models/IssueExtensions.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,10 @@ namespace Quest2GitHub.Models;
44

55
public static class IssueExtensions
66
{
7-
private static readonly Dictionary<string, int> s_months = new ()
8-
{
9-
["Jan"] = 1, // 3
10-
["Feb"] = 2, // 3
11-
["Mar"] = 3, // 3
12-
["Apr"] = 4, // 4
13-
["May"] = 5, // 4
14-
["Jun"] = 6, // 4
15-
["Jul"] = 7, // 1
16-
["Aug"] = 8, // 1
17-
["Sep"] = 9, // 1
18-
["Oct"] = 10, // 2
19-
["Nov"] = 11, // 2
20-
["Dec"] = 12 // 2
21-
};
227
public static StoryPointSize? LatestStoryPointSize(this QuestIssueOrPullRequest issue)
238
{
249
IEnumerable<StoryPointSize> sizes = from size in issue.ProjectStoryPoints
25-
let month = s_months[size.Month]
10+
let month = StoryPointSize.MonthOrdinal(size.Month)
2611
orderby size.CalendarYear descending,
2712
month descending
2813
select size;
@@ -74,7 +59,7 @@ month descending
7459
/// <returns>The current iteration</returns>
7560
public static QuestIteration? ProjectIteration(string month, int calendarYear, IEnumerable<QuestIteration> iterations)
7661
{
77-
if (!s_months.TryGetValue(month, out int monthNumber))
62+
if (!StoryPointSize.TryGetMonthOrdinal(month, out int monthNumber))
7863
{
7964
return default;
8065
}

actions/sequester/Quest2GitHub/Models/QuestIteration.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
public class QuestIteration
44
{
5-
public required Guid Id { get; init; }
5+
public required int Id { get; init; }
6+
public required Guid Identifier { get; init; }
67
public required string Name { get; init; }
78
public required string Path { get; init; }
89

@@ -13,6 +14,9 @@ public class QuestIteration
1314
return IssueExtensions.ProjectIteration(currentMonth, currentYear, iterations);
1415
}
1516

16-
override public string ToString() => $"{Id} {Name} ({Path})";
17+
public static QuestIteration? FutureIteration(IEnumerable<QuestIteration> iterations)
18+
=> iterations.SingleOrDefault(sprint => sprint.Name is "Future");
19+
20+
override public string ToString() => $"{Identifier} {Id} {Name} ({Path})";
1721

1822
}

actions/sequester/Quest2GitHub/Models/QuestWorkItem.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,20 @@ public static async Task<QuestWorkItem> CreateWorkItemAsync(QuestIssueOrPullRequ
200200
};
201201
patchDocument.Add(assignPatch);
202202
StoryPointSize? iterationSize = issue.LatestStoryPointSize();
203+
QuestIteration? iteration = iterationSize?.ProjectIteration(allIterations);
203204
if (iterationSize != null)
204205
{
205206
Console.WriteLine($"Latest GitHub sprint project: {iterationSize?.Month}-{iterationSize?.CalendarYear}, size: {iterationSize?.Size}");
207+
if (iterationSize?.IsPastIteration == true)
208+
{
209+
Console.WriteLine($"Moving to the backlog / future iteration.");
210+
iteration = QuestIteration.FutureIteration(allIterations);
211+
}
206212
}
207213
else
208214
{
209215
Console.WriteLine("No GitHub sprint project found - using current iteration");
210216
}
211-
QuestIteration? iteration = iterationSize?.ProjectIteration(allIterations);
212217
patchDocument.Add(new JsonPatchDocument
213218
{
214219
Operation = Op.Add,

0 commit comments

Comments
 (0)