Commit 460d7af3 authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Dylan Griffith

Modify mutation to add Project to Instance Security Dashboard

This change introduces small improvement to AddProject mutation to not
accept multiple project ids, but only one and let GraphQL manage
multiple mutations to add multiple projects to Security Dashboard.
parent 500f70a5
......@@ -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.
"""
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 {
"""
IDs of projects that were added to the Instance Security Dashboard
"""
addedProjectIds: [ID!]
type AddProjectToSecurityDashboardPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
IDs of projects that are already added to the Instance Security Dashboard
"""
duplicatedProjectIds: [ID!]
"""
Reasons why the mutation failed.
"""
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
}
"""
......@@ -5972,7 +5962,7 @@ enum MoveType {
type Mutation {
addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload
addProjectsToSecurityDashboard(input: AddProjectsToSecurityDashboardInput!): AddProjectsToSecurityDashboardPayload
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload
......
......@@ -127,28 +127,20 @@
},
{
"kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput",
"description": "Autogenerated input type of AddProjectsToSecurityDashboard",
"name": "AddProjectToSecurityDashboardInput",
"description": "Autogenerated input type of AddProjectToSecurityDashboard",
"fields": null,
"inputFields": [
{
"name": "projectIds",
"description": "IDs of projects to be added to Instance Security Dashboard",
"name": "id",
"description": "ID of the project to be added to Instance Security Dashboard",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
......@@ -170,31 +162,9 @@
},
{
"kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload",
"description": "Autogenerated return type of AddProjectsToSecurityDashboard",
"name": "AddProjectToSecurityDashboardPayload",
"description": "Autogenerated return type of AddProjectToSecurityDashboard",
"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",
"description": "A unique identifier for the client performing the mutation.",
......@@ -209,28 +179,6 @@
"isDeprecated": false,
"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",
"description": "Reasons why the mutation failed.",
......@@ -258,23 +206,15 @@
"deprecationReason": null
},
{
"name": "invalidProjectIds",
"description": "IDs of projects that were not added to the Instance Security Dashboard",
"name": "project",
"description": "Project that was 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
}
}
"kind": "OBJECT",
"name": "Project",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
......@@ -17054,7 +16994,7 @@
"deprecationReason": null
},
{
"name": "addProjectsToSecurityDashboard",
"name": "addProjectToSecurityDashboard",
"description": null,
"args": [
{
......@@ -17065,7 +17005,7 @@
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput",
"name": "AddProjectToSecurityDashboardInput",
"ofType": null
}
},
......@@ -17074,7 +17014,7 @@
],
"type": {
"kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload",
"name": "AddProjectToSecurityDashboardPayload",
"ofType": null
},
"isDeprecated": false,
......
......@@ -26,17 +26,15 @@ Autogenerated return type of AddAwardEmoji
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
## AddProjectsToSecurityDashboardPayload
## AddProjectToSecurityDashboardPayload
Autogenerated return type of AddProjectsToSecurityDashboard
Autogenerated return type of AddProjectToSecurityDashboard
| 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. |
| `duplicatedProjectIds` | ID! => Array | IDs of projects that are already added to the Instance Security Dashboard |
| `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
......
......@@ -18,7 +18,7 @@ module EE
mount_mutation ::Mutations::Requirements::Update
mount_mutation ::Mutations::Vulnerabilities::Dismiss
mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
mount_mutation ::Mutations::SecurityDashboard::AddProjects
mount_mutation ::Mutations::SecurityDashboard::AddProject
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
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) }
describe '#resolve' do
......@@ -11,14 +11,14 @@ describe Mutations::SecurityDashboard::AddProjects do
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
my_project.add_developer(user)
already_added_project.add_developer(user)
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
let(:current_user) { nil }
......@@ -42,25 +42,31 @@ describe Mutations::SecurityDashboard::AddProjects do
stub_licensed_features(security_dashboard: true)
end
context 'when project_ids is empty' do
let(:project_ids) { [] }
context 'when project is available to the user and can be added to the security dashboard' do
let(:selected_project) { my_project }
it { is_expected.to eq(added_project_ids: [], duplicated_project_ids: [], invalid_project_ids: [], errors: []) }
end
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)])
it 'adds project to the security dashboard', :aggregate_failures do
expect(subject[:project]).to eq(my_project)
expect(subject[:errors]).to be_empty
expect(user.security_dashboard_projects).to include(my_project)
end
end
it 'does not add project that already exist in the security dashboard', :aggregate_failures do
expect(subject[:duplicated_project_ids]).to eq([GitlabSchema.id_from_object(already_added_project)])
expect(user.security_dashboard_projects).to include(already_added_project)
context 'when project is not available to the user' do
let(:selected_project) { project }
it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
it 'does not add project that is not available for the user' do
expect(subject[:invalid_project_ids]).to eq([GitlabSchema.id_from_object(project)])
context 'when project is already added to the security dashboard' do
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
......
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