Skip to content

Commit 38f4e75

Browse files
authored
Add support for boards and sprints (#54)
* WIP add boards UI * add boards tab page * add edit item action * sort items by changed date desc * add add item action Disallow changing areaPath and limit work item types to the ones allowed in the board. * add move to column action * add link to boards from project detail page And remove boards from project detail page. * add sprints in boards page * handle boards/sprints error * don't show teams with no sprints * extract BoardWidget component * show 'No items' text if there are no items in a column * add item to sprint * make some models private * bump version * improve project boards page UI And update boards icon * improve board/sprint page UI * fix get sprint columns with old data provider
1 parent b38eddc commit 38f4e75

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2085
-297
lines changed

assets/fonts/DevOpsIcons.ttf

700 Bytes
Binary file not shown.

lib/src/models/backlog.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/src/response.dart';
4+
5+
class BacklogsResponse {
6+
BacklogsResponse({required this.boards});
7+
8+
factory BacklogsResponse.fromJson(Map<String, dynamic> json) => BacklogsResponse(
9+
boards: List<Backlog>.from(
10+
(json['value'] as List<dynamic>).map((x) => Backlog.fromJson(x as Map<String, dynamic>)),
11+
),
12+
);
13+
14+
final List<Backlog> boards;
15+
16+
static List<Backlog> fromResponse(Response res) =>
17+
BacklogsResponse.fromJson(jsonDecode(res.body) as Map<String, dynamic>).boards;
18+
}
19+
20+
class Backlog {
21+
Backlog({
22+
required this.id,
23+
required this.name,
24+
});
25+
26+
factory Backlog.fromJson(Map<String, dynamic> json) => Backlog(
27+
id: json['id'] as String? ?? '',
28+
name: json['name'] as String? ?? '',
29+
);
30+
31+
final String id;
32+
final String name;
33+
}

lib/src/models/board.dart

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import 'dart:convert';
2+
3+
import 'package:azure_devops/src/models/work_items.dart';
4+
import 'package:http/src/response.dart';
5+
6+
class BoardsResponse {
7+
BoardsResponse({required this.boards});
8+
9+
factory BoardsResponse.fromJson(Map<String, dynamic> json) => BoardsResponse(
10+
boards:
11+
List<Board>.from((json['value'] as List<dynamic>).map((x) => Board.fromJson(x as Map<String, dynamic>))),
12+
);
13+
14+
final List<Board> boards;
15+
16+
static List<Board> fromResponse(Response res) =>
17+
BoardsResponse.fromJson(jsonDecode(res.body) as Map<String, dynamic>).boards;
18+
}
19+
20+
class Board {
21+
Board({
22+
required this.id,
23+
required this.name,
24+
});
25+
26+
factory Board.fromJson(Map<String, dynamic> json) => Board(
27+
id: json['id'] as String? ?? '',
28+
name: json['name'] as String? ?? '',
29+
);
30+
31+
final String id;
32+
final String name;
33+
34+
// Used to get the board's work items
35+
String? backlogId;
36+
}
37+
38+
class BoardDetail {
39+
BoardDetail({
40+
required this.id,
41+
required this.name,
42+
required this.columns,
43+
required this.allowedMappings,
44+
required this.fields,
45+
});
46+
47+
factory BoardDetail.fromJson(Map<String, dynamic> json) => BoardDetail(
48+
id: json['id'] as String? ?? '',
49+
name: json['name'] as String? ?? '',
50+
columns: List<BoardColumn>.from(
51+
(json['columns'] as List<dynamic>? ?? []).map((x) => BoardColumn.fromJson(x as Map<String, dynamic>)),
52+
),
53+
allowedMappings: AllowedMappings.fromJson(json['allowedMappings'] as Map<String, dynamic>? ?? {}),
54+
fields: BoardFields.fromJson(json['fields'] as Map<String, dynamic>? ?? {}),
55+
);
56+
57+
static BoardDetail fromResponse(Response res) => BoardDetail.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
58+
59+
final String id;
60+
final String name;
61+
final List<BoardColumn> columns;
62+
final AllowedMappings allowedMappings;
63+
final BoardFields fields;
64+
}
65+
66+
class AllowedMappings {
67+
AllowedMappings({
68+
required this.incoming,
69+
required this.inProgress,
70+
required this.outgoing,
71+
});
72+
73+
factory AllowedMappings.fromJson(Map<String, dynamic> json) => AllowedMappings(
74+
incoming: <String, List<String>>{
75+
for (final entry in (json['Incoming'] as Map<String, dynamic>? ?? {}).entries)
76+
entry.key: List<String>.from(entry.value as List<dynamic>),
77+
},
78+
inProgress: <String, List<String>>{
79+
for (final entry in (json['InProgress'] as Map<String, dynamic>? ?? {}).entries)
80+
entry.key: List<String>.from(entry.value as List<dynamic>),
81+
},
82+
outgoing: <String, List<String>>{
83+
for (final entry in (json['Outgoing'] as Map<String, dynamic>? ?? {}).entries)
84+
entry.key: List<String>.from(entry.value as List<dynamic>),
85+
},
86+
);
87+
88+
final Map<String, List<String>> incoming;
89+
final Map<String, List<String>> inProgress;
90+
final Map<String, List<String>> outgoing;
91+
}
92+
93+
class BoardColumn {
94+
BoardColumn({
95+
required this.id,
96+
required this.name,
97+
required this.itemLimit,
98+
required this.stateMappings,
99+
required this.columnType,
100+
required this.isSplit,
101+
required this.description,
102+
});
103+
104+
factory BoardColumn.fromJson(Map<String, dynamic> json) => BoardColumn(
105+
id: json['id'] as String? ?? '',
106+
name: json['name'] as String? ?? '',
107+
itemLimit: json['itemLimit'] as int? ?? 0,
108+
stateMappings: json['stateMappings'] as Map<String, dynamic>? ?? {},
109+
columnType: json['columnType'] as String? ?? '',
110+
isSplit: json['isSplit'] as bool? ?? false,
111+
description: json['description'] as String? ?? '',
112+
);
113+
114+
factory BoardColumn.fromState({required String state}) => BoardColumn(
115+
id: state,
116+
name: state,
117+
itemLimit: 0,
118+
stateMappings: {},
119+
columnType: '',
120+
isSplit: false,
121+
description: '',
122+
);
123+
124+
final String id;
125+
final String name;
126+
final int itemLimit;
127+
final Map<String, dynamic> stateMappings;
128+
final String columnType;
129+
final bool isSplit;
130+
final String description;
131+
132+
@override
133+
bool operator ==(covariant BoardColumn other) {
134+
if (identical(this, other)) return true;
135+
136+
return other.id == id;
137+
}
138+
139+
@override
140+
int get hashCode {
141+
return id.hashCode;
142+
}
143+
}
144+
145+
class BoardFields {
146+
BoardFields({required this.columnField});
147+
148+
factory BoardFields.fromJson(Map<String, dynamic> json) => BoardFields(
149+
columnField: BoardField.fromJson(json['columnField'] as Map<String, dynamic>? ?? {}),
150+
);
151+
152+
final BoardField columnField;
153+
}
154+
155+
class BoardField {
156+
BoardField({required this.referenceName});
157+
158+
factory BoardField.fromJson(Map<String, dynamic> json) => BoardField(
159+
referenceName: json['referenceName'] as String? ?? '',
160+
);
161+
162+
final String referenceName;
163+
}
164+
165+
class BoardDetailWithItems {
166+
BoardDetailWithItems({
167+
required this.board,
168+
required this.items,
169+
});
170+
171+
final BoardDetail board;
172+
final List<WorkItem> items;
173+
}
174+
175+
class BoardItemsResponse {
176+
BoardItemsResponse({required this.data});
177+
178+
factory BoardItemsResponse.fromJson(Map<String, dynamic> json) => BoardItemsResponse(
179+
data: _DataProvidersData.fromJson(json['dataProviders'] as Map<String, dynamic>? ?? {}),
180+
);
181+
182+
static BoardItemsResponse fromResponse(Response res) =>
183+
BoardItemsResponse.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
184+
185+
final _DataProvidersData data;
186+
}
187+
188+
class _DataProvidersData {
189+
_DataProvidersData({required this.content});
190+
191+
factory _DataProvidersData.fromJson(Map<String, dynamic> json) => _DataProvidersData(
192+
content: _MsVssWorkWebKanbanBoardContentDataProvider.fromJson(
193+
json['ms.vss-work-web.kanban-board-content-data-provider'] as Map<String, dynamic>? ?? {},
194+
),
195+
);
196+
197+
final _MsVssWorkWebKanbanBoardContentDataProvider content;
198+
}
199+
200+
class _MsVssWorkWebKanbanBoardContentDataProvider {
201+
_MsVssWorkWebKanbanBoardContentDataProvider({
202+
required this.boardModel,
203+
});
204+
205+
factory _MsVssWorkWebKanbanBoardContentDataProvider.fromJson(Map<String, dynamic> json) =>
206+
_MsVssWorkWebKanbanBoardContentDataProvider(
207+
boardModel: _BoardModel.fromJson(json['boardModel'] as Map<String, dynamic>? ?? {}),
208+
);
209+
210+
final _BoardModel boardModel;
211+
}
212+
213+
class _BoardModel {
214+
_BoardModel({required this.itemSource});
215+
216+
factory _BoardModel.fromJson(Map<String, dynamic> json) => _BoardModel(
217+
itemSource: _ItemSource.fromJson(json['itemSource'] as Map<String, dynamic>? ?? {}),
218+
);
219+
220+
final _ItemSource itemSource;
221+
}
222+
223+
class _ItemSource {
224+
_ItemSource({required this.payload});
225+
226+
factory _ItemSource.fromJson(Map<String, dynamic> json) => _ItemSource(
227+
payload: _Payload.fromJson(json['payload'] as Map<String, dynamic>? ?? {}),
228+
);
229+
230+
final _Payload payload;
231+
}
232+
233+
class _Payload {
234+
_Payload({
235+
required this.rows,
236+
required this.orderedOutgoingIds,
237+
});
238+
239+
factory _Payload.fromJson(Map<String, dynamic> json) => _Payload(
240+
rows: List<int>.from(
241+
(json['rows'] as List<dynamic>? ?? [])
242+
.map((x) => List<dynamic>.from(x as List<dynamic>? ?? []))
243+
.map((l) => l.firstOrNull as int? ?? 0)
244+
.toList(),
245+
),
246+
orderedOutgoingIds: List<int>.from((json['orderedOutgoingIds'] as List<dynamic>? ?? []).map((x) => x)),
247+
);
248+
249+
final List<int> rows;
250+
final List<int> orderedOutgoingIds;
251+
}

0 commit comments

Comments
 (0)