Skip to content

Commit 03d3c01

Browse files
committed
Moved workitem details to a separate view
1 parent 2144dc9 commit 03d3c01

4 files changed

Lines changed: 218 additions & 51 deletions

File tree

CloneDevOpsTemplate/Controllers/WorkItemsController.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public async Task<IActionResult> WorkItems(Guid projectId, string projectName)
1717

1818
if (!ModelState.IsValid)
1919
{
20-
return View(workItems.ToArray());
20+
return View(new Tuple<Guid, WorkItem[]>(projectId, [.. workItems]));
2121
}
2222

2323
WorkItemsListQueryResult workItemsList = await _workItemService.GetWorkItemsListAsync(projectId, projectName) ?? new();
@@ -33,7 +33,20 @@ public async Task<IActionResult> WorkItems(Guid projectId, string projectName)
3333
workItems.AddRange(currentWorkItems.Value);
3434
}
3535
}
36-
return View(workItems.ToArray());
36+
return View(new Tuple<Guid, WorkItem[]>(projectId, [.. workItems]));
37+
}
38+
39+
public async Task<IActionResult> WorkItem(Guid projectId, int workitemId)
40+
{
41+
WorkItem workItem = new();
42+
43+
if (!ModelState.IsValid)
44+
{
45+
return View(workItem);
46+
}
47+
48+
workItem = await _workItemService.GetWorkItemAsync(projectId, workitemId) ?? new();
49+
return View(workItem);
3750
}
3851
}
3952

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
@model WorkItem
2+
@{
3+
ViewData["Title"] = "WorkItem";
4+
var fields = Model.Fields;
5+
}
6+
7+
@Html.ValidationSummary(false, "", new { @class = "text-danger" })
8+
9+
<table class="table table-striped">
10+
<thead>
11+
<tr class="table-primary">
12+
<th colspan="4">
13+
<h2>@fields.SystemTitle</h2>
14+
</th>
15+
</tr>
16+
</thead>
17+
<tbody>
18+
<tr>
19+
<td>Description</td>
20+
<td colspan="3">@fields.SystemDescription</td>
21+
</tr>
22+
<tr>
23+
@{ var assignedTo = fields.SystemAssignedTo; }
24+
<td>Assigned to</td>
25+
<td>@assignedTo.DisplayName</td>
26+
<td>@assignedTo.UniqueName</td>
27+
<td>
28+
<img src="@assignedTo.ImageUrl" alt="@assignedTo.DisplayName" />
29+
</td>
30+
</tr>
31+
<tr>
32+
<td>Effort</td>
33+
<td colspan="3">@fields.MicrosoftVSTSSchedulingEffort</td>
34+
</tr>
35+
<tr>
36+
<td>Priority</td>
37+
<td colspan="3">@fields.MicrosoftVSTSCommonPriority</td>
38+
</tr>
39+
<tr>
40+
<td>Workitem type</td>
41+
<td colspan="3">@fields.SystemWorkItemType</td>
42+
</tr>
43+
<tr>
44+
<td>Iteration path</td>
45+
<td colspan="3">@fields.SystemIterationPath</td>
46+
</tr>
47+
<tr>
48+
<td>State</td>
49+
<td colspan="3">@fields.SystemState</td>
50+
</tr>
51+
<tr>
52+
<td>Reason</td>
53+
<td colspan="3">@fields.SystemReason</td>
54+
</tr>
55+
<tr>
56+
@{ var createdBy = fields.SystemCreatedBy; }
57+
<td>Created by</td>
58+
<td>@createdBy.DisplayName</td>
59+
<td>@createdBy.UniqueName</td>
60+
<td>
61+
<img src="@createdBy.ImageUrl" alt="@createdBy.DisplayName" />
62+
</td>
63+
</tr>
64+
<tr>
65+
<td>Created date</td>
66+
<td colspan="3">@fields.SystemCreatedDate</td>
67+
</tr>
68+
<tr>
69+
@{ var changedBy = fields.SystemChangedBy; }
70+
<td>Changed by</td>
71+
<td>@changedBy.DisplayName</td>
72+
<td>@changedBy.UniqueName</td>
73+
<td>
74+
<img src="@changedBy.ImageUrl" alt="@changedBy.DisplayName" />
75+
</td>
76+
</tr>
77+
<tr>
78+
<td>Changed date</td>
79+
<td colspan="3">@fields.SystemChangedDate</td>
80+
</tr>
81+
<tr>
82+
<td>Area path</td>
83+
<td colspan="3">@fields.SystemAreaPath</td>
84+
</tr>
85+
<tr>
86+
<td>Team project</td>
87+
<td colspan="3">@fields.SystemTeamProject</td>
88+
</tr>
89+
<tr>
90+
<td>Board lane</td>
91+
<td colspan="3">@fields.SystemBoardLane</td>
92+
</tr>
93+
<tr>
94+
<td>Tags</td>
95+
<td colspan="3">@fields.SystemTags</td>
96+
</tr>
97+
</tbody>
98+
</table>
Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model WorkItem[]
1+
@model Tuple<Guid, WorkItem[]>
22
@{
33
ViewData["Title"] = "WorkItems";
44
}
@@ -7,42 +7,29 @@
77

