diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 634b8a6c2d72d0c477d5eea8e6ec13d69055e99d..67d7ed19f8a0b77fbbcf3a8e41b2ad9b1df55400 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -3139,6 +3139,21 @@ enum CommitEncoding { Represents a ComplianceFramework associated with a Project """ type ComplianceFramework { + """ + Hexadecimal representation of compliance framework's label color + """ + color: String! + + """ + Description of the compliance framework + """ + description: String! + + """ + Compliance framework ID + """ + id: ID! + """ Name of the compliance framework """ @@ -8818,6 +8833,32 @@ type Group { startDate: Date! ): CodeCoverageActivityConnection + """ + Compliance frameworks available to projects in this namespace. Available only + when feature flag `ff_custom_compliance_frameworks` is enabled + """ + complianceFrameworks( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ComplianceFrameworkConnection + """ Container repositories of the group """ @@ -14182,6 +14223,32 @@ type Namespace { """ additionalPurchasedStorageSize: Float + """ + Compliance frameworks available to projects in this namespace. Available only + when feature flag `ff_custom_compliance_frameworks` is enabled + """ + complianceFrameworks( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ComplianceFrameworkConnection + """ Includes at least one project where the repository size exceeds the limit """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index d95bf9024968fba54d40e2f8d947da7a138aced1..5def7d20e585bf9f4135dca8cc02185690d1da02 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -8529,6 +8529,60 @@ "name": "ComplianceFramework", "description": "Represents a ComplianceFramework associated with a Project", "fields": [ + { + "name": "color", + "description": "Hexadecimal representation of compliance framework's label color", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": "Description of the compliance framework", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "Compliance framework ID", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "name", "description": "Name of the compliance framework", @@ -24512,6 +24566,59 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "complianceFrameworks", + "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled", + "args": [ + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ComplianceFrameworkConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "containerRepositories", "description": "Container repositories of the group", @@ -42200,6 +42307,59 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "complianceFrameworks", + "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled", + "args": [ + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ComplianceFrameworkConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "containsLockedProjects", "description": "Includes at least one project where the repository size exceeds the limit", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e00696fdc122b6b21516378987695f0f07fe14cb..c2d03d31919c7facced67f846f025c8fd16d49c9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -507,6 +507,9 @@ Represents a ComplianceFramework associated with a Project. | Field | Type | Description | | ----- | ---- | ----------- | +| `color` | String! | Hexadecimal representation of compliance framework's label color | +| `description` | String! | Description of the compliance framework | +| `id` | ID! | Compliance framework ID | | `name` | String! | Name of the compliance framework | ### ConfigureSastPayload @@ -1459,6 +1462,7 @@ Autogenerated return type of EpicTreeReorder. | `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 | | `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 | @@ -2183,6 +2187,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 | | `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` | diff --git a/ee/app/graphql/ee/types/namespace_type.rb b/ee/app/graphql/ee/types/namespace_type.rb index 22091e5928f3069d8af02beae987d671d170fc42..d2156496cc3a8dfee8848dc0fd2dc2c130d79e2a 100644 --- a/ee/app/graphql/ee/types/namespace_type.rb +++ b/ee/app/graphql/ee/types/namespace_type.rb @@ -54,6 +54,12 @@ module EE null: true, description: 'Date until the temporary storage increase is active' + 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 + def additional_purchased_storage_size object.additional_purchased_storage_size.megabytes end @@ -61,6 +67,16 @@ module EE def storage_size_limit 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) + + results.each do |framework| + loader.call(framework.namespace.id) { |xs| xs << framework } + end + end + end end end end diff --git a/ee/app/graphql/ee/types/project_type.rb b/ee/app/graphql/ee/types/project_type.rb index a11e322a6b116cc7269373838c507b9baf6740c6..16a4e2f0fcbfb3dba17fd2b2d126ff9ed420038e 100644 --- a/ee/app/graphql/ee/types/project_type.rb +++ b/ee/app/graphql/ee/types/project_type.rb @@ -55,7 +55,6 @@ module EE field :compliance_frameworks, ::Types::ComplianceManagement::ComplianceFrameworkType.connection_type, description: 'Compliance frameworks associated with the project', - resolver: ::Resolvers::ComplianceFrameworksResolver, null: true field :security_dashboard_path, GraphQL::STRING_TYPE, @@ -141,6 +140,18 @@ module EE def security_dashboard_path Rails.application.routes.url_helpers.project_security_dashboard_index_path(object) end + + def compliance_frameworks + BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |project_ids, loader| + results = ::ComplianceManagement::Framework.with_projects(project_ids) + + results.each do |framework| + framework.project_ids.each do |project_id| + loader.call(project_id) { |xs| xs << framework } + end + end + end + end end end end diff --git a/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb b/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb deleted file mode 100644 index 3adc28f6091e69a2b7e73bf185ab8077b0ffc324..0000000000000000000000000000000000000000 --- a/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - class ComplianceFrameworksResolver < BaseResolver - type Types::ComplianceManagement::ComplianceFrameworkType, null: true - - alias_method :project, :object - - def resolve(**args) - Array.wrap(project.compliance_framework_setting) - end - end -end diff --git a/ee/app/graphql/types/compliance_management/compliance_framework_type.rb b/ee/app/graphql/types/compliance_management/compliance_framework_type.rb index d151ef218ee588478f02f53bfd62c53cb1bae274..737ac70a07d6eb9afd7facf0d2a9447b34f6f26d 100644 --- a/ee/app/graphql/types/compliance_management/compliance_framework_type.rb +++ b/ee/app/graphql/types/compliance_management/compliance_framework_type.rb @@ -7,13 +7,21 @@ module Types graphql_name 'ComplianceFramework' description 'Represents a ComplianceFramework associated with a Project' + field :id, GraphQL::ID_TYPE, + null: false, + description: 'Compliance framework ID' + field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the compliance framework' - def name - object.compliance_management_framework.name - end + field :description, GraphQL::STRING_TYPE, + null: false, + description: 'Description of the compliance framework' + + field :color, GraphQL::STRING_TYPE, + null: false, + description: 'Hexadecimal representation of compliance framework\'s label color' end end end diff --git a/ee/app/models/compliance_management/framework.rb b/ee/app/models/compliance_management/framework.rb index b526711cd4ddaa4cd81c1ddf7719acf599436471..c44208b7cc4d5dc7d9eaa924037d2d4407afea00 100644 --- a/ee/app/models/compliance_management/framework.rb +++ b/ee/app/models/compliance_management/framework.rb @@ -59,6 +59,8 @@ module ComplianceManagement strip_attributes :name, :color belongs_to :namespace + has_many :project_settings, class_name: 'ComplianceManagement::ComplianceFramework::ProjectSettings' + has_many :projects, through: :project_settings validates :namespace, presence: true validates :name, presence: true, length: { maximum: 255 } @@ -67,6 +69,9 @@ module ComplianceManagement validates :regulated, presence: true validates :namespace_id, uniqueness: { scope: :name } + scope :with_projects, ->(project_ids) { includes(:projects).where(projects: { id: project_ids }) } + scope :with_namespaces, ->(namespace_ids) { includes(:namespace).where(namespaces: { id: namespace_ids })} + def default_framework_definition strong_memoize(:default_framework_definition) do DEFAULT_FRAMEWORKS.find { |framework| framework.name.eql?(name) } diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index 08e42cbdf1d5966ec40b08a8d08d8ffd58339f18..476e5b0f667f9373ede97755276e2a5937d82791 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -43,6 +43,7 @@ module EE has_one :status_page_setting, inverse_of: :project, class_name: 'StatusPage::ProjectSetting' has_one :compliance_framework_setting, class_name: 'ComplianceManagement::ComplianceFramework::ProjectSettings', inverse_of: :project + has_many :compliance_management_frameworks, through: :compliance_framework_setting, source: 'compliance_management_framework' has_one :security_setting, class_name: 'ProjectSecuritySetting' has_one :vulnerability_statistic, class_name: 'Vulnerabilities::Statistic' diff --git a/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml b/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml new file mode 100644 index 0000000000000000000000000000000000000000..cf23c38a566964feb8a73d4daeb6b47a1be5d8c2 --- /dev/null +++ b/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml @@ -0,0 +1,5 @@ +--- +title: Add compliance frameworks to namespaces in GraphQL API +merge_request: 47779 +author: +type: added diff --git a/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml b/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml new file mode 100644 index 0000000000000000000000000000000000000000..3cb7aa90b61b15025c754b2167e8dfe5dd3abd04 --- /dev/null +++ b/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml @@ -0,0 +1,8 @@ +--- +name: ff_custom_compliance_frameworks +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47779 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287779 +milestone: '13.7' +type: development +group: group::compliance +default_enabled: false diff --git a/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb b/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb deleted file mode 100644 index 355b7fe130cdaa724a425888c57da19321671c19..0000000000000000000000000000000000000000 --- a/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Resolvers::ComplianceFrameworksResolver do - include GraphqlHelpers - - let(:project) { create(:project) } - - describe '#resolve' do - subject { resolve_compliance_frameworks(project) } - - context 'when a project has a compliance framework set' do - before do - project.update!(compliance_framework_setting: create(:compliance_framework_project_setting, :sox)) - end - - it 'includes the name of the compliance frameworks' do - expect(subject.size).to eq(1) - - framework = subject.first.compliance_management_framework - expect(framework.name).to eq('SOX') - end - end - - context 'when a project has no compliance framework set' do - it 'is an empty array' do - expect(subject).to be_empty - end - end - end - - def resolve_compliance_frameworks(project) - resolve(described_class, obj: project) - end -end diff --git a/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb b/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb index dbbd41477ef0a54a2d92be14c7d0635518ddaf4c..41cdfdafc164cd1ee82798d236ff17cb51109606 100644 --- a/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb +++ b/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb @@ -3,5 +3,16 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ComplianceFramework'] do - it { expect(described_class).to have_graphql_field(:name) } + subject { described_class } + + fields = %w[ + id + name + description + color + ] + + it 'has the correct fields' do + is_expected.to have_graphql_fields(fields) + end end diff --git a/ee/spec/graphql/ee/types/namespace_type_spec.rb b/ee/spec/graphql/ee/types/namespace_type_spec.rb index 2a9fb5732fa6bc29a3ab27c59e7ceade61360ac2..431b52be54c657b7a1a0ea726fdf97321936dfab 100644 --- a/ee/spec/graphql/ee/types/namespace_type_spec.rb +++ b/ee/spec/graphql/ee/types/namespace_type_spec.rb @@ -14,6 +14,7 @@ RSpec.describe GitlabSchema.types['Namespace'] do storage_size_limit is_temporary_storage_increase_enabled temporary_storage_increase_ends_on + compliance_frameworks ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/ee/spec/graphql/types/project_type_spec.rb b/ee/spec/graphql/types/project_type_spec.rb index 4fff5203b93beb45331b7670c82be6e99b85539c..43a964d5229fef0736e94b991ce64e435319bfe1 100644 --- a/ee/spec/graphql/types/project_type_spec.rb +++ b/ee/spec/graphql/types/project_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Project'] do + include GraphqlHelpers + let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:vulnerability) { create(:vulnerability, project: project, severity: :high) } @@ -356,4 +358,29 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::Ci::CodeCoverageSummaryType) } end + + describe 'compliance_frameworks' do + it 'queries in batches' do + projects = create_list(:project, 2, :with_compliance_framework) + + projects.each { |p| p.add_maintainer(user) } + + results = batch_sync(max_queries: 1) do + projects.flat_map do |p| + resolve_field(:compliance_frameworks, p) + end + end + frameworks = results.flat_map(&:items) + + expect(frameworks).to match_array(projects.flat_map(&:compliance_management_frameworks)) + end + end + + private + + def query_for_project(project) + graphql_query_for( + :projects, { ids: [global_id_of(project)] }, "nodes { #{query_nodes(:compliance_frameworks)} }" + ) + end end diff --git a/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..84caa279a1c4dc63b1485f39b8b2d70f81854c4e --- /dev/null +++ b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting a list of compliance frameworks for a root namespace' do + include GraphqlHelpers + + let_it_be(:namespace) { create(:namespace) } + let_it_be(:compliance_framework_1) { create(:compliance_framework, namespace: namespace, name: 'Test1') } + let_it_be(:compliance_framework_2) { create(:compliance_framework, namespace: namespace, name: 'Test2') } + let(:path) { %i[namespace compliance_frameworks nodes] } + + let!(:query) do + graphql_query_for( + :namespace, { full_path: namespace.full_path }, query_nodes(:compliance_frameworks) + ) + end + + context 'when authenticated as the namespace owner' do + let(:current_user) { namespace.owner } + + it 'returns the groups compliance frameworks' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path)).to contain_exactly( + a_hash_including('id' => global_id_of(compliance_framework_1)), + a_hash_including('id' => global_id_of(compliance_framework_2)) + ) + end + + context 'when querying multiple namespaces' do + let(:group) { create(:group) } + let(:multiple_namespace_query) do + <<~QUERY + query { + a: namespace(fullPath: "#{namespace.full_path}") { + complianceFrameworks { nodes { id name } } + } + b: namespace(fullPath: "#{group.full_path}") { + complianceFrameworks { nodes { id name } } + } + } + QUERY + end + + before do + create(:compliance_framework, namespace: group) + group.add_owner(current_user) + end + + it 'avoids N+1 queries' do + 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) + 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') + end + end + + context 'feature is disabled' do + before do + stub_feature_flags(ff_custom_compliance_frameworks: false) + end + + it 'responds with error when querying a compliance framework' do + post_graphql(query, current_user: current_user) + + expect(graphql_errors).to contain_exactly(include('message' => "Field 'complianceFrameworks' doesn't exist on type 'Namespace'")) + end + end + end + + context 'when authenticated as a different user' do + let(:current_user) { build(:user) } + + it "does not return the namespaces compliance frameworks" do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path)).to be_nil + end + end + + context 'when not authenticated' do + it "does not return the namespace's compliance frameworks" do + post_graphql(query) + + expect(graphql_data_at(*path)).to be_nil + end + end +end diff --git a/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb b/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4cc1f3da4249e311ea153f77298f737d825717f7 --- /dev/null +++ b/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting compliance frameworks for a collection of projects' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project_members) { create_list(:project_member, 2, :maintainer, user: current_user) } + let_it_be(:project_ids) { project_members.map { |p| global_id_of(p.source) } } + let(:query) do + graphql_query_for( + :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }" + ) + end + + before_all do + project_members.map(&:project).each do |project| + project.compliance_framework_setting = create(:compliance_framework_project_setting) + end + end + + context 'querying a single project' do + let(:single_project_query) do + graphql_query_for( + :projects, { ids: [project_ids.first] }, "nodes { #{query_nodes(:compliance_frameworks)} }" + ) + end + + it 'avoids N+1 queries', :use_sql_query_cache do + query_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, current_user: current_user) }.count + + expect { post_graphql(single_project_query, current_user: current_user) }.not_to exceed_all_query_limit(query_count) + end + + it 'contains the expected compliance framework' do + post_graphql(single_project_query, current_user: current_user) + + expect(graphql_data_at(:projects, :nodes, 0, :complianceFrameworks, :nodes, 0, :name)).to eq 'GDPR' + end + end + + context 'projects can have a compliance framework' do + let_it_be(:compliance_projects) { create_list(:project, 2, :with_compliance_framework) } + let_it_be(:non_compliance_project) { create(:project) } + let(:projects) { compliance_projects + [non_compliance_project] } + let(:project_ids) { projects.map { |p| global_id_of(p) } } + + let(:query) do + graphql_query_for( + :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }" + ) + end + + before do + projects.each { |p| create(:project_member, :maintainer, source: p, user: current_user)} + post_graphql(query, current_user: current_user) + end + + subject { graphql_data_at(:projects, :nodes).map { |p| p.dig('complianceFrameworks', 'nodes') } } + + it 'contains the correct number of compliance frameworks' do + expect(subject[0].size).to eq 0 + expect(subject[1].size).to eq 1 + expect(subject[2].size).to eq 1 + end + end + + context 'projects that share the same compliance framework' do + let_it_be(:framework) { create(:compliance_framework) } + let_it_be(:project_1) { create(:project, compliance_framework_setting: create(:compliance_framework_project_setting, compliance_management_framework: framework )) } + let_it_be(:project_2) { create(:project, compliance_framework_setting: create(:compliance_framework_project_setting, compliance_management_framework: framework )) } + let(:projects) { [project_1, project_2] } + let(:project_ids) { projects.map { |p| global_id_of(p) } } + let(:query) do + graphql_query_for( + :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }" + ) + end + + before do + projects.each { |p| create(:project_member, :maintainer, source: p, user: current_user)} + post_graphql(query, current_user: current_user) + end + + subject { graphql_data_at(:projects, :nodes).map { |p| p.dig('complianceFrameworks', 'nodes', 0, 'id') } } + + it 'shares the same compliance framework id' do + expect(subject[0]).to eq(subject[1]) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 412f45739b12413ea6ac4013e1bc3a7728803950..6b783be12bf31e6bf01abd2b99bece19cc0c9bb9 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -542,6 +542,7 @@ project: - daily_build_group_report_results - jira_imports - compliance_framework_setting +- compliance_management_frameworks - metrics_users_starred_dashboards - alert_management_alerts - repository_storage_moves