Skip to content
Snippets Groups Projects
Commit 3fe34368 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 41d446ba
No related branches found
No related tags found
No related merge requests found
Showing
with 501 additions and 3 deletions
doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_4.png

61.5 KiB

Loading
Loading
@@ -76,7 +76,7 @@ To the right of the filters, you should see a **Hide dismissed** toggle button (
NOTE: **Note:**
The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
 
![dashboard with action buttons and metrics](img/group_security_dashboard_v12_3.png)
![dashboard with action buttons and metrics](img/group_security_dashboard_v12_4.png)
 
Selecting one or more filters will filter the results in this page. Disabling the **Hide dismissed**
toggle button will let you also see vulnerabilities that have been dismissed.
Loading
Loading
Loading
Loading
@@ -479,6 +479,8 @@ module API
finder_params[:user] = params.delete(:user) if params[:user]
finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
finder_params[:id_after] = params[:id_after] if params[:id_after]
finder_params[:id_before] = params[:id_before] if params[:id_before]
finder_params
end
 
Loading
Loading
Loading
Loading
@@ -61,6 +61,8 @@ module API
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
optional :with_programming_language, type: String, desc: 'Limit to repositories which use the given programming language'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
optional :id_after, type: Integer, desc: 'Limit results to projects with IDs greater than the specified ID'
optional :id_before, type: Integer, desc: 'Limit results to projects with IDs less than the specified ID'
 
use :optional_filter_params_ee
end
Loading
Loading
Loading
Loading
@@ -4994,6 +4994,9 @@ msgstr ""
msgid "Current password"
msgstr ""
 
msgid "Current vulnerabilities count"
msgstr ""
msgid "CurrentUser|Profile"
msgstr ""
 
Loading
Loading
@@ -5791,6 +5794,9 @@ msgstr ""
msgid "Diff limits"
msgstr ""
 
msgid "Difference between start date and now"
msgstr ""
msgid "DiffsCompareBaseBranch|(base)"
msgstr ""
 
Loading
Loading
@@ -19180,6 +19186,9 @@ msgstr ""
msgid "VulnerabilityChart|%{formattedStartDate} to today"
msgstr ""
 
msgid "VulnerabilityChart|Severity"
msgstr ""
msgid "Vulnerability|Class"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -165,6 +165,7 @@ module QA
module Dashboard
autoload :Projects, 'qa/page/dashboard/projects'
autoload :Groups, 'qa/page/dashboard/groups'
autoload :Welcome, 'qa/page/dashboard/welcome'
 
module Snippet
autoload :New, 'qa/page/dashboard/snippet/new'
Loading
Loading
Loading
Loading
@@ -135,6 +135,40 @@ module QA
has_no_css?('.fa-spinner.block-loading', wait: Capybara.default_max_wait_time)
end
 
def has_loaded_all_images?
# I don't know of a foolproof way to wait for all images to load
# This loop gives time for the img tags to be rendered and for
# images to start loading.
previous_total_images = 0
wait(interval: 1) do
current_total_images = all("img").size
result = previous_total_images == current_total_images
previous_total_images = current_total_images
result
end
# Retry until all images found can be fetched via HTTP, and
# check that the image has a non-zero natural width (a broken
# img tag could have a width, but wouldn't have a natural width)
# Unfortunately, this doesn't account for SVGs. They're rendered
# as HTML, so there doesn't seem to be a way to check that they
# display properly via Selenium. However, if the SVG couldn't be
# rendered (e.g., because the file doesn't exist), the whole page
# won't display properly, so we should catch that with the test
# this method is called from.
# The user's avatar is an img, which could be a gravatar image,
# so we skip that by only checking for images hosted internally
retry_until(sleep_interval: 1) do
all("img").all? do |image|
next true unless URI(image['src']).host == URI(page.current_url).host
asset_exists?(image['src']) && image['naturalWidth'].to_i > 0
end
end
end
def wait_for_animated_element(name)
# It would be ideal if we could detect when the animation is complete
# but in some cases there's nothing we can easily access via capybara
Loading
Loading
# frozen_string_literal: true
module QA
module Page
module Dashboard
class Welcome < Page::Base
view 'app/views/dashboard/projects/_zero_authorized_projects.html.haml' do
element :welcome_title_content
end
def has_welcome_title?(text)
has_element?(:welcome_title_content, text: text)
end
end
end
end
end
Loading
Loading
@@ -7,6 +7,10 @@ module QA::Page
element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern
end
 
view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
element :pipeline_commit_status
end
def click_on_latest_pipeline
css = '.js-pipeline-url-link'
 
Loading
Loading
@@ -16,6 +20,14 @@ module QA::Page
 
link.click
end
def wait_for_latest_pipeline_success
wait(reload: false, max: 300) do
within_element_by_index(:pipeline_commit_status, 0) do
has_text?('passed')
end
end
end
end
end
end
Loading
Loading
@@ -7,13 +7,14 @@ module QA
class User < Base
attr_reader :unique_id
attr_writer :username, :password
attr_accessor :provider, :extern_uid
attr_accessor :admin, :provider, :extern_uid
 
attribute :id
attribute :name
attribute :email
 
def initialize
@admin = false
@unique_id = SecureRandom.hex(8)
end
 
Loading
Loading
@@ -75,6 +76,16 @@ module QA
super
end
 
def api_delete
super
QA::Runtime::Logger.debug("Deleted user '#{username}'") if Runtime::Env.debug?
end
def api_delete_path
"/users/#{id}"
end
def api_get_path
"/users/#{fetch_id(username)}"
end
Loading
Loading
@@ -85,6 +96,7 @@ module QA
 
def api_post_body
{
admin: admin,
email: email,
password: password,
username: username,
Loading
Loading
# frozen_string_literal: true
require 'nokogiri'
module QA
context 'Manage' do
describe 'Check for broken images', :requires_admin do
before(:context) do
admin = QA::Resource::User.new.tap do |user|
user.username = QA::Runtime::User.admin_username
user.password = QA::Runtime::User.admin_password
end
@api_client = Runtime::API::Client.new(:gitlab, user: admin)
@new_user = Resource::User.fabricate_via_api! do |user|
user.api_client = @api_client
end
@new_admin = Resource::User.fabricate_via_api! do |user|
user.admin = true
user.api_client = @api_client
end
Page::Main::Menu.perform(&:sign_out_if_signed_in)
end
after(:context) do
@new_user.remove_via_api!
@new_admin.remove_via_api!
end
shared_examples 'loads all images' do
it 'loads all images' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: new_user) }
Page::Dashboard::Welcome.perform do |welcome|
expect(welcome).to have_welcome_title("Welcome to GitLab")
# This would be better if it were a visual validation test
expect(welcome).to have_loaded_all_images
end
end
end
context 'when logged in as a new user' do
it_behaves_like 'loads all images' do
let(:new_user) { @new_user }
end
end
context 'when logged in as a new admin' do
it_behaves_like 'loads all images' do
let(:new_user) { @new_admin }
end
end
end
end
end
Loading
Loading
@@ -58,6 +58,31 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to eq([internal_project]) }
end
 
