Commit 2bd0ab09 authored by charlie ablett's avatar charlie ablett

Merge branch '214583-add-ability-to-query-security-dashboard-projects' into 'master'

Add query for projects selected for instance security dashboard

See merge request gitlab-org/gitlab!30064
parents 55a1c6ab 9f67451c
...@@ -4319,6 +4319,33 @@ enum HealthStatus { ...@@ -4319,6 +4319,33 @@ enum HealthStatus {
onTrack onTrack
} }
type InstanceSecurityDashboard {
"""
Projects selected in Instance Security Dashboard
"""
projects(
"""
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
): ProjectConnection!
}
""" """
State of a GitLab issue or merge request State of a GitLab issue or merge request
""" """
...@@ -8021,6 +8048,11 @@ type Query { ...@@ -8021,6 +8048,11 @@ type Query {
fullPath: ID! fullPath: ID!
): Group ): Group
"""
Fields related to Instance Security Dashboard
"""
instanceSecurityDashboard: InstanceSecurityDashboard
""" """
Metadata about GitLab Metadata about GitLab
""" """
......
...@@ -12025,6 +12025,76 @@ ...@@ -12025,6 +12025,76 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "InstanceSecurityDashboard",
"description": null,
"fields": [
{
"name": "projects",
"description": "Projects selected in Instance Security Dashboard",
"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": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ProjectConnection",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Int",
...@@ -23715,6 +23785,20 @@ ...@@ -23715,6 +23785,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "instanceSecurityDashboard",
"description": "Fields related to Instance Security Dashboard",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "InstanceSecurityDashboard",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "metadata", "name": "metadata",
"description": "Metadata about GitLab", "description": "Metadata about GitLab",
......
...@@ -18,7 +18,7 @@ module EE ...@@ -18,7 +18,7 @@ module EE
mount_mutation ::Mutations::Requirements::Update mount_mutation ::Mutations::Requirements::Update
mount_mutation ::Mutations::Vulnerabilities::Dismiss mount_mutation ::Mutations::Vulnerabilities::Dismiss
mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
mount_mutation ::Mutations::SecurityDashboard::AddProject mount_mutation ::Mutations::InstanceSecurityDashboard::AddProject
end end
end end
end end
......
...@@ -24,6 +24,11 @@ module EE ...@@ -24,6 +24,11 @@ module EE
resolver: Resolvers::Geo::GeoNodeResolver, resolver: Resolvers::Geo::GeoNodeResolver,
description: 'Find a Geo node' description: 'Find a Geo node'
field :instance_security_dashboard, ::Types::InstanceSecurityDashboardType,
null: true,
resolver: Resolvers::InstanceSecurityDashboardResolver,
description: 'Fields related to Instance Security Dashboard'
def design_management def design_management
DesignManagementObject.new(nil) DesignManagementObject.new(nil)
end end
......
# frozen_string_literal: true # frozen_string_literal: true
module Mutations module Mutations
module SecurityDashboard module InstanceSecurityDashboard
class AddProject < BaseMutation class AddProject < BaseMutation
graphql_name 'AddProjectToSecurityDashboard' graphql_name 'AddProjectToSecurityDashboard'
......
# frozen_string_literal: true
module Resolvers
module InstanceSecurityDashboard
class ProjectsResolver < BaseResolver
type ::Types::ProjectType, null: true
alias_method :dashboard, :object
def resolve(**args)
dashboard&.projects
end
end
end
end
# frozen_string_literal: true
module Resolvers
class InstanceSecurityDashboardResolver < BaseResolver
type ::Types::InstanceSecurityDashboardType, null: true
def resolve(**args)
::InstanceSecurityDashboard.new(current_user)
end
end
end
...@@ -38,7 +38,7 @@ module Resolvers ...@@ -38,7 +38,7 @@ module Resolvers
strong_memoize(:vulnerable) do strong_memoize(:vulnerable) do
if resolve_vulnerabilities_for_instance_security_dashboard? if resolve_vulnerabilities_for_instance_security_dashboard?
InstanceSecurityDashboard.new(current_user) ::InstanceSecurityDashboard.new(current_user)
elsif object.respond_to?(:sync) elsif object.respond_to?(:sync)
object.sync object.sync
else else
......
# frozen_string_literal: true
module Types
class InstanceSecurityDashboardType < BaseObject
graphql_name 'InstanceSecurityDashboard'
authorize :read_instance_security_dashboard
field :projects,
Types::ProjectType.connection_type,
null: false,
authorize: :read_project,
description: 'Projects selected in Instance Security Dashboard'
end
end
---
title: Add GraphQL query for Instance Security Dashboard projects
merge_request: 30064
author:
type: added
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Mutations::SecurityDashboard::AddProject do describe Mutations::InstanceSecurityDashboard::AddProject do
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
describe '#resolve' do describe '#resolve' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::InstanceSecurityDashboard::ProjectsResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class, obj: object, ctx: { current_user: user }) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
context 'when provided object is InstanceSecurityDashboard' do
let(:object) { InstanceSecurityDashboard.new(user) }
it { is_expected.to eq(object.projects) }
end
context 'when object is not provided' do
let(:object) { nil }
it { is_expected.to be_nil }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::InstanceSecurityDashboardResolver do
include GraphqlHelpers
describe '#resolve' do
subject(:instance_security_dashboard) { resolve(described_class, ctx: { current_user: current_user }) }
let_it_be(:current_user) { create(:user) }
it { is_expected.to be_a(InstanceSecurityDashboard) }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['InstanceSecurityDashboard'] do
let_it_be(:project) { create(:project) }
let_it_be(:other_project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let(:fields) do
%i[projects]
end
before do
project.add_developer(user)
other_project.add_developer(user)
stub_licensed_features(security_dashboard: true)
end
let(:result) { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
specify { expect(described_class).to have_graphql_fields(fields) }
describe 'projects' do
let(:query) do
%(
query {
instanceSecurityDashboard {
projects {
nodes {
id
}
}
}
}
)
end
subject(:projects) { result.dig('data', 'instanceSecurityDashboard', 'projects') }
context 'when user is not logged in' do
let(:current_user) { nil }
it { is_expected.to be_nil }
end
context 'when user is logged in' do
let(:current_user) { user }
it 'is a list of projects configured for instance security dashboard' do
project_ids = projects['nodes'].pluck('id')
expect(project_ids).to eq [GitlabSchema.id_from_object(project).to_s]
end
end
end
end
...@@ -7,7 +7,8 @@ describe GitlabSchema.types['Query'] do ...@@ -7,7 +7,8 @@ describe GitlabSchema.types['Query'] do
expect(described_class).to have_graphql_fields( expect(described_class).to have_graphql_fields(
:design_management, :design_management,
:geo_node, :geo_node,
:vulnerabilities :vulnerabilities,
:instance_security_dashboard
).at_least ).at_least
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'Query.instanceSecurityDashboard.projects' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:other_project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let(:current_user) { user }
before do
project.add_developer(user)
other_project.add_developer(user)
stub_licensed_features(security_dashboard: true)
end
let(:fields) do
<<~QUERY
nodes {
id
}
QUERY
end
let(:query) do
graphql_query_for('instanceSecurityDashboard', nil, query_graphql_field('projects', {}, fields))
end
subject(:projects) { graphql_data.dig('instanceSecurityDashboard', 'projects', 'nodes') }
context 'with logged in user' do
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
it 'finds only projects that were added to instance security dashboard' do
expect(projects).to eq([{ "id" => GitlabSchema.id_from_object(project).to_s }])
end
end
end
context 'with no user' do
let(:current_user) { nil }
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
it { is_expected.to be_nil }
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