Commit e9af15f5 authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak

Merge branch 'jivanvl-add-group-ci-minutes-query' into 'master'

Add namespace to the ci_minutes_usage resolver

See merge request gitlab-org/gitlab!74436
parents 4246141f d7b64118
...@@ -73,7 +73,7 @@ Returns [`CiConfig`](#ciconfig). ...@@ -73,7 +73,7 @@ Returns [`CiConfig`](#ciconfig).
### `Query.ciMinutesUsage` ### `Query.ciMinutesUsage`
Monthly CI minutes usage data for the current user. CI/CD minutes usage data for a namespace.
Returns [`CiMinutesNamespaceMonthlyUsageConnection`](#ciminutesnamespacemonthlyusageconnection). Returns [`CiMinutesNamespaceMonthlyUsageConnection`](#ciminutesnamespacemonthlyusageconnection).
...@@ -81,6 +81,12 @@ This field returns a [connection](#connections). It accepts the ...@@ -81,6 +81,12 @@ This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments): four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`. `before: String`, `after: String`, `first: Int`, `last: Int`.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryciminutesusagenamespaceid"></a>`namespaceId` | [`NamespaceID`](#namespaceid) | Global ID of the Namespace for the monthly CI/CD minutes usage. |
### `Query.containerRepository` ### `Query.containerRepository`
Find a container repository. Find a container repository.
...@@ -4,7 +4,6 @@ module EE ...@@ -4,7 +4,6 @@ module EE
module Types module Types
module QueryType module QueryType
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
field :iteration, ::Types::IterationType, field :iteration, ::Types::IterationType,
null: true, null: true,
...@@ -70,7 +69,12 @@ module EE ...@@ -70,7 +69,12 @@ module EE
field :ci_minutes_usage, ::Types::Ci::Minutes::NamespaceMonthlyUsageType.connection_type, field :ci_minutes_usage, ::Types::Ci::Minutes::NamespaceMonthlyUsageType.connection_type,
null: true, null: true,
description: 'Monthly CI minutes usage data for the current user.' description: 'CI/CD minutes usage data for a namespace.' do
argument :namespace_id,
::Types::GlobalIDType[::Namespace],
required: false,
description: 'Global ID of the Namespace for the monthly CI/CD minutes usage.'
end
end end
def vulnerability(id:) def vulnerability(id:)
...@@ -87,8 +91,20 @@ module EE ...@@ -87,8 +91,20 @@ module EE
::GitlabSchema.find_by_gid(id) ::GitlabSchema.find_by_gid(id)
end end
def ci_minutes_usage def ci_minutes_usage(namespace_id: nil)
::Ci::Minutes::NamespaceMonthlyUsage.for_namespace(current_user.namespace) root_namespace = find_root_namespace(namespace_id)
::Ci::Minutes::NamespaceMonthlyUsage.for_namespace(root_namespace)
end
private
def find_root_namespace(namespace_id)
return current_user&.namespace unless namespace_id
namespace = ::Gitlab::Graphql::Lazy.force(::GitlabSchema.find_by_gid(namespace_id))
return unless namespace&.root?
namespace
end end
end end
end end
......
...@@ -7,6 +7,7 @@ module Types ...@@ -7,6 +7,7 @@ module Types
# this type only exposes data related to the current user # this type only exposes data related to the current user
class NamespaceMonthlyUsageType < BaseObject class NamespaceMonthlyUsageType < BaseObject
graphql_name 'CiMinutesNamespaceMonthlyUsage' graphql_name 'CiMinutesNamespaceMonthlyUsage'
authorize :read_usage
field :month, ::GraphQL::Types::String, null: true, field :month, ::GraphQL::Types::String, null: true,
description: 'Month related to the usage data.' description: 'Month related to the usage data.'
......
# frozen_string_literal: true
module Ci
module Minutes
class NamespaceMonthlyUsagePolicy < BasePolicy
delegate { @subject.namespace }
rule { can?(:owner_access) }.enable :read_usage
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Minutes::NamespaceMonthlyUsagePolicy do
let(:group) { create(:group, :private, name: 'test') }
let(:current_user) { create(:user) }
let(:namespace_monthly_usage) do
create(:ci_namespace_monthly_usage, namespace: group)
end
subject(:policy) do
described_class.new(current_user, namespace_monthly_usage)
end
context 'with an owner' do
before do
group.add_owner(current_user)
end
it { is_expected.to be_allowed(:read_usage) }
end
context 'with a developer' do
before do
group.add_developer(current_user)
end
it { is_expected.not_to be_allowed(:read_usage) }
end
context "with a user's namespace" do
let(:namespace_monthly_usage) do
create(:ci_namespace_monthly_usage, namespace: current_user.namespace)
end
it { is_expected.to be_allowed(:read_usage) }
end
context 'with a different namespace' do
let(:namespace_monthly_usage) do
create(:ci_namespace_monthly_usage, namespace: create(:user).namespace)
end
it { is_expected.not_to be_allowed(:read_usage) }
end
end
...@@ -6,70 +6,130 @@ RSpec.describe 'Query.ciMinutesUsage' do ...@@ -6,70 +6,130 @@ RSpec.describe 'Query.ciMinutesUsage' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, name: 'Project 1', namespace: user.namespace) } let_it_be(:user_project) { create(:project, name: 'Project 1', namespace: user.namespace) }
let_it_be_with_refind(:group) { create(:group, :public, name: 'test') }
before(:all) do before(:all) do
create(:ci_namespace_monthly_usage, namespace: user.namespace, amount_used: 50, date: Date.new(2021, 5, 1)) create(:ci_namespace_monthly_usage, namespace: user.namespace, amount_used: 50, date: Date.new(2021, 5, 1))
create(:ci_project_monthly_usage, project: project, amount_used: 50, date: Date.new(2021, 5, 1)) create(:ci_project_monthly_usage, project: user_project, amount_used: 40, date: Date.new(2021, 5, 1))
create(:ci_namespace_monthly_usage, namespace: group, amount_used: 100, date: Date.new(2021, 6, 1))
end end
it 'returns usage data by month for the current user' do subject(:result) { post_graphql(query, current_user: user) }
query = <<-QUERY
{ context 'when no namespace_id is provided' do
ciMinutesUsage { let(:query) do
nodes { <<-QUERY
minutes {
month ciMinutesUsage {
projects { nodes {
nodes { minutes
name month
minutes projects {
nodes {
name
minutes
}
} }
} }
} }
} }
} QUERY
QUERY end
post_graphql(query, current_user: user) it 'returns usage data by month for the current user' do
subject
monthly_usage = graphql_data_at(:ci_minutes_usage, :nodes)
expect(monthly_usage).to contain_exactly({ monthly_usage = graphql_data_at(:ci_minutes_usage, :nodes)
'month' => 'May', expect(monthly_usage).to contain_exactly({
'minutes' => 50, 'month' => 'May',
'projects' => { 'nodes' => [{ 'minutes' => 50,
'name' => 'Project 1', 'projects' => { 'nodes' => [{
'minutes' => 50 'name' => 'Project 1',
}] } 'minutes' => 40
}) }] }
})
end
it 'does not create N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: user)
end
expect(graphql_errors).to be_nil
project_2 = create(:project, name: 'Project 2', namespace: user.namespace)
create(:ci_project_monthly_usage, project: project_2, amount_used: 50, date: Date.new(2021, 5, 1))
expect do
post_graphql(query, current_user: user)
end.not_to exceed_query_limit(control_count)
expect(graphql_errors).to be_nil
end
end end
it 'does not create N+1 queries' do context 'when namespace_id is provided' do
query = <<-QUERY let(:namespace) { group }
{
ciMinutesUsage { let(:query) do
nodes { <<-QUERY
projects { {
nodes { ciMinutesUsage(namespaceId: "#{namespace.to_global_id}") {
name nodes {
} minutes
month
} }
} }
} }
} QUERY
QUERY end
context 'when group is root' do
context 'when user is an owner' do
before do
group.add_owner(user)
end
it 'returns the usage data' do
subject
monthly_usage = graphql_data_at(:ci_minutes_usage, :nodes)
expect(monthly_usage).to contain_exactly({
'month' => 'June',
'minutes' => 100
})
end
end
control_count = ActiveRecord::QueryRecorder.new do context 'when user is not an owner' do
post_graphql(query, current_user: user) before do
group.add_developer(user)
end
it 'does not return usage data' do
subject
monthly_usage = graphql_data_at(:ci_minutes_usage, :nodes)
expect(monthly_usage).to be_empty
end
end
end end
expect(graphql_errors).to be_nil
project_2 = create(:project, name: 'Project 2', namespace: user.namespace) context 'when group is a subgroup' do
create(:ci_project_monthly_usage, project: project_2, amount_used: 50, date: Date.new(2021, 5, 1)) let(:subgroup) { create(:group, :public, parent: group) }
let(:namespace) { subgroup }
before do
create(:ci_namespace_monthly_usage, namespace: subgroup)
subgroup.add_owner(user)
end
expect do it 'does not return usage data' do
post_graphql(query, current_user: user) subject
end.not_to exceed_query_limit(control_count)
expect(graphql_errors).to be_nil monthly_usage = graphql_data_at(:ci_minutes_usage, :nodes)
expect(monthly_usage).to be_empty
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