describe 'with id_after' do
context 'only returns projects with a project id greater than given' do
let(:params) { { id_after: internal_project.id }}
it { is_expected.to eq([public_project]) }
end
end
describe 'with id_before' do
context 'only returns projects with a project id less than given' do
let(:params) { { id_before: public_project.id }}
it { is_expected.to eq([internal_project]) }
end
end
describe 'with both id_before and id_after' do
context 'only returns projects with a project id less than given' do
let!(:projects) { create_list(:project, 5, :public) }
let(:params) { { id_after: projects.first.id, id_before: projects.last.id }}
it { is_expected.to contain_exactly(*projects[1..-2]) }
end
end
describe 'filter by visibility_level' do
before do
private_project.add_maintainer(user)
Loading
Loading
import { firstAndLastY } from '~/lib/utils/chart_utils';
describe('Chart utils', () => {
describe('firstAndLastY', () => {
it('returns the first and last y-values of a given data set as an array', () => {
const data = [['', 1], ['', 2], ['', 3]];
expect(firstAndLastY(data)).toEqual([1, 3]);
});
});
});
Loading
Loading
@@ -7,6 +7,8 @@ import {
sum,
isOdd,
median,
changeInPercent,
formattedChangeInPercent,
} from '~/lib/utils/number_utils';
 
