Skip to content

Commit 966b635

Browse files
author
Robert Speicher
committed
Merge branch '28453-add-time-estimate-time-spent-to-api-issue-output' into 'master'
Add time stats to Issue and Merge Request API Closes #28453 See merge request !13335
2 parents b190c6a + 54b0f57 commit 966b635

File tree

9 files changed

+207
-17
lines changed

9 files changed

+207
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: Add time stats to Issue and Merge Request API
3+
merge_request: 13335
4+
author: @travismiller

doc/api/issues.md

+55-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ Example response:
101101
"user_notes_count": 1,
102102
"due_date": "2016-07-22",
103103
"web_url": "http://example.com/example/example/issues/6",
104+
"time_stats": {
105+
"time_estimate": 0,
106+
"total_time_spent": 0,
107+
"human_time_estimate": null,
108+
"human_total_time_spent": null
109+
},
104110
"confidential": false
105111
}
106112
]
@@ -198,6 +204,12 @@ Example response:
198204
"user_notes_count": 1,
199205
"due_date": null,
200206
"web_url": "http://example.com/example/example/issues/1",
207+
"time_stats": {
208+
"time_estimate": 0,
209+
"total_time_spent": 0,
210+
"human_time_estimate": null,
211+
"human_total_time_spent": null
212+
},
201213
"confidential": false
202214
}
203215
]
@@ -296,6 +308,12 @@ Example response:
296308
"user_notes_count": 1,
297309
"due_date": "2016-07-22",
298310
"web_url": "http://example.com/example/example/issues/1",
311+
"time_stats": {
312+
"time_estimate": 0,
313+
"total_time_spent": 0,
314+
"human_time_estimate": null,
315+
"human_total_time_spent": null
316+
},
299317
"confidential": false
300318
}
301319
]
@@ -372,6 +390,12 @@ Example response:
372390
"user_notes_count": 1,
373391
"due_date": null,
374392
"web_url": "http://example.com/example/example/issues/1",
393+
"time_stats": {
394+
"time_estimate": 0,
395+
"total_time_spent": 0,
396+
"human_time_estimate": null,
397+
"human_total_time_spent": null
398+
},
375399
"confidential": false,
376400
"_links": {
377401
"self": "http://example.com/api/v4/projects/1/issues/2",
@@ -440,6 +464,12 @@ Example response:
440464
"user_notes_count": 0,
441465
"due_date": null,
442466
"web_url": "http://example.com/example/example/issues/14",
467+
"time_stats": {
468+
"time_estimate": 0,
469+
"total_time_spent": 0,
470+
"human_time_estimate": null,
471+
"human_total_time_spent": null
472+
},
443473
"confidential": false,
444474
"_links": {
445475
"self": "http://example.com/api/v4/projects/1/issues/2",
@@ -509,6 +539,12 @@ Example response:
509539
"user_notes_count": 0,
510540
"due_date": "2016-07-22",
511541
"web_url": "http://example.com/example/example/issues/15",
542+
"time_stats": {
543+
"time_estimate": 0,
544+
"total_time_spent": 0,
545+
"human_time_estimate": null,
546+
"human_total_time_spent": null
547+
},
512548
"confidential": false,
513549
"_links": {
514550
"self": "http://example.com/api/v4/projects/1/issues/2",
@@ -601,6 +637,12 @@ Example response:
601637
},
602638
"due_date": null,
603639
"web_url": "http://example.com/example/example/issues/11",
640+
"time_stats": {
641+
"time_estimate": 0,
642+
"total_time_spent": 0,
643+
"human_time_estimate": null,
644+
"human_total_time_spent": null
645+
},
604646
"confidential": false,
605647
"_links": {
606648
"self": "http://example.com/api/v4/projects/1/issues/2",
@@ -672,6 +714,12 @@ Example response:
672714
},
673715
"due_date": null,
674716
"web_url": "http://example.com/example/example/issues/11",
717+
"time_stats": {
718+
"time_estimate": 0,
719+
"total_time_spent": 0,
720+
"human_time_estimate": null,
721+
"human_total_time_spent": null
722+
},
675723
"confidential": false,
676724
"_links": {
677725
"self": "http://example.com/api/v4/projects/1/issues/2",
@@ -1001,7 +1049,13 @@ Example response:
10011049
"user_notes_count": 1,
10021050
"should_remove_source_branch": null,
10031051
"force_remove_source_branch": false,
1004-
"web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432"
1052+
"web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432",
1053+
"time_stats": {
1054+
"time_estimate": 0,
1055+
"total_time_spent": 0,
1056+
"human_time_estimate": null,
1057+
"human_total_time_spent": null
1058+
}
10051059
}
10061060
]
10071061
```

doc/api/merge_requests.md

+55-7
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ Parameters:
9292
"user_notes_count": 1,
9393
"should_remove_source_branch": true,
9494
"force_remove_source_branch": false,
95-
"web_url": "http://example.com/example/example/merge_requests/1"
95+
"web_url": "http://example.com/example/example/merge_requests/1",
96+
"time_stats": {
97+
"time_estimate": 0,
98+
"total_time_spent": 0,
99+
"human_time_estimate": null,
100+
"human_total_time_spent": null
101+
}
96102
}
97103
]
98104
```
@@ -181,7 +187,13 @@ Parameters:
181187
"user_notes_count": 1,
182188
"should_remove_source_branch": true,
183189
"force_remove_source_branch": false,
184-
"web_url": "http://example.com/example/example/merge_requests/1"
190+
"web_url": "http://example.com/example/example/merge_requests/1",
191+
"time_stats": {
192+
"time_estimate": 0,
193+
"total_time_spent": 0,
194+
"human_time_estimate": null,
195+
"human_total_time_spent": null
196+
}
185197
}
186198
]
187199
```
@@ -250,7 +262,13 @@ Parameters:
250262
"user_notes_count": 1,
251263
"should_remove_source_branch": true,
252264
"force_remove_source_branch": false,
253-
"web_url": "http://example.com/example/example/merge_requests/1"
265+
"web_url": "http://example.com/example/example/merge_requests/1",
266+
"time_stats": {
267+
"time_estimate": 0,
268+
"total_time_spent": 0,
269+
"human_time_estimate": null,
270+
"human_total_time_spent": null
271+
}
254272
}
255273
```
256274

@@ -356,6 +374,12 @@ Parameters:
356374
"should_remove_source_branch": true,
357375
"force_remove_source_branch": false,
358376
"web_url": "http://example.com/example/example/merge_requests/1",
377+
"time_stats": {
378+
"time_estimate": 0,
379+
"total_time_spent": 0,
380+
"human_time_estimate": null,
381+
"human_total_time_spent": null
382+
}
359383
"changes": [
360384
{
361385
"old_path": "VERSION",
@@ -442,7 +466,13 @@ POST /projects/:id/merge_requests
442466
"user_notes_count": 0,
443467
"should_remove_source_branch": true,
444468
"force_remove_source_branch": false,
445-
"web_url": "http://example.com/example/example/merge_requests/1"
469+
"web_url": "http://example.com/example/example/merge_requests/1",
470+
"time_stats": {
471+
"time_estimate": 0,
472+
"total_time_spent": 0,
473+
"human_time_estimate": null,
474+
"human_total_time_spent": null
475+
}
446476
}
447477
```
448478

@@ -519,7 +549,13 @@ Must include at least one non-required attribute from above.
519549
"user_notes_count": 1,
520550
"should_remove_source_branch": true,
521551
"force_remove_source_branch": false,
522-
"web_url": "http://example.com/example/example/merge_requests/1"
552+
"web_url": "http://example.com/example/example/merge_requests/1",
553+
"time_stats": {
554+
"time_estimate": 0,
555+
"total_time_spent": 0,
556+
"human_time_estimate": null,
557+
"human_total_time_spent": null
558+
}
523559
}
524560
```
525561

@@ -617,7 +653,13 @@ Parameters:
617653
"user_notes_count": 1,
618654
"should_remove_source_branch": true,
619655
"force_remove_source_branch": false,
620-
"web_url": "http://example.com/example/example/merge_requests/1"
656+
"web_url": "http://example.com/example/example/merge_requests/1",
657+
"time_stats": {
658+
"time_estimate": 0,
659+
"total_time_spent": 0,
660+
"human_time_estimate": null,
661+
"human_total_time_spent": null
662+
}
621663
}
622664
```
623665

@@ -687,7 +729,13 @@ Parameters:
687729
"user_notes_count": 1,
688730
"should_remove_source_branch": true,
689731
"force_remove_source_branch": false,
690-
"web_url": "http://example.com/example/example/merge_requests/1"
732+
"web_url": "http://example.com/example/example/merge_requests/1",
733+
"time_stats": {
734+
"time_estimate": 0,
735+
"total_time_spent": 0,
736+
"human_time_estimate": null,
737+
"human_total_time_spent": null
738+
}
691739
}
692740
```
693741

lib/api/entities.rb

+41-3
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,10 @@ class Milestone < Grape::Entity
320320
end
321321

322322
class IssueBasic < ProjectEntity
323-
expose :label_names, as: :labels
323+
expose :labels do |issue, options|
324+
# Avoids an N+1 query since labels are preloaded
325+
issue.labels.map(&:title).sort
326+
end
324327
expose :milestone, using: Entities::Milestone
325328
expose :assignees, :author, using: Entities::UserBasic
326329

@@ -329,13 +332,32 @@ class IssueBasic < ProjectEntity
329332
end
330333

331334
expose :user_notes_count
332-
expose :upvotes, :downvotes
335+
expose :upvotes do |issue, options|
336+
if options[:issuable_metadata]
337+
# Avoids an N+1 query when metadata is included
338+
options[:issuable_metadata][issue.id].upvotes
339+
else
340+
issue.upvotes
341+
end
342+
end
343+
expose :downvotes do |issue, options|
344+
if options[:issuable_metadata]
345+
# Avoids an N+1 query when metadata is included
346+
options[:issuable_metadata][issue.id].downvotes
347+
else
348+
issue.downvotes
349+
end
350+
end
333351
expose :due_date
334352
expose :confidential
335353

336354
expose :web_url do |issue, options|
337355
Gitlab::UrlBuilder.build(issue)
338356
end
357+
358+
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
359+
issue
360+
end
339361
end
340362

341363
class Issue < IssueBasic
@@ -365,10 +387,22 @@ class Issue < IssueBasic
365387
end
366388

367389
class IssuableTimeStats < Grape::Entity
390+
format_with(:time_tracking_formatter) do |time_spent|
391+
Gitlab::TimeTrackingFormatter.output(time_spent)
392+
end
393+
368394
expose :time_estimate
369395
expose :total_time_spent
370396
expose :human_time_estimate
371-
expose :human_total_time_spent
397+
398+
with_options(format_with: :time_tracking_formatter) do
399+
expose :total_time_spent, as: :human_total_time_spent
400+
end
401+
402+
def total_time_spent
403+
# Avoids an N+1 query since timelogs are preloaded
404+
object.timelogs.map(&:time_spent).sum
405+
end
372406
end
373407

374408
class ExternalIssue < Grape::Entity
@@ -418,6 +452,10 @@ class MergeRequestBasic < ProjectEntity
418452
expose :web_url do |merge_request, options|
419453
Gitlab::UrlBuilder.build(merge_request)
420454
end
455+
456+
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
457+
merge_request
458+
end
421459
end
422460

423461
class MergeRequest < MergeRequestBasic

lib/api/issues.rb

+25-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class Issues < Grape::API
44

55
before { authenticate! }
66

7+
helpers ::Gitlab::IssuableMetadata
8+
79
helpers do
810
def find_issues(args = {})
911
args = params.merge(args)
@@ -13,6 +15,7 @@ def find_issues(args = {})
1315
args[:label_name] = args.delete(:labels)
1416

1517
issues = IssuesFinder.new(current_user, args).execute
18+
.preload(:assignees, :labels, :notes, :timelogs)
1619

1720
issues.reorder(args[:order_by] => args[:sort])
1821
end
@@ -65,7 +68,13 @@ def find_issues(args = {})
6568
get do
6669
issues = find_issues
6770

68-
present paginate(issues), with: Entities::IssueBasic, current_user: current_user
71+
options = {
72+
with: Entities::IssueBasic,
73+
current_user: current_user,
74+
issuable_metadata: issuable_meta_data(issues, 'Issue')
75+
}
76+
77+
present paginate(issues), options
6978
end
7079
end
7180

@@ -86,7 +95,13 @@ def find_issues(args = {})
8695

8796
issues = find_issues(group_id: group.id)
8897

89-
present paginate(issues), with: Entities::IssueBasic, current_user: current_user
98+
options = {
99+
with: Entities::IssueBasic,
100+
current_user: current_user,
101+
issuable_metadata: issuable_meta_data(issues, 'Issue')
102+
}
103+
104+
present paginate(issues), options
90105
end
91106
end
92107

@@ -109,7 +124,14 @@ def find_issues(args = {})
109124

110125
issues = find_issues(project_id: project.id)
111126

112-
present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
127+
options = {
128+
with: Entities::IssueBasic,
129+
current_user: current_user,
130+
project: user_project,
131+
issuable_metadata: issuable_meta_data(issues, 'Issue')
132+
}
133+
134+
present paginate(issues), options
113135
end
114136

115137
desc 'Get a single project issue' do

lib/api/merge_requests.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def find_merge_requests(args = {})
2121
return merge_requests if args[:view] == 'simple'
2222

2323
merge_requests
24-
.preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
24+
.preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs)
2525
end
2626

2727
params :merge_requests_params do

0 commit comments

Comments
 (0)