Skip to content
代码片段 群组 项目
提交 9bc0e5dd 编辑于 作者: Dmytro Biryukov's avatar Dmytro Biryukov 提交者: Matt Kasa
浏览文件

Add GraphQL resolver, query to fetch group environment scopes

上级 44918cf8
No related branches found
No related tags found
无相关合并请求
显示
373 个添加0 个删除
# frozen_string_literal: true
# Groups::EnvironmentsScopesFinder
#
# Arguments:
# group
# params:
# search: string
#
module Groups
class EnvironmentScopesFinder
DEFAULT_ENVIRONMENT_SCOPES_LIMIT = 100
def initialize(group:, params: {})
@group = group
@params = params
end
EnvironmentScope = Struct.new(:name)
def execute
variables = group.variables
variables = by_name(variables)
variables = by_search(variables)
variables = variables.limit(DEFAULT_ENVIRONMENT_SCOPES_LIMIT)
environment_scope_names = variables.environment_scope_names
environment_scope_names.map { |environment_scope| EnvironmentScope.new(environment_scope) }
end
private
attr_reader :group, :params
def by_name(group_variables)
if params[:name].present?
group_variables.by_environment_scope(params[:name])
else
group_variables
end
end
def by_search(group_variables)
if params[:search].present?
group_variables.for_environment_scope_like(params[:search])
else
group_variables
end
end
end
end
# frozen_string_literal: true
module Resolvers
class GroupEnvironmentScopesResolver < BaseResolver
type Types::Ci::GroupEnvironmentScopeType.connection_type, null: true
alias_method :group, :object
argument :name, GraphQL::Types::String,
required: false,
description: 'Name of the environment scope.'
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for environment scope name.'
def resolve(**args)
return unless group.present?
::Groups::EnvironmentScopesFinder.new(group: group, params: args).execute
end
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class GroupEnvironmentScopeConnectionType < GraphQL::Types::Relay::BaseConnection
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class GroupEnvironmentScopeType < BaseObject
graphql_name 'CiGroupEnvironmentScope'
description 'Ci/CD environment scope for a group.'
connection_type_class(Types::Ci::GroupEnvironmentScopeConnectionType)
field :name, GraphQL::Types::String,
null: true,
description: 'Scope name defininig the enviromnments that can use the variable.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
...@@ -83,6 +83,12 @@ class GroupType < NamespaceType ...@@ -83,6 +83,12 @@ class GroupType < NamespaceType
description: 'Merge requests for projects in this group.', description: 'Merge requests for projects in this group.',
resolver: Resolvers::GroupMergeRequestsResolver resolver: Resolvers::GroupMergeRequestsResolver
field :environment_scopes,
Types::Ci::GroupEnvironmentScopeType.connection_type,
description: 'Environment scopes of the group.',
null: true,
resolver: Resolvers::GroupEnvironmentScopesResolver
field :milestones, field :milestones,
description: 'Milestones of the group.', description: 'Milestones of the group.',
extras: [:lookahead], extras: [:lookahead],
......
...@@ -23,6 +23,19 @@ class GroupVariable < Ci::ApplicationRecord ...@@ -23,6 +23,19 @@ class GroupVariable < Ci::ApplicationRecord
scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) } scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) }
scope :for_groups, ->(group_ids) { where(group_id: group_ids) } scope :for_groups, ->(group_ids) { where(group_id: group_ids) }
scope :for_environment_scope_like, -> (query) do
top_level = 'LOWER(ci_group_variables.environment_scope) LIKE LOWER(?) || \'%\''
search_like = "%#{sanitize_sql_like(query)}%"
where(top_level, search_like)
end
scope :environment_scope_names, -> do
group(:environment_scope)
.order(:environment_scope)
.pluck(:environment_scope)
end
self.limit_name = 'group_ci_variables' self.limit_name = 'group_ci_variables'
self.limit_scope = :group self.limit_scope = :group
......
...@@ -7784,6 +7784,29 @@ The edge type for [`CiGroup`](#cigroup). ...@@ -7784,6 +7784,29 @@ The edge type for [`CiGroup`](#cigroup).
| <a id="cigroupedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="cigroupedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cigroupedgenode"></a>`node` | [`CiGroup`](#cigroup) | The item at the end of the edge. | | <a id="cigroupedgenode"></a>`node` | [`CiGroup`](#cigroup) | The item at the end of the edge. |
   
#### `CiGroupEnvironmentScopeConnection`
The connection type for [`CiGroupEnvironmentScope`](#cigroupenvironmentscope).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cigroupenvironmentscopeconnectionedges"></a>`edges` | [`[CiGroupEnvironmentScopeEdge]`](#cigroupenvironmentscopeedge) | A list of edges. |
| <a id="cigroupenvironmentscopeconnectionnodes"></a>`nodes` | [`[CiGroupEnvironmentScope]`](#cigroupenvironmentscope) | A list of nodes. |
| <a id="cigroupenvironmentscopeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiGroupEnvironmentScopeEdge`
The edge type for [`CiGroupEnvironmentScope`](#cigroupenvironmentscope).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cigroupenvironmentscopeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cigroupenvironmentscopeedgenode"></a>`node` | [`CiGroupEnvironmentScope`](#cigroupenvironmentscope) | The item at the end of the edge. |
#### `CiGroupVariableConnection` #### `CiGroupVariableConnection`
   
The connection type for [`CiGroupVariable`](#cigroupvariable). The connection type for [`CiGroupVariable`](#cigroupvariable).
...@@ -12719,6 +12742,16 @@ Represents a deployment freeze window of a project. ...@@ -12719,6 +12742,16 @@ Represents a deployment freeze window of a project.
| <a id="cigroupname"></a>`name` | [`String`](#string) | Name of the job group. | | <a id="cigroupname"></a>`name` | [`String`](#string) | Name of the job group. |
| <a id="cigroupsize"></a>`size` | [`Int`](#int) | Size of the group. | | <a id="cigroupsize"></a>`size` | [`Int`](#int) | Size of the group. |
   
### `CiGroupEnvironmentScope`
Ci/CD environment scope for a group.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cigroupenvironmentscopename"></a>`name` | [`String`](#string) | Scope name defininig the enviromnments that can use the variable. |
### `CiGroupVariable` ### `CiGroupVariable`
   
CI/CD variables for a group. CI/CD variables for a group.
...@@ -15866,6 +15899,23 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -15866,6 +15899,23 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupdescendantgroupsowned"></a>`owned` | [`Boolean`](#boolean) | Limit result to groups owned by authenticated user. | | <a id="groupdescendantgroupsowned"></a>`owned` | [`Boolean`](#boolean) | Limit result to groups owned by authenticated user. |
| <a id="groupdescendantgroupssearch"></a>`search` | [`String`](#string) | Search query for group name or group full path. | | <a id="groupdescendantgroupssearch"></a>`search` | [`String`](#string) | Search query for group name or group full path. |
   
##### `Group.environmentScopes`
Environment scopes of the group.
Returns [`CiGroupEnvironmentScopeConnection`](#cigroupenvironmentscopeconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupenvironmentscopesname"></a>`name` | [`String`](#string) | Name of the environment scope. |
| <a id="groupenvironmentscopessearch"></a>`search` | [`String`](#string) | Search query for environment scope name. |
##### `Group.epic` ##### `Group.epic`
   
Find a single epic. Find a single epic.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::EnvironmentScopesFinder, feature_category: :secrets_management do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let!(:environment1) { create(:ci_group_variable, group: group, key: 'var1', environment_scope: 'environment1') }
let!(:environment2) { create(:ci_group_variable, group: group, key: 'var2', environment_scope: 'environment2') }
let!(:environment3) { create(:ci_group_variable, group: group, key: 'var2', environment_scope: 'environment3') }
let(:finder) { described_class.new(group: group, params: params) }
subject { finder.execute }
context 'with default no arguments' do
let(:params) { {} }
it do
expected_result = group.variables.environment_scope_names
expect(subject.map(&:name))
.to match_array(expected_result)
end
end
context 'with search' do
let(:params) { { search: 'ment1' } }
it do
expected_result = ['environment1']
expect(subject.map(&:name))
.to match_array(expected_result)
end
end
context 'with specific name' do
let(:params) { { name: 'environment3' } }
it do
expect(subject.map(&:name))
.to match_array([environment3.environment_scope])
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::GroupEnvironmentScopesResolver, feature_category: :secrets_management do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let(:group) { create(:group) }
context "with a group" do
let(:expected_environment_scopes) do
%w[environment1 environment2 environment3 environment4 environment5 environment6]
end
before do
group.add_developer(current_user)
expected_environment_scopes.each_with_index do |env, index|
create(:ci_group_variable, group: group, key: "var#{index + 1}", environment_scope: env)
end
end
describe '#resolve' do
it 'finds all environment scopes' do
expect(resolve_environment_scopes.map(&:name)).to match_array(
expected_environment_scopes
)
end
end
end
context 'without a group' do
describe '#resolve' do
it 'rails to find any environment scopes' do
expect(resolve_environment_scopes.map(&:name)).to match_array(
[]
)
end
end
end
def resolve_environment_scopes(args = {}, context = { current_user: current_user })
resolve(described_class, obj: group, args: args, ctx: context)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['CiGroupEnvironmentScope'], feature_category: :secrets_management do
specify do
expect(described_class).to have_graphql_fields(
:name
).at_least
end
end
...@@ -54,6 +54,36 @@ ...@@ -54,6 +54,36 @@
it { expect(described_class.for_groups([group.id])).to eq([group_variable]) } it { expect(described_class.for_groups([group.id])).to eq([group_variable]) }
end end
describe '.for_environment_scope_like' do
let_it_be(:group) { create(:group) }
let_it_be(:variable1_on_staging1) { create(:ci_group_variable, group: group, environment_scope: 'staging1') }
let_it_be(:variable2_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
let_it_be(:variable3_on_production) { create(:ci_group_variable, group: group, environment_scope: 'production') }
it {
expect(described_class.for_environment_scope_like('staging'))
.to match_array([variable1_on_staging1, variable2_on_staging2])
}
it {
expect(described_class.for_environment_scope_like('production'))
.to match_array([variable3_on_production])
}
end
describe '.environment_scope_names' do
let_it_be(:group) { create(:group) }
let_it_be(:variable1_on_staging1) { create(:ci_group_variable, group: group, environment_scope: 'staging1') }
let_it_be(:variable2_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
let_it_be(:variable3_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
let_it_be(:variable4_on_production) { create(:ci_group_variable, group: group, environment_scope: 'production') }
it 'groups and orders' do
expect(described_class.environment_scope_names)
.to match_array(%w[production staging1 staging2])
end
end
it_behaves_like 'cleanup by a loose foreign key' do it_behaves_like 'cleanup by a loose foreign key' do
let!(:model) { create(:ci_group_variable) } let!(:model) { create(:ci_group_variable) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.group(fullPath).environmentScopes', feature_category: :secrets_management do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let(:expected_environment_scopes) do
%w[
group1_environment1
group1_environment2
group2_environment3
group2_environment4
group2_environment5
group2_environment6
]
end
let(:query) do
%(
query {
group(fullPath: "#{group.full_path}") {
environmentScopes#{environment_scopes_params} {
nodes {
name
}
}
}
}
)
end
before do
group.add_developer(user)
expected_environment_scopes.each_with_index do |env, index|
create(:ci_group_variable, group: group, key: "var#{index + 1}", environment_scope: env)
end
end
context 'when query has no parameters' do
let(:environment_scopes_params) { "" }
it 'returns all avaiable environment scopes' do
post_graphql(query, current_user: user)
expect(graphql_data.dig('group', 'environmentScopes', 'nodes')).to eq(
expected_environment_scopes.map { |env_scope| { 'name' => env_scope } }
)
end
end
context 'when query has search parameters' do
let(:environment_scopes_params) { "(search: \"group1\")" }
it 'returns only environment scopes with group1 prefix' do
post_graphql(query, current_user: user)
expect(graphql_data.dig('group', 'environmentScopes', 'nodes')).to eq(
[
{ 'name' => 'group1_environment1' },
{ 'name' => 'group1_environment2' }
]
)
end
end
end
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
# FooFinder # Reason: It uses a memory backend # FooFinder # Reason: It uses a memory backend
- Namespaces::BilledUsersFinder # Reason: There is no need to have anything else besides the ids is current structure - Namespaces::BilledUsersFinder # Reason: There is no need to have anything else besides the ids is current structure
- Namespaces::FreeUserCap::UsersFinder # Reason: There is no need to have anything else besides the count - Namespaces::FreeUserCap::UsersFinder # Reason: There is no need to have anything else besides the count
- Groups::EnvironmentScopesFinder # Reason: There is no need to have anything else besides the simple strucutre with the scope name
# Temporary excludes (aka TODOs) # Temporary excludes (aka TODOs)
# For example: # For example:
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册