describe('Number Utils', () => {
Loading
Loading
@@ -122,4 +124,42 @@ describe('Number Utils', () => {
expect(median(items)).toBe(14.5);
});
});
describe('changeInPercent', () => {
it.each`
firstValue | secondValue | expectedOutput
${99} | ${100} | ${1}
${100} | ${99} | ${-1}
${0} | ${99} | ${Infinity}
${2} | ${2} | ${0}
${-100} | ${-99} | ${1}
`(
'computes the change between $firstValue and $secondValue in percent',
({ firstValue, secondValue, expectedOutput }) => {
expect(changeInPercent(firstValue, secondValue)).toBe(expectedOutput);
},
);
});
describe('formattedChangeInPercent', () => {
it('prepends "%" to the output', () => {
expect(formattedChangeInPercent(1, 2)).toMatch(/%$/);
});
it('indicates if the change was a decrease', () => {
expect(formattedChangeInPercent(100, 99)).toContain('-1');
});
it('indicates if the change was an increase', () => {
expect(formattedChangeInPercent(99, 100)).toContain('+1');
});
it('shows "-" per default if the change can not be expressed in an integer', () => {
expect(formattedChangeInPercent(0, 1)).toBe('-');
});
it('shows the given fallback if the change can not be expressed in an integer', () => {
expect(formattedChangeInPercent(0, 1, { nonFiniteResult: '*' })).toBe('*');
});
});
});
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::MergeRequests::SetLabels do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
describe '#resolve' do
let(:label) { create(:label, project: merge_request.project) }
let(:label2) { create(:label, project: merge_request.project) }
let(:label_ids) { [label.to_global_id] }
let(:mutated_merge_request) { subject[:merge_request] }
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the merge request' do
before do
merge_request.project.add_developer(user)
end
it 'sets the labels, removing all others' do
merge_request.update!(labels: [label2])
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.labels).to contain_exactly(label)
expect(subject[:errors]).to be_empty
end
it 'returns errors merge request could not be updated' do
# Make the merge request invalid
merge_request.allow_broken = true
merge_request.update!(source_project: nil)
expect(subject[:errors]).not_to be_empty
end
context 'when passing an empty array' do
let(:label_ids) { [] }
it 'removes all labels' do
merge_request.update!(labels: [label])
expect(mutated_merge_request.labels).to be_empty
end
end
context 'when passing operation_mode as APPEND' do
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:append]) }
it 'sets the labels, without removing others' do
merge_request.update!(labels: [label2])
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.labels).to contain_exactly(label, label2)
expect(subject[:errors]).to be_empty
end
end
context 'when passing operation_mode as REMOVE' do
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:remove])}
it 'removes the labels, without removing others' do
merge_request.update!(labels: [label, label2])
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.labels).to contain_exactly(label2)
expect(subject[:errors]).to be_empty
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::MergeRequests::SetLocked do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
describe '#resolve' do
let(:locked) { true }
let(:mutated_merge_request) { subject[:merge_request] }
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, locked: locked) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the merge request' do
before do
merge_request.project.add_developer(user)
end
it 'returns the merge request as discussion locked' do
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request).to be_discussion_locked
expect(subject[:errors]).to be_empty
end
it 'returns errors merge request could not be updated' do
# Make the merge request invalid
merge_request.allow_broken = true
merge_request.update!(source_project: nil)
expect(subject[:errors]).not_to be_empty
end
context 'when passing locked as false' do
let(:locked) { false }
it 'unlocks the discussion' do
merge_request.update(discussion_locked: true)
expect(mutated_merge_request).not_to be_discussion_locked
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::MergeRequests::SetSubscription do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
describe '#resolve' do
let(:subscribe) { true }
let(:mutated_merge_request) { subject[:merge_request] }
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, subscribed_state: subscribe) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the merge request' do
before do
merge_request.project.add_developer(user)
end
it 'returns the merge request as discussion locked' do
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.subscribed?(user, project)).to eq(true)
expect(subject[:errors]).to be_empty
end
context 'when passing subscribe as false' do
let(:subscribe) { false }
it 'unsubscribes from the discussion' do
merge_request.subscribe(user, project)
expect(mutated_merge_request.subscribed?(user, project)).to eq(false)
end
end
end
end
end
Loading
Loading
@@ -3,7 +3,7 @@ require 'spec_helper'
 
describe GitlabSchema.types['Label'] do
it 'has the correct fields' do
expected_fields = [:description, :description_html, :title, :color, :text_color]
expected_fields = [:id, :description, :description_html, :title, :color, :text_color]
 
is_expected.to have_graphql_fields(*expected_fields)
end
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe 'Setting labels of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:label) { create(:label, project: project) }
let(:label2) { create(:label, project: project) }
let(:input) { { label_ids: [GitlabSchema.id_from_object(label).to_s] } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: merge_request.iid.to_s
}
graphql_mutation(:merge_request_set_labels, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
mergeRequest {
id
labels {
nodes {
id
}
}
}
QL
)
end
def mutation_response
graphql_mutation_response(:merge_request_set_labels)
end
def mutation_label_nodes
mutation_response['mergeRequest']['labels']['nodes']
end
before do
project.add_developer(current_user)
end
it 'returns an error if the user is not allowed to update the merge request' do
post_graphql_mutation(mutation, current_user: create(:user))
expect(graphql_errors).not_to be_empty
end
it 'sets the merge request labels, removing existing ones' do
merge_request.update(labels: [label2])
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_label_nodes.count).to eq(1)
expect(mutation_label_nodes[0]['id']).to eq(label.to_global_id.to_s)
end
context 'when passing label_ids empty array as input' do
let(:input) { { label_ids: [] } }
it 'removes the merge request labels' do
merge_request.update!(labels: [label])
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_label_nodes.count).to eq(0)
end
end
context 'when passing operation_mode as APPEND' do
let(:input) { { operation_mode: Types::MutationOperationModeEnum.enum[:append], label_ids: [GitlabSchema.id_from_object(label).to_s] } }
before do
merge_request.update!(labels: [label2])
end
it 'sets the labels, without removing others' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_label_nodes.count).to eq(2)
expect(mutation_label_nodes).to contain_exactly({ 'id' => label.to_global_id.to_s }, { 'id' => label2.to_global_id.to_s })
end
end
context 'when passing operation_mode as REMOVE' do
let(:input) { { operation_mode: Types::MutationOperationModeEnum.enum[:remove], label_ids: [GitlabSchema.id_from_object(label).to_s] } }
before do
merge_request.update!(labels: [label, label2])
end
it 'removes the labels, without removing others' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_label_nodes.count).to eq(1)
expect(mutation_label_nodes[0]['id']).to eq(label2.to_global_id.to_s)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment