Commit ff0b736a authored by Dylan Griffith's avatar Dylan Griffith

Merge branch '214583-change-mutation-to-add-projects-to-security-dashboard' into 'master'

Modify mutation to add Project to Instance Security Dashboard

See merge request gitlab-org/gitlab!30865
parents 4c9f3782 460d7af3
...@@ -39,48 +39,38 @@ type AddAwardEmojiPayload { ...@@ -39,48 +39,38 @@ type AddAwardEmojiPayload {
} }
""" """
Autogenerated input type of AddProjectsToSecurityDashboard Autogenerated input type of AddProjectToSecurityDashboard
""" """
input AddProjectsToSecurityDashboardInput { input AddProjectToSecurityDashboardInput {
""" """
A unique identifier for the client performing the mutation. A unique identifier for the client performing the mutation.
""" """
clientMutationId: String clientMutationId: String
""" """
IDs of projects to be added to Instance Security Dashboard ID of the project to be added to Instance Security Dashboard
""" """
projectIds: [ID!]! id: ID!
} }
""" """
Autogenerated return type of AddProjectsToSecurityDashboard Autogenerated return type of AddProjectToSecurityDashboard
""" """
type AddProjectsToSecurityDashboardPayload { type AddProjectToSecurityDashboardPayload {
"""
IDs of projects that were added to the Instance Security Dashboard
"""
addedProjectIds: [ID!]
""" """
A unique identifier for the client performing the mutation. A unique identifier for the client performing the mutation.
""" """
clientMutationId: String clientMutationId: String
"""
IDs of projects that are already added to the Instance Security Dashboard
"""
duplicatedProjectIds: [ID!]
""" """
Reasons why the mutation failed. Reasons why the mutation failed.
""" """
errors: [String!]! errors: [String!]!
""" """
IDs of projects that were not added to the Instance Security Dashboard Project that was added to the Instance Security Dashboard
""" """
invalidProjectIds: [ID!] project: Project
} }
""" """
...@@ -6049,7 +6039,7 @@ enum MoveType { ...@@ -6049,7 +6039,7 @@ enum MoveType {
type Mutation { type Mutation {
addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload
addProjectsToSecurityDashboard(input: AddProjectsToSecurityDashboardInput!): AddProjectsToSecurityDashboardPayload addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
createBranch(input: CreateBranchInput!): CreateBranchPayload createBranch(input: CreateBranchInput!): CreateBranchPayload
......
...@@ -127,20 +127,14 @@ ...@@ -127,20 +127,14 @@
}, },
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput", "name": "AddProjectToSecurityDashboardInput",
"description": "Autogenerated input type of AddProjectsToSecurityDashboard", "description": "Autogenerated input type of AddProjectToSecurityDashboard",
"fields": null, "fields": null,
"inputFields": [ "inputFields": [
{ {
"name": "projectIds", "name": "id",
"description": "IDs of projects to be added to Instance Security Dashboard", "description": "ID of the project to be added to Instance Security Dashboard",
"type": { "type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
...@@ -148,8 +142,6 @@ ...@@ -148,8 +142,6 @@
"name": "ID", "name": "ID",
"ofType": null "ofType": null
} }
}
}
}, },
"defaultValue": null "defaultValue": null
}, },
...@@ -170,31 +162,9 @@ ...@@ -170,31 +162,9 @@
}, },
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload", "name": "AddProjectToSecurityDashboardPayload",
"description": "Autogenerated return type of AddProjectsToSecurityDashboard", "description": "Autogenerated return type of AddProjectToSecurityDashboard",
"fields": [ "fields": [
{
"name": "addedProjectIds",
"description": "IDs of projects that were added to the Instance Security Dashboard",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
...@@ -209,28 +179,6 @@ ...@@ -209,28 +179,6 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "duplicatedProjectIds",
"description": "IDs of projects that are already added to the Instance Security Dashboard",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "errors", "name": "errors",
"description": "Reasons why the mutation failed.", "description": "Reasons why the mutation failed.",
...@@ -258,23 +206,15 @@ ...@@ -258,23 +206,15 @@
"deprecationReason": null "deprecationReason": null
}, },
{ {
"name": "invalidProjectIds", "name": "project",
"description": "IDs of projects that were not added to the Instance Security Dashboard", "description": "Project that was added to the Instance Security Dashboard",
"args": [ "args": [
], ],
"type": { "type": {
"kind": "LIST", "kind": "OBJECT",
"name": null, "name": "Project",
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null "ofType": null
}
}
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
...@@ -17245,7 +17185,7 @@ ...@@ -17245,7 +17185,7 @@
"deprecationReason": null "deprecationReason": null
}, },
{ {
"name": "addProjectsToSecurityDashboard", "name": "addProjectToSecurityDashboard",
"description": null, "description": null,
"args": [ "args": [
{ {
...@@ -17256,7 +17196,7 @@ ...@@ -17256,7 +17196,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput", "name": "AddProjectToSecurityDashboardInput",
"ofType": null "ofType": null
} }
}, },
...@@ -17265,7 +17205,7 @@ ...@@ -17265,7 +17205,7 @@
], ],
"type": { "type": {
"kind": "OBJECT", "kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload", "name": "AddProjectToSecurityDashboardPayload",
"ofType": null "ofType": null
}, },
"isDeprecated": false, "isDeprecated": false,
......
...@@ -26,17 +26,15 @@ Autogenerated return type of AddAwardEmoji ...@@ -26,17 +26,15 @@ Autogenerated return type of AddAwardEmoji
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. | | `errors` | String! => Array | Reasons why the mutation failed. |
## AddProjectsToSecurityDashboardPayload ## AddProjectToSecurityDashboardPayload
Autogenerated return type of AddProjectsToSecurityDashboard Autogenerated return type of AddProjectToSecurityDashboard
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `addedProjectIds` | ID! => Array | IDs of projects that were added to the Instance Security Dashboard |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `duplicatedProjectIds` | ID! => Array | IDs of projects that are already added to the Instance Security Dashboard |
| `errors` | String! => Array | Reasons why the mutation failed. | | `errors` | String! => Array | Reasons why the mutation failed. |
| `invalidProjectIds` | ID! => Array | IDs of projects that were not added to the Instance Security Dashboard | | `project` | Project | Project that was added to the Instance Security Dashboard |
## AdminSidekiqQueuesDeleteJobsPayload ## AdminSidekiqQueuesDeleteJobsPayload
......
...@@ -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::AddProjects mount_mutation ::Mutations::SecurityDashboard::AddProject
end end
end end
end end
......
# frozen_string_literal: true
module Mutations
module SecurityDashboard
class AddProject < BaseMutation
graphql_name 'AddProjectToSecurityDashboard'
authorize :read_vulnerability
field :project, Types::ProjectType,
null: true,
description: 'Project that was added to the Instance Security Dashboard'
argument :id, GraphQL::ID_TYPE,
required: true,
description: 'ID of the project to be added to Instance Security Dashboard'
def resolve(id:)
project = authorized_find!(id: id)
result = add_project(project)
{
project: result ? project : nil,
errors: result ? [] : ['The project already belongs to your dashboard or you don\'t have permission to perform this action']
}
end
private
def find_object(id:)
GitlabSchema.object_from_id(id)
end
def add_project(project)
Dashboard::Projects::CreateService
.new(current_user, current_user.security_dashboard_projects, feature: :security_dashboard)
.execute([project.id])
.then { |result| result.added_project_ids.include?(project.id) }
end
end
end
end
# frozen_string_literal: true
module Mutations
module SecurityDashboard
class AddProjects < BaseMutation
graphql_name 'AddProjectsToSecurityDashboard'
authorize :read_instance_security_dashboard
field :invalid_project_ids, [GraphQL::ID_TYPE],
null: true,
description: 'IDs of projects that were not added to the Instance Security Dashboard'
field :added_project_ids, [GraphQL::ID_TYPE],
null: true,
description: 'IDs of projects that were added to the Instance Security Dashboard'
field :duplicated_project_ids, [GraphQL::ID_TYPE],
null: true,
description: 'IDs of projects that are already added to the Instance Security Dashboard'
argument :project_ids, [GraphQL::ID_TYPE],
required: true,
description: 'IDs of projects to be added to Instance Security Dashboard'
def resolve(project_ids:)
dashboard = authorized_find!
raise_resource_not_available_error! unless dashboard.feature_available?(:security_dashboard)
result = add_projects(project_ids.map(&method(:extract_project_id)))
{
invalid_project_ids: result.invalid_project_ids.map(&method(:to_global_id)),
added_project_ids: result.added_project_ids.map(&method(:to_global_id)),
duplicated_project_ids: result.duplicate_project_ids.map(&method(:to_global_id)),
errors: []
}
end
private
def find_object(*args)
InstanceSecurityDashboard.new(current_user)
end
def extract_project_id(global_id)
return unless global_id.present?
GitlabSchema.parse_gid(global_id, expected_type: ::Project).model_id
end
def add_projects(project_ids)
Dashboard::Projects::CreateService.new(
current_user,
current_user.security_dashboard_projects,
feature: :security_dashboard
).execute(project_ids.compact)
end
def to_global_id(project_id)
GitlabSchema.id_from_object(Project.new(id: project_id))
end
end
end
end
---
title: Modify GraphQL mutation for adding projects to Instance Security Dashboard
to support only single project id
merge_request: 30865
author:
type: changed
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Mutations::SecurityDashboard::AddProjects do describe Mutations::SecurityDashboard::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
...@@ -11,14 +11,14 @@ describe Mutations::SecurityDashboard::AddProjects do ...@@ -11,14 +11,14 @@ describe Mutations::SecurityDashboard::AddProjects do
let_it_be(:user) { create(:user, security_dashboard_projects: [already_added_project]) } let_it_be(:user) { create(:user, security_dashboard_projects: [already_added_project]) }
let(:project_ids) { [project, my_project, already_added_project].map(&GitlabSchema.method(:id_from_object)).map(&:to_s) } let(:selected_project) { project }
before do before do
my_project.add_developer(user) my_project.add_developer(user)
already_added_project.add_developer(user) already_added_project.add_developer(user)
end end
subject { mutation.resolve(project_ids: project_ids) } subject { mutation.resolve(id: GitlabSchema.id_from_object(selected_project)) }
context 'when user is not logged_in' do context 'when user is not logged_in' do
let(:current_user) { nil } let(:current_user) { nil }
...@@ -42,25 +42,31 @@ describe Mutations::SecurityDashboard::AddProjects do ...@@ -42,25 +42,31 @@ describe Mutations::SecurityDashboard::AddProjects do
stub_licensed_features(security_dashboard: true) stub_licensed_features(security_dashboard: true)
end end
context 'when project_ids is empty' do context 'when project is available to the user and can be added to the security dashboard' do
let(:project_ids) { [] } let(:selected_project) { my_project }
it { is_expected.to eq(added_project_ids: [], duplicated_project_ids: [], invalid_project_ids: [], errors: []) } it 'adds project to the security dashboard', :aggregate_failures do
end expect(subject[:project]).to eq(my_project)
expect(subject[:errors]).to be_empty
context 'when project_ids contains ids' do
it 'adds project that is available to the user to the security dashboard', :aggregate_failures do
expect(subject[:added_project_ids]).to eq([GitlabSchema.id_from_object(my_project)])
expect(user.security_dashboard_projects).to include(my_project) expect(user.security_dashboard_projects).to include(my_project)
end end
end
it 'does not add project that already exist in the security dashboard', :aggregate_failures do context 'when project is not available to the user' do
expect(subject[:duplicated_project_ids]).to eq([GitlabSchema.id_from_object(already_added_project)]) let(:selected_project) { project }
expect(user.security_dashboard_projects).to include(already_added_project)
it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end end
it 'does not add project that is not available for the user' do context 'when project is already added to the security dashboard' do
expect(subject[:invalid_project_ids]).to eq([GitlabSchema.id_from_object(project)]) let(:selected_project) { already_added_project }
it 'does not add project to the security dashboard', :aggregate_failures do
expect(subject[:project]).to be_nil
expect(subject[:errors]).to eq(['The project already belongs to your dashboard or you don\'t have permission to perform this action'])
expect(user.security_dashboard_projects).to include(already_added_project)
end end
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