Commit ab3740be authored by Markus Koller's avatar Markus Koller

Merge branch '289846-graphql-namespace-frameworks-filter-by-id' into 'master'

Filter by ID for namespace compliance frameworks

See merge request gitlab-org/gitlab!49108
parents ab1a156c 682d36e2
...@@ -9499,6 +9499,11 @@ type GeoNode { ...@@ -9499,6 +9499,11 @@ type GeoNode {
""" """
first: Int first: Int
"""
Global ID of a specific compliance framework to return.
"""
id: ComplianceManagementFrameworkID
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
...@@ -9715,7 +9720,7 @@ type Group { ...@@ -9715,7 +9720,7 @@ type Group {
): CodeCoverageActivityConnection ): CodeCoverageActivityConnection
""" """
Compliance frameworks available to projects in this namespace Available only Compliance frameworks available to projects in this namespace. Available only
when feature flag `ff_custom_compliance_frameworks` is enabled. when feature flag `ff_custom_compliance_frameworks` is enabled.
""" """
complianceFrameworks( complianceFrameworks(
...@@ -15368,7 +15373,7 @@ type Namespace { ...@@ -15368,7 +15373,7 @@ type Namespace {
additionalPurchasedStorageSize: Float additionalPurchasedStorageSize: Float
""" """
Compliance frameworks available to projects in this namespace Available only Compliance frameworks available to projects in this namespace. Available only
when feature flag `ff_custom_compliance_frameworks` is enabled. when feature flag `ff_custom_compliance_frameworks` is enabled.
""" """
complianceFrameworks( complianceFrameworks(
...@@ -15387,6 +15392,11 @@ type Namespace { ...@@ -15387,6 +15392,11 @@ type Namespace {
""" """
first: Int first: Int
"""
Global ID of a specific compliance framework to return.
"""
id: ComplianceManagementFrameworkID
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
......
...@@ -26177,6 +26177,16 @@ ...@@ -26177,6 +26177,16 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "id",
"description": "Global ID of a specific compliance framework to return.",
"type": {
"kind": "SCALAR",
"name": "ComplianceManagementFrameworkID",
"ofType": null
},
"defaultValue": null
} }
], ],
"type": { "type": {
...@@ -26928,7 +26938,7 @@ ...@@ -26928,7 +26938,7 @@
}, },
{ {
"name": "complianceFrameworks", "name": "complianceFrameworks",
"description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.", "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
"args": [ "args": [
{ {
"name": "after", "name": "after",
...@@ -45542,7 +45552,7 @@ ...@@ -45542,7 +45552,7 @@
}, },
{ {
"name": "complianceFrameworks", "name": "complianceFrameworks",
"description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.", "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
"args": [ "args": [
{ {
"name": "after", "name": "after",
...@@ -45583,6 +45593,16 @@ ...@@ -45583,6 +45593,16 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "id",
"description": "Global ID of a specific compliance framework to return.",
"type": {
"kind": "SCALAR",
"name": "ComplianceManagementFrameworkID",
"ofType": null
},
"defaultValue": null
} }
], ],
"type": { "type": {
...@@ -1579,7 +1579,7 @@ Represents an external issue. ...@@ -1579,7 +1579,7 @@ Represents an external issue.
| `board` | Board | A single board of the group | | `board` | Board | A single board of the group |
| `boards` | BoardConnection | Boards of the group | | `boards` | BoardConnection | Boards of the group |
| `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group | | `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group |
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. | | `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
| `containerRepositories` | ContainerRepositoryConnection | Container repositories of the group | | `containerRepositories` | ContainerRepositoryConnection | Container repositories of the group |
| `containerRepositoriesCount` | Int! | Number of container repositories in the group | | `containerRepositoriesCount` | Int! | Number of container repositories in the group |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit | | `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
...@@ -2329,7 +2329,7 @@ Contains statistics about a milestone. ...@@ -2329,7 +2329,7 @@ Contains statistics about a milestone.
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `actualRepositorySizeLimit` | Float | Size limit for repositories in the namespace in bytes | | `actualRepositorySizeLimit` | Float | Size limit for repositories in the namespace in bytes |
| `additionalPurchasedStorageSize` | Float | Additional storage purchased for the root namespace in bytes | | `additionalPurchasedStorageSize` | Float | Additional storage purchased for the root namespace in bytes |
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. | | `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit | | `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
| `description` | String | Description of the namespace | | `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
......
...@@ -57,8 +57,12 @@ module EE ...@@ -57,8 +57,12 @@ module EE
field :compliance_frameworks, field :compliance_frameworks,
::Types::ComplianceManagement::ComplianceFrameworkType.connection_type, ::Types::ComplianceManagement::ComplianceFrameworkType.connection_type,
null: true, null: true,
description: 'Compliance frameworks available to projects in this namespace', description: 'Compliance frameworks available to projects in this namespace.',
feature_flag: :ff_custom_compliance_frameworks feature_flag: :ff_custom_compliance_frameworks do
argument :id, ::Types::GlobalIDType[::ComplianceManagement::Framework],
description: 'Global ID of a specific compliance framework to return.',
required: false
end
def additional_purchased_storage_size def additional_purchased_storage_size
object.additional_purchased_storage_size.megabytes object.additional_purchased_storage_size.megabytes
...@@ -68,12 +72,22 @@ module EE ...@@ -68,12 +72,22 @@ module EE
object.root_storage_size.limit object.root_storage_size.limit
end end
def compliance_frameworks def compliance_frameworks(id: nil)
BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |namespace_ids, loader| id = ::Types::GlobalIDType[::ComplianceManagement::Framework].coerce_isolated_input(id) unless id.nil?
results = ::ComplianceManagement::Framework.with_namespaces(namespace_ids) BatchLoader::GraphQL
.for([object.id, id&.model_id])
.batch(default_value: []) do |keys, loader|
namespace_ids = keys.map(&:first).uniq
by_namespace_id = keys.group_by(&:first).transform_values { |k| k.map(&:second) }
frameworks = ::ComplianceManagement::Framework.with_namespaces(namespace_ids)
frameworks.group_by(&:namespace_id).each do |ns_id, group|
by_namespace_id[ns_id].each do |fw_id|
group.each do |fw|
next unless fw_id.nil? || fw_id.to_i == fw.id
results.each do |framework| loader.call([ns_id, fw_id]) { |array| array << fw }
loader.call(framework.namespace.id) { |xs| xs << framework } end
end
end end
end end
end end
......
---
title: Add ID filter to Namespace -> ComplianceFramework GraphQL
merge_request: 49108
author:
type: added
...@@ -28,8 +28,51 @@ RSpec.describe 'getting a list of compliance frameworks for a root namespace' do ...@@ -28,8 +28,51 @@ RSpec.describe 'getting a list of compliance frameworks for a root namespace' do
) )
end end
context 'when querying a specific framework ID' do
let(:query) do
graphql_query_for(
:namespace, { full_path: namespace.full_path }, query_nodes(:compliance_frameworks, nil, args: { id: global_id_of(compliance_framework_1) })
)
end
it 'returns only a single compliance framework' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(:namespace, :complianceFrameworks, :nodes).map { |n| n['id'] }).to contain_exactly(global_id_of(compliance_framework_1))
end
end
context 'when querying an invalid object ID' do
let(:query) do
graphql_query_for(
:namespace, { full_path: namespace.full_path }, query_nodes(:compliance_frameworks, nil, args: { id: global_id_of(namespace) })
)
end
it 'returns an error message' do
post_graphql(query, current_user: current_user)
expect(graphql_errors).to contain_exactly(include('message' => "\"#{global_id_of(namespace)}\" does not represent an instance of ComplianceManagement::Framework"))
end
end
context 'when querying a specific framework that current_user has no access to' do
let(:query) do
graphql_query_for(
:namespace, { full_path: namespace.full_path }, query_nodes(:compliance_frameworks, nil, args: { id: global_id_of(create(:compliance_framework)) })
)
end
it 'does not return the framework' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(:namespace, :complianceFrameworks, :nodes)).to be_empty
end
end
context 'when querying multiple namespaces' do context 'when querying multiple namespaces' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:sox_framework) { create(:compliance_framework, namespace: group, name: 'SOX') }
let(:multiple_namespace_query) do let(:multiple_namespace_query) do
<<~QUERY <<~QUERY
query { query {
...@@ -39,26 +82,33 @@ RSpec.describe 'getting a list of compliance frameworks for a root namespace' do ...@@ -39,26 +82,33 @@ RSpec.describe 'getting a list of compliance frameworks for a root namespace' do
b: namespace(fullPath: "#{group.full_path}") { b: namespace(fullPath: "#{group.full_path}") {
complianceFrameworks { nodes { id name } } complianceFrameworks { nodes { id name } }
} }
c: namespace(fullPath: "#{group.full_path}") {
complianceFrameworks(id: "#{sox_framework.to_global_id}") { nodes { id name } }
}
} }
QUERY QUERY
end end
before do before do
create(:compliance_framework, namespace: group) create(:compliance_framework, namespace: group, name: 'GDPR')
group.add_owner(current_user) group.add_owner(current_user)
end end
it 'avoids N+1 queries' do it 'avoids N+1 queries' do
post_graphql(query, current_user: current_user)
post_graphql(multiple_namespace_query, current_user: current_user)
query_count = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }.count query_count = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }.count
expect { post_graphql(multiple_namespace_query, current_user: current_user) }.not_to exceed_query_limit(query_count + 4) expect { post_graphql(multiple_namespace_query, current_user: current_user) }.not_to exceed_query_limit(query_count + 2)
end end
it 'responds with the expected list of compliance frameworks' do it 'responds with the expected list of compliance frameworks' do
post_graphql(multiple_namespace_query, current_user: current_user) post_graphql(multiple_namespace_query, current_user: current_user)
expect(graphql_data_at(:a, :complianceFrameworks, :nodes).map { |f| f['name'] }).to contain_exactly('Test1', 'Test2') expect(graphql_data_at(:a, :complianceFrameworks, :nodes, :name)).to contain_exactly('Test1', 'Test2')
expect(graphql_data_at(:b, :complianceFrameworks, :nodes).map { |f| f['name'] }).to contain_exactly('GDPR') expect(graphql_data_at(:b, :complianceFrameworks, :nodes, :name)).to contain_exactly('GDPR', 'SOX')
expect(graphql_data_at(:c, :complianceFrameworks, :nodes, :name)).to contain_exactly('SOX')
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