88
@Html.ValidationSummary(false, "", new { @class = "text-danger" })
99

10-
@foreach (var fields in Model.Select(workitem => workitem.Fields))
11-
{
12-
<table class="table table-striped">
13-
<thead>
14-
<tr class="table-primary">
15-
<th colspan="4">
16-
<h2>@fields.SystemTitle</h2>
17-
</th>
18-
</tr>
19-
</thead>
20-
<tbody>
21-
<tr>
22-
@{ var assignedTo = fields.SystemAssignedTo; }
23-
<td>Assigned to</td>
24-
<td>@assignedTo.DisplayName</td>
25-
<td>@assignedTo.UniqueName</td>
26-
<td>
27-
<img src="@assignedTo.ImageUrl" alt="@assignedTo.DisplayName" />
28-
</td>
29-
</tr>
30-
<tr>
31-
<td>Effort</td>
32-
<td colspan="3">@fields.MicrosoftVSTSSchedulingEffort</td>
33-
</tr>
34-
<tr>
35-
<td>Priority</td>
36-
<td colspan="3">@fields.MicrosoftVSTSCommonPriority</td>
37-
</tr>
38-
<tr>
39-
<td>Workitem type</td>
40-
<td colspan="3">@fields.SystemWorkItemType</td>
41-
</tr>
10+
<table class="table table-striped">
11+
<thead>
12+
<tr class="table-primary">
13+
<th>ID</th>
14+
<th>Title</th>
15+
<th>AssignedTo</th>
16+
<th>State</th>
17+
<th>AreaPath</th>
18+
<th>ActivityDate</th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
@foreach (var workitem in Model.Item2)
23+
{
4224
<tr>
43-
<td>Iteration path</td>
44-
<td colspan="3">@fields.SystemIterationPath</td>
25+
@{ var fields = workitem.Fields; }
26+
<td>@workitem.Id</td>
27+
<td>@Html.ActionLink(fields.SystemTitle, "WorkItem", new { projectId = Model.Item1, workitemId = workitem.Id })</td>
28+
<td>@fields.SystemAssignedTo.DisplayName</td>
29+
<td>@fields.SystemState</td>
30+
<td>@fields.SystemAreaPath</td>
31+
<td>@fields.SystemCreatedDate</td>
4532
</tr>
46-
</tbody>
47-
</table>
48-
}
33+
}
34+
</tbody>
35+
</table>

CloneDevOpsTemplateTest/Controllers/WorkItemsControllerTest.cs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ public WorkItemsControllerTest()
2121
public async Task WorkItems_InvalidModelState_ReturnsEmptyView()
2222
{
2323
// Arrange
24-
_controller.ModelState.AddModelError("Error", "Invalid model state");
24+
Guid projectId = Guid.NewGuid();
25+
_controller.ModelState.AddModelError("Error", "Invalid model state");
2526

2627
// Act
27-
var result = await _controller.WorkItems(Guid.NewGuid(), "TestProject");
28+
var result = await _controller.WorkItems(projectId, "TestProject");
2829

2930
// Assert
3031
var viewResult = Assert.IsType<ViewResult>(result);
31-
var model = Assert.IsType<WorkItem[]>(viewResult.Model);
32-
Assert.Empty(model);
32+
var model = Assert.IsType<Tuple<Guid, WorkItem[]>>(viewResult.Model);
33+
Assert.Equal(projectId, model.Item1);
34+
Assert.Empty(model.Item2);
3335
}
3436

