Commit 04e2a8c0 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 5deff621 afdc69e9
......@@ -2,12 +2,21 @@
module Resolvers
class ProjectPipelinesResolver < BaseResolver
include LooksAhead
include ResolvesPipelines
alias_method :project, :object
def resolve(**args)
resolve_pipelines(project, args)
def resolve_with_lookahead(**args)
apply_lookahead(resolve_pipelines(project, args))
end
private
def preloads
{
jobs: [:statuses]
}
end
end
end
......@@ -56,6 +56,12 @@ module Types
description: 'Specifies if a pipeline can be canceled',
method: :cancelable?,
null: false
field :jobs,
::Types::Ci::JobType.connection_type,
null: true,
description: 'Jobs belonging to the pipeline',
method: :statuses
end
end
end
......
......@@ -191,6 +191,7 @@ module Types
Types::Ci::PipelineType.connection_type,
null: true,
description: 'Build pipelines of the project',
extras: [:lookahead],
resolver: Resolvers::ProjectPipelinesResolver
field :pipeline,
......
......@@ -36,7 +36,7 @@
= clipboard_button(target: '#serverless_domain_verification', class: 'btn-default d-none d-sm-block')
%p.form-text.text-muted
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
= _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration.").html_safe % { link_to_help: link_to_help }
- else
.form-group
......
......@@ -30,4 +30,4 @@
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
%p.form-text.text-muted
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
= _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration.").html_safe % { link_to_help: link_to_help }
---
title: Add jobs field with secureReportTypes argument to Ci::PipelineType
merge_request: 45837
author:
type: added
......@@ -13675,6 +13675,36 @@ type Pipeline {
"""
iid: String!
"""
Jobs belonging to the pipeline
"""
jobs(
"""
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
"""
Filter jobs by the type of security report they produce
"""
securityReportTypes: [SecurityReportTypeEnum!]
): CiJobConnection
"""
Specifies if a pipeline can be retried
"""
......@@ -18181,6 +18211,43 @@ type SecurityReportSummarySection {
vulnerabilitiesCount: Int
}
enum SecurityReportTypeEnum {
"""
API FUZZING scan report
"""
API_FUZZING
"""
CONTAINER SCANNING scan report
"""
CONTAINER_SCANNING
"""
COVERAGE FUZZING scan report
"""
COVERAGE_FUZZING
"""
DAST scan report
"""
DAST
"""
DEPENDENCY SCANNING scan report
"""
DEPENDENCY_SCANNING
"""
SAST scan report
"""
SAST
"""
SECRET DETECTION scan report
"""
SECRET_DETECTION
}
"""
The type of the security scanner
"""
......
......@@ -40202,6 +40202,77 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "jobs",
"description": "Jobs belonging to the pipeline",
"args": [
{
"name": "securityReportTypes",
"description": "Filter jobs by the type of security report they produce",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SecurityReportTypeEnum",
"ofType": null
}
}
},
"defaultValue": null
},
{
"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": "CiJobConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "retryable",
"description": "Specifies if a pipeline can be retried",
......@@ -52355,6 +52426,59 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "SecurityReportTypeEnum",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SAST",
"description": "SAST scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DAST",
"description": "DAST scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DEPENDENCY_SCANNING",
"description": "DEPENDENCY SCANNING scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CONTAINER_SCANNING",
"description": "CONTAINER SCANNING scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SECRET_DETECTION",
"description": "SECRET DETECTION scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "COVERAGE_FUZZING",
"description": "COVERAGE FUZZING scan report",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "API_FUZZING",
"description": "API FUZZING scan report",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "SecurityScannerType",
......@@ -3806,6 +3806,18 @@ Size of UI component in SAST configuration page.
| `MEDIUM` | |
| `SMALL` | |
### SecurityReportTypeEnum
| Value | Description |
| ----- | ----------- |
| `API_FUZZING` | API FUZZING scan report |
| `CONTAINER_SCANNING` | CONTAINER SCANNING scan report |
| `COVERAGE_FUZZING` | COVERAGE FUZZING scan report |
| `DAST` | DAST scan report |
| `DEPENDENCY_SCANNING` | DEPENDENCY SCANNING scan report |
| `SAST` | SAST scan report |
| `SECRET_DETECTION` | SECRET DETECTION scan report |
### SecurityScannerType
The type of the security scanner.
......
......@@ -13,6 +13,12 @@ module EE
extras: [:lookahead],
description: 'Vulnerability and scanned resource counts for each security scanner of the pipeline',
resolver: ::Resolvers::SecurityReportSummaryResolver
field :jobs,
::Types::Ci::JobType.connection_type,
null: true,
description: 'Jobs belonging to the pipeline',
resolver: ::Resolvers::Ci::JobsResolver
end
end
end
......
# frozen_string_literal: true
module Resolvers
module Ci
class JobsResolver < BaseResolver
alias_method :pipeline, :object
argument :security_report_types, [Types::Security::ReportTypeEnum],
required: false,
description: 'Filter jobs by the type of security report they produce'
def resolve(security_report_types: [])
if security_report_types.present?
::Security::SecurityJobsFinder.new(
pipeline: pipeline,
job_types: security_report_types
).execute
else
pipeline.statuses
end
end
end
end
end
# frozen_string_literal: true
module Types
module Security
class ReportTypeEnum < BaseEnum
graphql_name 'SecurityReportTypeEnum'
::Security::SecurityJobsFinder.allowed_job_types.each do |report_type|
value report_type.upcase,
value: report_type,
description: "#{report_type.upcase.to_s.tr('_', ' ')} scan report"
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::JobsResolver do
include GraphqlHelpers
let_it_be(:pipeline) { create(:ci_pipeline) }
before_all do
create(:ci_build, name: 'Normal job', pipeline: pipeline)
create(:ci_build, :sast, name: 'DAST job', pipeline: pipeline)
create(:ci_build, :dast, name: 'SAST job', pipeline: pipeline)
create(:ci_build, :container_scanning, name: 'Container scanning job', pipeline: pipeline)
end
describe '#resolve' do
context 'when security_report_types is empty' do
it "returns all of the pipeline's jobs" do
jobs = resolve(described_class, obj: pipeline, args: {}, ctx: {})
job_names = jobs.map(&:name)
expect(job_names).to contain_exactly('Normal job', 'DAST job', 'SAST job', 'Container scanning job')
end
end
context 'when security_report_types is present' do
it "returns the pipeline's jobs with the given security report types" do
report_types = [
::Types::Security::ReportTypeEnum.values['SAST'].value,
::Types::Security::ReportTypeEnum.values['DAST'].value
]
jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, ctx: {})
job_names = jobs.map(&:name)
expect(job_names).to contain_exactly('DAST job', 'SAST job')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SecurityReportTypeEnum'] do
it 'exposes all security report types' do
expect(described_class.values.keys).to contain_exactly(
*::Security::SecurityJobsFinder.allowed_job_types.map(&:to_s).map(&:upcase)
)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).pipelines' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:first_user) { create(:user) }
let_it_be(:second_user) { create(:user) }
describe '.jobs' do
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
jobs {
nodes {
name
}
}
}
}
}
}
)
end
it 'fetches the jobs without an N+1' do
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, pipeline: pipeline, name: 'Job 1')
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: first_user)
end
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, pipeline: pipeline, name: 'Job 2')
expect do
post_graphql(query, current_user: second_user)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes')
job_names = pipelines_data.map do |pipeline_data|
jobs_data = pipeline_data.dig('jobs', 'nodes')
jobs_data.map { |job_data| job_data['name'] }
end.flatten
expect(job_names).to contain_exactly('Job 1', 'Job 2')
end
end
describe '.jobs(securityReportTypes)' do
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
jobs(securityReportTypes: [SAST]) {
nodes {
name
}
}
}
}
}
}
)
end
it 'fetches the jobs matching the report type filter' do
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, :dast, name: 'DAST Job 1', pipeline: pipeline)
create(:ci_build, :sast, name: 'SAST Job 1', pipeline: pipeline)
post_graphql(query, current_user: first_user)
expect(response).to have_gitlab_http_status(:ok)
pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes')
job_names = pipelines_data.map do |pipeline_data|
jobs_data = pipeline_data.dig('jobs', 'nodes')
jobs_data.map { |job_data| job_data['name'] }
end.flatten
expect(job_names).to contain_exactly('SAST Job 1')
end
end
end
......@@ -27810,7 +27810,7 @@ msgstr ""
msgid "To"
msgstr ""
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration."
msgstr ""
msgid "To Do"
......
# frozen_string_literal: true
module QA
RSpec.describe 'Package', :orchestrated, :packages do
describe 'Maven Repository with Gradle' do
include Runtime::Fixtures
let(:group_id) { 'com.gitlab.qa' }
let(:artifact_id) { 'maven_gradle' }
let(:package_name) { "#{group_id}/#{artifact_id}".tr('.', '/') }
let(:auth_token) do
unless Page::Main::Menu.perform(&:signed_in?)
Flow::Login.sign_in
end
Resource::PersonalAccessToken.fabricate!.access_token
end
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'maven-with-gradle-project'
project.initialize_with_readme = true
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
runner.tags = ["runner-for-#{project.name}"]
runner.executor = :docker
runner.project = project
end
end
let(:gitlab_address_with_port) do
uri = URI.parse(Runtime::Scenario.gitlab_address)
"#{uri.scheme}://#{uri.host}:#{uri.port}"
end
after do
runner.remove_via_api!
end
it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([{
file_path: '.gitlab-ci.yml',
content:
<<~YAML
deploy:
image: gradle:6.5-jdk11
script:
- 'gradle publish'
only:
- master
tags:
- "runner-for-#{project.name}"
YAML
},
{
file_path: 'build.gradle',
content:
<<~EOF
plugins {
id 'java'
id 'maven-publish'
}
publishing {
publications {
library(MavenPublication) {
groupId '#{group_id}'
artifactId '#{artifact_id}'
from components.java
}
}
repositories {
maven {
url "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/maven"
credentials(HttpHeaderCredentials) {
name = "Private-Token"
value = "#{auth_token}"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
EOF
}])
end
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 800)
end
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
end
Page::Project::Packages::Show.perform do |show|
show.click_delete
end
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).pipelines' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:first_user) { create(:user) }
let_it_be(:second_user) { create(:user) }
describe '.jobs' do
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
jobs {
nodes {
name
}
}
}
}
}
}
)
end
it 'fetches the jobs without an N+1' do
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, pipeline: pipeline, name: 'Job 1')
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: first_user)
end
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, pipeline: pipeline, name: 'Job 2')
expect do
post_graphql(query, current_user: second_user)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes')
job_names = pipelines_data.map do |pipeline_data|
jobs_data = pipeline_data.dig('jobs', 'nodes')
jobs_data.map { |job_data| job_data['name'] }
end.flatten
expect(job_names).to contain_exactly('Job 1', 'Job 2')
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