Commit 682d36e2 authored by Max Woolf's avatar Max Woolf Committed by Markus Koller

Filter by ID for namespace compliance frameworks

Adds an ID parameter to NamespaceType#compliance_frameworks
to filter a namespace's compliance frameworks to a specific
framework

wip
parent e43566c0
......@@ -9499,6 +9499,11 @@ type GeoNode {
"""
first: Int
"""
Global ID of a specific compliance framework to return.
"""
id: ComplianceManagementFrameworkID
"""
Returns the last _n_ elements from the list.
"""
......@@ -9715,7 +9720,7 @@ type Group {
): 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.
"""
complianceFrameworks(
......@@ -15368,7 +15373,7 @@ type Namespace {
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.
"""
complianceFrameworks(
......@@ -15387,6 +15392,11 @@ type Namespace {
"""
first: Int
"""
Global ID of a specific compliance framework to return.
"""
id: ComplianceManagementFrameworkID
"""
Returns the last _n_ elements from the list.
"""
......
......@@ -26177,6 +26177,16 @@
"ofType": 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": {
......@@ -26928,7 +26938,7 @@
},
{
"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": [
{
"name": "after",
......@@ -45542,7 +45552,7 @@
},
{
"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": [
{
"name": "after",
......@@ -45583,6 +45593,16 @@
"ofType": 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": {
......@@ -1579,7 +1579,7 @@ Represents an external issue.
| `board` | Board | A single board of the group |
| `boards` | BoardConnection | Boards of the 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 |
| `containerRepositoriesCount` | Int! | Number of container repositories in the group |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
......@@ -2329,7 +2329,7 @@ Contains statistics about a milestone.
| ----- | ---- | ----------- |
| `actualRepositorySizeLimit` | Float | Size limit for repositories in the 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 |
| `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
......
......@@ -57,8 +57,12 @@ module EE
field :compliance_frameworks,
::Types::ComplianceManagement::ComplianceFrameworkType.connection_type,
null: true,
description: 'Compliance frameworks available to projects in this namespace',
feature_flag: :ff_custom_compliance_frameworks
description: 'Compliance frameworks available to projects in this namespace.',
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
object.additional_purchased_storage_size.megabytes
......@@ -68,12 +72,22 @@ module EE
object.root_storage_size.limit
end
def compliance_frameworks
BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |namespace_ids, loader|
results = ::ComplianceManagement::Framework.with_namespaces(namespace_ids)
def compliance_frameworks(id: nil)
id = ::Types::GlobalIDType[::ComplianceManagement::Framework].coerce_isolated_input(id) unless id.nil?
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(framework.namespace.id) { |xs| xs << framework }
loader.call([ns_id, fw_id]) { |array| array << fw }
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
)
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
let(:group) { create(:group) }
let(:sox_framework) { create(:compliance_framework, namespace: group, name: 'SOX') }
let(:multiple_namespace_query) do
<<~QUERY
query {
......@@ -39,26 +82,33 @@ RSpec.describe 'getting a list of compliance frameworks for a root namespace' do
b: namespace(fullPath: "#{group.full_path}") {
complianceFrameworks { nodes { id name } }
}
c: namespace(fullPath: "#{group.full_path}") {
complianceFrameworks(id: "#{sox_framework.to_global_id}") { nodes { id name } }
}
}
QUERY
end
before do
create(:compliance_framework, namespace: group)
create(:compliance_framework, namespace: group, name: 'GDPR')
group.add_owner(current_user)
end
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
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
it 'responds with the expected list of compliance frameworks' do
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(:b, :complianceFrameworks, :nodes).map { |f| f['name'] }).to contain_exactly('GDPR')
expect(graphql_data_at(:a, :complianceFrameworks, :nodes, :name)).to contain_exactly('Test1', 'Test2')
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
......
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