3537
[Fact]
@@ -69,10 +71,11 @@ public async Task WorkItems_ValidModelState_ReturnsWorkItems()
6971

7072
// Assert
7173
var viewResult = Assert.IsType<ViewResult>(result);
72-
var model = Assert.IsType<WorkItem[]>(viewResult.Model);
73-
Assert.Equal(2, model.Length);
74-
Assert.Contains(model, w => w.Id == 1 && w.Fields.SystemTitle == "WorkItem1");
75-
Assert.Contains(model, w => w.Id == 2 && w.Fields.SystemTitle == "WorkItem2");
74+
var model = Assert.IsType<Tuple<Guid, WorkItem[]>>(viewResult.Model);
75+
Assert.Equal(projectId, model.Item1);
76+
Assert.Equal(2, model.Item2.Length);
77+
Assert.Contains(model.Item2, w => w.Id == 1 && w.Fields.SystemTitle == "WorkItem1");
78+
Assert.Contains(model.Item2, w => w.Id == 2 && w.Fields.SystemTitle == "WorkItem2");
7679
}
7780

7881
[Fact]
@@ -91,7 +94,73 @@ public async Task WorkItems_ValidModelState_NoWorkItems_ReturnsEmptyView()
9194

9295
// Assert
9396
var viewResult = Assert.IsType<ViewResult>(result);
94-
var model = Assert.IsType<WorkItem[]>(viewResult.Model);
95-
Assert.Empty(model);
97+
var model = Assert.IsType<Tuple<Guid, WorkItem[]>>(viewResult.Model);
98+
Assert.Equal(projectId, model.Item1);
99+
Assert.Empty(model.Item2);
96100
}
97-
}
101+
102+
[Fact]
103+
public async Task WorkItem_InvalidModelState_ReturnsEmptyWorkItem()
104+
{
105+
// Arrange
106+
_controller.ModelState.AddModelError("Error", "Invalid model state");
107+
108+
// Act
109+
var result = await _controller.WorkItem(Guid.NewGuid(), 1);
110+
111+
// Assert
112+
var viewResult = Assert.IsType<ViewResult>(result);
113+
var model = Assert.IsType<WorkItem>(viewResult.Model);
114+
Assert.NotNull(model);
115+
Assert.Equal(0, model.Id);
116+
}
117+
118+
[Fact]
119+
public async Task WorkItem_ValidModelState_ReturnsWorkItem()
120+
{
121+
// Arrange
122+
var projectId = Guid.NewGuid();
123+
var workitemId = 1;
124+
var expectedWorkItem = new WorkItem
125+
{
126+
Id = workitemId,
127+
Fields = new Fields { SystemTitle = "Test WorkItem" }
128+
};
129+
130+
_mockWorkItemService
131+
.Setup(s => s.GetWorkItemAsync(projectId, workitemId))
132+
.ReturnsAsync(expectedWorkItem);
133+
134+
// Act
135+
var result = await _controller.WorkItem(projectId, workitemId);
136+
137+
// Assert
138+
var viewResult = Assert.IsType<ViewResult>(result);
139+
var model = Assert.IsType<WorkItem>(viewResult.Model);
140+
Assert.NotNull(model);
141+
Assert.Equal(expectedWorkItem.Id, model.Id);
142+
Assert.Equal(expectedWorkItem.Fields.SystemTitle, model.Fields.SystemTitle);
143+
}
144+
145+
[Fact]
146+
public async Task WorkItem_ValidModelState_WorkItemNotFound_ReturnsEmptyWorkItem()
147+
{
148+
// Arrange
149+
var projectId = Guid.NewGuid();
150+
var workitemId = 1;
151+
152+
_mockWorkItemService
153+
.Setup(s => s.GetWorkItemAsync(projectId, workitemId))
154+
.ReturnsAsync((WorkItem?)null);
155+
156+
// Act
157+
var result = await _controller.WorkItem(projectId, workitemId);
158+
159+
// Assert
160+
var viewResult = Assert.IsType<ViewResult>(result);
161+
var model = Assert.IsType<WorkItem>(viewResult.Model);
162+
Assert.NotNull(model);
163+
Assert.Equal(0, model.Id);
164+
}
165+
}
166+

0 commit comments

Comments
 (0)