Commit bf29f197 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'expose-mr-timelogs-via-graphql' into 'master'

Expose merge request timelogs via graphql

See merge request gitlab-org/gitlab!57322
parents 9d6f4ecd 72f5e1fc
...@@ -144,7 +144,7 @@ module Types ...@@ -144,7 +144,7 @@ module Types
resolver: Resolvers::GroupLabelsResolver resolver: Resolvers::GroupLabelsResolver
field :timelogs, ::Types::TimelogType.connection_type, null: false, field :timelogs, ::Types::TimelogType.connection_type, null: false,
description: 'Time logged on issues in the group and its subgroups.', description: 'Time logged on issues and merge requests in the group and its subgroups.',
extras: [:lookahead], extras: [:lookahead],
complexity: 5, complexity: 5,
resolver: ::Resolvers::TimelogResolver resolver: ::Resolvers::TimelogResolver
......
...@@ -26,6 +26,11 @@ module Types ...@@ -26,6 +26,11 @@ module Types
null: true, null: true,
description: 'The issue that logged time was added to.' description: 'The issue that logged time was added to.'
field :merge_request,
Types::MergeRequestType,
null: true,
description: 'The merge request that logged time was added to.'
field :note, field :note,
Types::Notes::NoteType, Types::Notes::NoteType,
null: true, null: true,
......
...@@ -15,6 +15,6 @@ module HasTimelogsReport ...@@ -15,6 +15,6 @@ module HasTimelogsReport
private private
def timelogs_for(start_time, end_time) def timelogs_for(start_time, end_time)
Timelog.between_times(start_time, end_time).for_issues_in_group(self) Timelog.between_times(start_time, end_time).in_group(self)
end end
end end
...@@ -14,12 +14,8 @@ class Timelog < ApplicationRecord ...@@ -14,12 +14,8 @@ class Timelog < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :note belongs_to :note
scope :for_issues_in_group, -> (group) do scope :in_group, -> (group) do
joins(:issue).where( joins(:project).where(projects: { namespace: group.self_and_descendants })
'EXISTS (?)',
Project.select(1).where(namespace: group.self_and_descendants)
.where('issues.project_id = projects.id')
)
end end
scope :between_times, -> (start_time, end_time) do scope :between_times, -> (start_time, end_time) do
......
---
title: Expose merge request timelogs via GraphQL
merge_request: 57322
author: Lee Tickett @leetickett
type: added
...@@ -9005,7 +9005,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -9005,7 +9005,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
##### `Group.timelogs` ##### `Group.timelogs`
Time logged on issues in the group and its subgroups. Time logged on issues and merge requests in the group and its subgroups.
Returns [`TimelogConnection!`](#timelogconnection). Returns [`TimelogConnection!`](#timelogconnection).
...@@ -12498,6 +12498,7 @@ Represents a historically accurate report about the timebox. ...@@ -12498,6 +12498,7 @@ Represents a historically accurate report about the timebox.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="timelogissue"></a>`issue` | [`Issue`](#issue) | The issue that logged time was added to. | | <a id="timelogissue"></a>`issue` | [`Issue`](#issue) | The issue that logged time was added to. |
| <a id="timelogmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | The merge request that logged time was added to. |
| <a id="timelognote"></a>`note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. | | <a id="timelognote"></a>`note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. |
| <a id="timelogspentat"></a>`spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. | | <a id="timelogspentat"></a>`spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. |
| <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. | | <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. |
......
...@@ -99,3 +99,8 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`. ...@@ -99,3 +99,8 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
## Other interesting links ## Other interesting links
- [Time Tracking solutions page](https://about.gitlab.com/solutions/time-tracking/) - [Time Tracking solutions page](https://about.gitlab.com/solutions/time-tracking/)
- Time Tracking GraphQL references:
- [Connection](../../api/graphql/reference/index.md#timelogconnection)
- [Edge](../../api/graphql/reference/index.md#timelogedge)
- [Fields](../../api/graphql/reference/index.md#timelog)
- [Group Timelogs](../../api/graphql/reference/index.md#grouptimelogs)
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['Timelog'] do RSpec.describe GitlabSchema.types['Timelog'] do
let(:fields) { %i[spent_at time_spent user issue note] } let(:fields) { %i[spent_at time_spent user issue merge_request note] }
it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class.graphql_name).to eq('Timelog') }
it { expect(described_class).to have_graphql_fields(fields) } it { expect(described_class).to have_graphql_fields(fields) }
...@@ -25,6 +25,14 @@ RSpec.describe GitlabSchema.types['Timelog'] do ...@@ -25,6 +25,14 @@ RSpec.describe GitlabSchema.types['Timelog'] do
end end
end end
describe 'merge_request field' do
subject { described_class.fields['mergeRequest'] }
it 'returns merge_request' do
is_expected.to have_graphql_type(Types::MergeRequestType)
end
end
describe 'note field' do describe 'note field' do
subject { described_class.fields['note'] } subject { described_class.fields['note'] }
......
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe HasTimelogsReport do RSpec.describe HasTimelogsReport do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:issue) { create(:issue, project: create(:project, :public, group: group)) } let(:project) { create(:project, :public, group: group) }
let(:issue1) { create(:issue, project: project) }
let(:merge_request1) { create(:merge_request, source_project: project) }
describe '#timelogs' do describe '#timelogs' do
let!(:timelog1) { create_timelog(15.days.ago) } let_it_be(:start_time) { 20.days.ago }
let!(:timelog2) { create_timelog(10.days.ago) } let_it_be(:end_time) { 8.days.ago }
let!(:timelog3) { create_timelog(5.days.ago) }
let(:start_time) { 20.days.ago } let!(:timelog1) { create_timelog(15.days.ago, issue: issue1) }
let(:end_time) { 8.days.ago } let!(:timelog2) { create_timelog(10.days.ago, merge_request: merge_request1) }
let!(:timelog3) { create_timelog(5.days.ago, issue: issue1) }
before do before do
group.add_developer(user) group.add_developer(user)
...@@ -45,7 +49,7 @@ RSpec.describe HasTimelogsReport do ...@@ -45,7 +49,7 @@ RSpec.describe HasTimelogsReport do
end end
end end
def create_timelog(time) def create_timelog(time, issue: nil, merge_request: nil)
create(:timelog, issue: issue, user: user, spent_at: time) create(:timelog, issue: issue, merge_request: merge_request, user: user, spent_at: time)
end end
end end
...@@ -5,8 +5,8 @@ require 'spec_helper' ...@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe Timelog do RSpec.describe Timelog do
subject { create(:timelog) } subject { create(:timelog) }
let(:issue) { create(:issue) } let_it_be(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) } let_it_be(:merge_request) { create(:merge_request) }
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:issue).touch(true) } it { is_expected.to belong_to(:issue).touch(true) }
...@@ -54,27 +54,34 @@ RSpec.describe Timelog do ...@@ -54,27 +54,34 @@ RSpec.describe Timelog do
end end
describe 'scopes' do describe 'scopes' do
describe 'for_issues_in_group' do let_it_be(:group) { create(:group) }
it 'return timelogs created for group issues' do let_it_be(:group_project) { create(:project, :empty_repo, group: group) }
group = create(:group) let_it_be(:group_issue) { create(:issue, project: group_project) }
subgroup = create(:group, parent: group) let_it_be(:group_merge_request) { create(:merge_request, source_project: group_project) }
create(:issue_timelog) let_it_be(:subgroup) { create(:group, parent: group) }
timelog1 = create(:issue_timelog, issue: create(:issue, project: create(:project, group: group))) let_it_be(:subgroup_project) { create(:project, :empty_repo, group: subgroup) }
timelog2 = create(:issue_timelog, issue: create(:issue, project: create(:project, group: subgroup))) let_it_be(:subgroup_issue) { create(:issue, project: subgroup_project) }
let_it_be(:subgroup_merge_request) { create(:merge_request, source_project: subgroup_project) }
expect(described_class.for_issues_in_group(group)).to contain_exactly(timelog1, timelog2)
let_it_be(:timelog) { create(:issue_timelog, spent_at: 65.days.ago) }
let_it_be(:timelog1) { create(:issue_timelog, spent_at: 15.days.ago, issue: group_issue) }
let_it_be(:timelog2) { create(:issue_timelog, spent_at: 5.days.ago, issue: subgroup_issue) }
let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: 65.days.ago) }
let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: 15.days.ago, merge_request: group_merge_request) }
let_it_be(:timelog5) { create(:merge_request_timelog, spent_at: 5.days.ago, merge_request: subgroup_merge_request) }
describe 'in_group' do
it 'return timelogs created for group issues and merge requests' do
expect(described_class.in_group(group)).to contain_exactly(timelog1, timelog2, timelog4, timelog5)
end end
end end
describe 'between_times' do describe 'between_times' do
it 'returns collection of timelogs within given times' do it 'returns collection of timelogs within given times' do
create(:issue_timelog, spent_at: 65.days.ago) timelogs = described_class.between_times(20.days.ago, 10.days.ago)
timelog1 = create(:issue_timelog, spent_at: 15.days.ago)
timelog2 = create(:issue_timelog, spent_at: 5.days.ago)
timelogs = described_class.between_times(20.days.ago, 1.day.ago)
expect(timelogs).to contain_exactly(timelog1, timelog2) expect(timelogs).to contain_exactly(timelog1, timelog4)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment