Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-23 09:12:16 +00:00
parent bf1a77ead7
commit e9c3815d3d
44 changed files with 349 additions and 91 deletions

View File

@ -1 +1 @@
13.23.2
13.24.0

View File

@ -167,7 +167,7 @@ export default class Diff {
e.toShowBtn.onclick = () => diff.showOneHideAnother('rendered', e); // eslint-disable-line no-param-reassign
e.toHideBtn.onclick = () => diff.showOneHideAnother('raw', e); // eslint-disable-line no-param-reassign
diff.showOneHideAnother('raw', e);
diff.showOneHideAnother('rendered', e);
});
}

View File

@ -9,7 +9,7 @@ export default {
},
},
render() {
return this.$slots?.[getExperimentVariant(this.name)];
return this.$scopedSlots?.[getExperimentVariant(this.name)]?.();
},
};
</script>

View File

@ -98,17 +98,14 @@ export default {
},
onError(error) {
const { message } = error;
createAlert({ message });
this.reportToSentry(error);
createAlert({ message });
captureException({ error, component: this.$options.name });
},
onSuccess(token) {
this.$toast?.show(s__('Runners|New registration token generated!'));
this.$emit('tokenReset', token);
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
},
};
</script>

View File

@ -116,11 +116,8 @@ export default {
},
onError(error) {
const { message } = error;
createAlert({ message });
this.reportToSentry(error);
},
reportToSentry(error) {
createAlert({ message });
captureException({ error, component: this.$options.name });
},
},

View File

@ -46,7 +46,7 @@ export default {
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
captureException({ error, component: this.$options.name });
},
},
},
@ -62,11 +62,6 @@ export default {
return this.$apollo.queries.jobs.loading;
},
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
},
I18N_NO_JOBS_FOUND,
};
</script>

View File

@ -92,11 +92,8 @@ export default {
},
onError(error) {
const { message } = error;
createAlert({ message });
this.reportToSentry(error);
},
reportToSentry(error) {
createAlert({ message });
captureException({ error, component: this.$options.name });
},
},

View File

@ -55,8 +55,7 @@ export default {
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
captureException({ error, component: this.$options.name });
},
},
},
@ -77,11 +76,6 @@ export default {
});
},
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
},
I18N_NONE,
};
</script>

View File

@ -82,9 +82,9 @@ export default {
this.onSuccess();
} catch (error) {
const { message } = error;
createAlert({ message });
this.reportToSentry(error);
createAlert({ message });
captureException({ error, component: this.$options.name });
} finally {
this.saving = false;
}
@ -93,9 +93,6 @@ export default {
createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
this.model = runnerToModel(this.runner);
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
},
ACCESS_LEVEL_NOT_PROTECTED,
ACCESS_LEVEL_REF_PROTECTED,

View File

@ -8,7 +8,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
if ::Ci::AssignRunnerService.new(@runner, @project, current_user).execute
redirect_to edit_admin_runner_url(@runner), notice: s_('Runners|Runner assigned to project.')
else
redirect_to edit_admin_runner_url(@runner), alert: 'Failed adding runner to project'

View File

@ -212,7 +212,7 @@ class GroupsController < Groups::ApplicationController
def issues
return super if !html_request? || Feature.disabled?(:vue_issues_list, group, default_enabled: :yaml)
@has_issues = IssuesFinder.new(current_user, group_id: group.id).execute
@has_issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true).execute
.non_archived
.exists?

View File

@ -24,11 +24,14 @@ class Projects::ApplicationController < ApplicationController
return unless params[:project_id] || params[:id]
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
@project = find_routable!(Project, path, request.fullpath, extra_authorization_proc: auth_proc)
end
def auth_proc
->(project) { !project.pending_delete? }
end
def build_canonical_path(project)
params[:namespace_id] = project.namespace.to_param
params[:project_id] = project.to_param
@ -89,3 +92,5 @@ class Projects::ApplicationController < ApplicationController
return render_404 unless @project.feature_available?(:issues, current_user)
end
end
Projects::ApplicationController.prepend_mod_with('Projects::ApplicationController')

View File

@ -14,7 +14,7 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
path = project_runners_path(project)
if @runner.assign_to(project, current_user)
if ::Ci::AssignRunnerService.new(@runner, @project, current_user).execute
redirect_to path, notice: s_('Runners|Runner assigned to project.')
else
assign_to_messages = @runner.errors.messages[:assign_to]

View File

@ -526,6 +526,7 @@ class Project < ApplicationRecord
# Scopes
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
scope :not_hidden, -> { where(hidden: false) }
scope :not_aimed_for_deletion, -> { where(marked_for_deletion_at: nil).without_deleted }
scope :with_storage_feature, ->(feature) do
@ -2805,6 +2806,10 @@ class Project < ApplicationRecord
end
end
def pending_delete_or_hidden?
pending_delete? || hidden?
end
private
# overridden in EE

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Ci
class AssignRunnerService
# @param [Ci::Runner] runner the runner to assign to a project
# @param [Project] project the new project to assign the runner to
# @param [User] user the user performing the operation
def initialize(runner, project, user)
@runner = runner
@project = project
@user = user
end
def execute
return false unless @user.present? && @user.can?(:assign_runner, @runner)
@runner.assign_to(@project, @user)
end
end
end

View File

@ -27,8 +27,8 @@
- if diff_file.has_renderable?
.btn-group.gl-ml-3
= diff_mode_swap_button('raw', file_hash)
= diff_mode_swap_button('rendered', file_hash)
= diff_mode_swap_button('raw', file_hash)
- if image_diff && image_replaced
= view_file_button(diff_file.old_content_sha, diff_file.old_path, project, replaced: true)

View File

@ -1,9 +1,3 @@
- sort_title = label_sort_options_hash[@sort] || sort_title_name_desc
.dropdown.inline
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
= sort_title
= sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort
%li
- label_sort_options_hash.each do |value, title|
= sortable_item(title, page_filter_path(sort: value), sort_title)
- label_sort_options = label_sort_options_hash.map { |value, text| { value: value, text: text, href: page_filter_path(sort: value) } }
= gl_redirect_listbox_tag label_sort_options, @sort, data: { right: true }

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddHiddenToProjects < Gitlab::Database::Migration[1.0]
DOWNTIME = false
enable_lock_retries!
def change
add_column :projects, :hidden, :boolean, default: false, null: false # rubocop: disable Migration/AddColumnsToWideTables
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
class UpdateApiIndexesForProjects < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
ARCHIVED_INDEX_NAME = 'idx_projects_api_created_at_id_for_archived'
OLD_ARCHIVED_INDEX_NAME = 'index_projects_api_created_at_id_for_archived'
PUBLIC_AND_ARCHIVED_INDEX_NAME = 'idx_projects_api_created_at_id_for_archived_vis20'
OLD_PUBLIC_AND_ARCHIVED_INDEX_NAME = 'index_projects_api_created_at_id_for_archived_vis20'
INTERNAL_PROJECTS_INDEX_NAME = 'idx_projects_api_created_at_id_for_vis10'
OLD_INTERNAL_PROJECTS_INDEX_NAME = 'index_projects_api_created_at_id_for_vis10'
def up
add_concurrent_index :projects, [:created_at, :id],
where: "archived = true AND pending_delete = false AND hidden = false",
name: ARCHIVED_INDEX_NAME
add_concurrent_index :projects, [:created_at, :id],
where: "archived = true AND visibility_level = 20 AND pending_delete = false AND hidden = false",
name: PUBLIC_AND_ARCHIVED_INDEX_NAME
add_concurrent_index :projects, [:created_at, :id],
where: "visibility_level = 10 AND pending_delete = false AND hidden = false",
name: INTERNAL_PROJECTS_INDEX_NAME
remove_concurrent_index_by_name :projects, OLD_ARCHIVED_INDEX_NAME
remove_concurrent_index_by_name :projects, OLD_PUBLIC_AND_ARCHIVED_INDEX_NAME
remove_concurrent_index_by_name :projects, OLD_INTERNAL_PROJECTS_INDEX_NAME
end
def down
add_concurrent_index :projects, [:created_at, :id],
where: "archived = true AND pending_delete = false",
name: OLD_ARCHIVED_INDEX_NAME
add_concurrent_index :projects, [:created_at, :id],
where: "archived = true AND visibility_level = 20 AND pending_delete = false",
name: OLD_PUBLIC_AND_ARCHIVED_INDEX_NAME
add_concurrent_index :projects, [:created_at, :id],
where: "visibility_level = 10 AND pending_delete = false",
name: OLD_INTERNAL_PROJECTS_INDEX_NAME
remove_concurrent_index_by_name :projects, ARCHIVED_INDEX_NAME
remove_concurrent_index_by_name :projects, PUBLIC_AND_ARCHIVED_INDEX_NAME
remove_concurrent_index_by_name :projects, INTERNAL_PROJECTS_INDEX_NAME
end
end

View File

@ -0,0 +1 @@
02f7a38c7bc19b1a266ac1f1d6631f1922fc135518baeb19ee90bebd7c31c6b9

View File

@ -0,0 +1 @@
f63be8bd42cc1856c92f9073fdb39c58c45806b483d38b91db007a8661c49a97

View File

@ -18918,7 +18918,8 @@ CREATE TABLE projects (
marked_for_deletion_by_user_id integer,
autoclose_referenced_issues boolean,
suggestion_commit_message character varying(255),
project_namespace_id bigint
project_namespace_id bigint,
hidden boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE projects_id_seq
@ -25452,6 +25453,12 @@ CREATE UNIQUE INDEX idx_project_id_payload_key_self_managed_prometheus_alert_eve
CREATE INDEX idx_project_repository_check_partial ON projects USING btree (repository_storage, created_at) WHERE (last_repository_check_at IS NULL);
CREATE INDEX idx_projects_api_created_at_id_for_archived ON projects USING btree (created_at, id) WHERE ((archived = true) AND (pending_delete = false) AND (hidden = false));
CREATE INDEX idx_projects_api_created_at_id_for_archived_vis20 ON projects USING btree (created_at, id) WHERE ((archived = true) AND (visibility_level = 20) AND (pending_delete = false) AND (hidden = false));
CREATE INDEX idx_projects_api_created_at_id_for_vis10 ON projects USING btree (created_at, id) WHERE ((visibility_level = 10) AND (pending_delete = false) AND (hidden = false));
CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_false ON projects USING btree (id, created_at) WHERE ((disable_overriding_approvers_per_merge_request = false) OR (disable_overriding_approvers_per_merge_request IS NULL));
CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_true ON projects USING btree (id, created_at) WHERE (disable_overriding_approvers_per_merge_request = true);
@ -27568,12 +27575,6 @@ CREATE INDEX index_projects_aimed_for_deletion ON projects USING btree (marked_f
CREATE INDEX index_projects_api_created_at_id_desc ON projects USING btree (created_at, id DESC);
CREATE INDEX index_projects_api_created_at_id_for_archived ON projects USING btree (created_at, id) WHERE ((archived = true) AND (pending_delete = false));
CREATE INDEX index_projects_api_created_at_id_for_archived_vis20 ON projects USING btree (created_at, id) WHERE ((archived = true) AND (visibility_level = 20) AND (pending_delete = false));
CREATE INDEX index_projects_api_created_at_id_for_vis10 ON projects USING btree (created_at, id) WHERE ((visibility_level = 10) AND (pending_delete = false));
CREATE INDEX index_projects_api_last_activity_at_id_desc ON projects USING btree (last_activity_at, id DESC);
CREATE INDEX index_projects_api_name_id_desc ON projects USING btree (name, id DESC);

View File

@ -4,12 +4,10 @@ group: Respond
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Alert Management Alerts API **(FREE)**
# Alert Management alerts API **(FREE)**
This is the documentation of Alert Management Alerts API.
NOTE:
This API is limited to metric images. For more API endpoints please refer to the [GraphQL API](graphql/reference/index.md#alertmanagementalert).
The Alert Management alerts API is limited to metric images. For more API endpoints, see the
[GraphQL API](graphql/reference/index.md#alertmanagementalert).
## Upload metric image
@ -48,8 +46,8 @@ GET /projects/:id/alert_management_alerts/:alert_iid/metric_images
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `alert_iid` | integer | yes | The internal ID of a project's alert |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `alert_iid` | integer | yes | The internal ID of a project's alert. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/alert_management_alerts/93/metric_images"

View File

@ -7,7 +7,7 @@ description: 'Learn how to install a GitLab instance on Google Cloud Platform.'
# Installing GitLab on Google Cloud Platform **(FREE SELF)**
This guide will help you install GitLab on a [Google Cloud Platform (GCP)](https://cloud.google.com/) using the official GitLab Linux package. You should customize it to accommodate your needs.
You can install GitLab on a [Google Cloud Platform (GCP)](https://cloud.google.com/) using the official GitLab Linux package. You should customize it to accommodate your needs.
NOTE:
To deploy production-ready GitLab on
@ -19,20 +19,20 @@ the [Cloud native GitLab Helm chart](https://docs.gitlab.com/charts/).
## Prerequisites
There are only two prerequisites in order to install GitLab on GCP:
There are two prerequisites to install GitLab on GCP:
1. You need to have a Google account.
1. You need to sign up for the GCP program. If this is your first time, Google
1. You must have a Google account.
1. You must sign up for the GCP program. If this is your first time, Google
gives you [$300 credit for free](https://console.cloud.google.com/freetrial) to consume over a 60-day period.
Once you have performed those two steps, you can [create a VM](#creating-the-vm).
After you have performed those two steps, you can [create a VM](#creating-the-vm).
## Creating the VM
To deploy GitLab on GCP you first need to create a virtual machine:
To deploy GitLab on GCP you must create a virtual machine:
1. Go to <https://console.cloud.google.com/compute/instances> and log in with your Google credentials.
1. Click on **Create**
1. Select **Create**
![Search for GitLab](img/launch_vm.png)
@ -43,9 +43,9 @@ To deploy GitLab on GCP you first need to create a virtual machine:
![Launch on Compute Engine](img/vm_details.png)
1. To select the size, type, and desired [operating system](../requirements.md#supported-linux-distributions),
click **Change** under `Boot disk`. Click **Select** when finished.
select **Change** under `Boot disk`. select **Select** when finished.
1. As a last step allow HTTP and HTTPS traffic, then click **Create**. The process finishes in a few seconds.
1. As a last step allow HTTP and HTTPS traffic, then select **Create**. The process finishes in a few seconds.
## Installing GitLab
@ -54,7 +54,7 @@ After a few seconds, the instance is created and available to log in. The next s
![Deploy settings](img/vm_created.png)
1. Make a note of the external IP address of the instance, as you will need that in a later step. <!-- using future tense is okay here -->
1. Click on the SSH button to connect to the instance.
1. Select **SSH** under the connect column to connect to the instance.
1. A new window appears, with you logged into the instance.
![GitLab first sign in](img/ssh_terminal.png)
@ -84,7 +84,7 @@ Assuming you have a domain name in your possession and you have correctly
set up DNS to point to the static IP you configured in the previous step,
here's how you configure GitLab to be aware of the change:
1. SSH into the VM. You can easily use the **SSH** button in the Google console
1. SSH into the VM. You can select **SSH** in the Google console
and a new window pops up.
![SSH button](img/vm_created.png)
@ -122,7 +122,7 @@ certificate. Follow the steps in the [Omnibus documentation](https://docs.gitlab
### Configuring the email SMTP settings
You need to configure the email SMTP settings correctly otherwise GitLab cannot send notification emails, like comments, and password changes.
You must configure the email SMTP settings correctly otherwise GitLab cannot send notification emails, like comments, and password changes.
Check the [Omnibus documentation](https://docs.gitlab.com/omnibus/settings/smtp.html#smtp-settings) how to do so.
## Further reading

View File

@ -85,7 +85,10 @@ are included when cloning.
Top-level groups created after August 12, 2021 have delayed project deletion enabled by default.
Projects are permanently deleted after a seven-day delay.
You can disable this by changing the [group setting](../group/index.md#enable-delayed-project-deletion).
If you are on:
- Premium tier and above, you can disable this by changing the [group setting](../group/index.md#enable-delayed-project-deletion).
- Free tier, you cannot disable this setting or restore projects.
## Alternative SSH port

View File

@ -44,7 +44,7 @@ To create a group access token:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Settings > Access Tokens**.
1. Enter a name.
1. Enter a name. The token name is visible to any user with permissions to view the group.
1. Optional. Enter an expiry date for the token. The token will expire on that date at midnight UTC.
1. Select a role for the token.
1. Select the [desired scopes](#scopes-for-a-group-access-token).

View File

@ -292,6 +292,7 @@ In these issues, you can also see our friendly neighborhood [Support Bot](#suppo
> Support for additional email headers [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346600) in GitLab 14.6.
> In earlier versions, the Service Desk email address had to be in the "To" field.
To create a Service Desk issue, an end user does not need to know anything about
the GitLab instance. They just send an email to the address they are given, and
receive an email back confirming receipt:
@ -327,7 +328,14 @@ You can read and write comments as you normally do in GitLab:
Note that:
- The project's visibility (private, internal, public) does not affect Service Desk.
- The path to the project, including its group or namespace, are shown in emails.
- The path to the project, including its group or namespace, is shown in emails.
#### Privacy considerations
Service Desk issues are confidential, but the project owner can
[make an issue public](issues/confidential_issues.md#modify-issue-confidentiality).
When a Service Desk issue becomes public, the issue creator's email address is disclosed
to everyone who can view the project.
### Support Bot user

View File

@ -187,7 +187,7 @@ module API
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
if runner.assign_to(user_project)
if ::Ci::AssignRunnerService.new(runner, user_project, current_user).execute
present runner, with: Entities::Ci::Runner
else
render_validation_error!(runner)

View File

@ -38,6 +38,10 @@ module API
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
require_packages_enabled!
@ -285,6 +289,7 @@ module API
params do
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do
detail 'This feature was introduced in GitLab 12.6'

View File

@ -119,7 +119,7 @@ module API
def find_project(id)
return unless id
projects = Project.without_deleted
projects = Project.without_deleted.not_hidden
if id.is_a?(Integer) || id =~ /^\d+$/
projects.find_by(id: id)

View File

@ -99,6 +99,9 @@ module Gitlab
# ^--+--+- components of hashed storage project path
cmd += %w[-mindepth 6 -maxdepth 6]
# Intentionally exclude pipeline artifacts which match the same path
cmd += %w[-not -path */pipelines/*]
# Artifact directories are named on their ID
cmd += %w[-type d]

View File

@ -37176,6 +37176,9 @@ msgstr ""
msgid "This action deletes %{codeOpen}%{project_path_with_namespace}%{codeClose} on %{date} and everything this project contains."
msgstr ""
msgid "This action deletes %{codeOpen}%{project_path_with_namespace}%{codeClose} on %{date} and everything this project contains. %{strongOpen}There is no going back.%{strongClose}"
msgstr ""
msgid "This action will %{strongOpen}permanently remove%{strongClose} %{codeOpen}%{group}%{codeClose} %{strongOpen}immediately%{strongClose}."
msgstr ""

View File

@ -154,6 +154,10 @@ FactoryBot.define do
archived { true }
end
trait :hidden do
hidden { true }
end
trait :last_repository_check_failed do
last_repository_check_failed { true }
end

View File

@ -108,6 +108,22 @@ RSpec.describe 'Group issues page' do
end
end
context 'group with no issues', :js do
let!(:group_with_no_issues) { create(:group) }
let!(:subgroup_with_issues) { create(:group, parent: group_with_no_issues) }
let!(:subgroup_project) { create(:project, :public, group: subgroup_with_issues) }
let!(:subgroup_issue) { create(:issue, project: subgroup_project) }
before do
stub_feature_flags(vue_issues_list: true)
visit issues_group_path(group_with_no_issues)
end
it 'shows issues from subgroups on issues list' do
expect(page).to have_text subgroup_issue.title
end
end
context 'projects with issues disabled' do
describe 'issue dropdown' do
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }

View File

@ -28,7 +28,7 @@ RSpec.describe 'Sort labels', :js do
it 'sorts by date' do
click_button 'Name'
sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
sort_options = find('ul.dropdown-menu').all('li').collect(&:text)
expect(sort_options[0]).to eq('Name')
expect(sort_options[1]).to eq('Name, descending')
@ -37,7 +37,7 @@ RSpec.describe 'Sort labels', :js do
expect(sort_options[4]).to eq('Updated date')
expect(sort_options[5]).to eq('Oldest updated')
click_link 'Name, descending'
click_button 'Name, descending'
# assert default sorting
within '.other-labels' do

View File

@ -56,14 +56,14 @@ RSpec.describe 'Multiple view Diffs', :js do
it 'loads the rendered diff as hidden' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff).to have_selector '[data-diff-toggle-entity="toHide"]'
expect(diff).not_to have_selector '[data-diff-toggle-entity="toShow"]'
expect(diff).not_to have_selector '[data-diff-toggle-entity="toHide"]'
expect(diff).to have_selector '[data-diff-toggle-entity="toShow"]'
expect(classes_for_element(diff, 'toShow', visible: false)).to include('hidden')
expect(classes_for_element(diff, 'toHide')).not_to include('hidden')
expect(classes_for_element(diff, 'toHide', visible: false)).to include('hidden')
expect(classes_for_element(diff, 'toShow')).not_to include('hidden')
expect(classes_for_element(diff, 'toHideBtn')).to include('selected')
expect(classes_for_element(diff, 'toShowBtn')).not_to include('selected')
expect(classes_for_element(diff, 'toShowBtn')).to include('selected')
expect(classes_for_element(diff, 'toHideBtn')).not_to include('selected')
end
it 'displays the rendered diff and hides after selection changes' do

View File

@ -28,7 +28,7 @@ RSpec.describe 'Sort labels', :js do
it 'sorts by date' do
click_button 'Name'
sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
sort_options = find('ul.dropdown-menu').all('li').collect(&:text)
expect(sort_options[0]).to eq('Name')
expect(sort_options[1]).to eq('Name, descending')
@ -37,7 +37,7 @@ RSpec.describe 'Sort labels', :js do
expect(sort_options[4]).to eq('Updated date')
expect(sort_options[5]).to eq('Oldest updated')
click_link 'Name, descending'
click_button 'Name, descending'
# assert default sorting
within '.other-labels' do

View File

@ -109,6 +109,26 @@ RSpec.describe API::Helpers do
end
end
end
context 'when project is pending delete' do
let(:project_pending_delete) { create(:project, pending_delete: true) }
it 'does not return the project pending delete' do
expect(Project).not_to receive(:find_by_full_path)
expect(subject.find_project(project_pending_delete.id)).to be_nil
end
end
context 'when project is hidden' do
let(:hidden_project) { create(:project, :hidden) }
it 'does not return the hidden project' do
expect(Project).not_to receive(:find_by_full_path)
expect(subject.find_project(hidden_project.id)).to be_nil
end
end
end
describe '#find_project!' do

View File

@ -256,4 +256,23 @@ RSpec.describe Banzai::Filter::References::ExternalIssueReferenceFilter do
it_behaves_like "external issue tracker"
end
end
context 'checking N+1' do
let_it_be(:integration) { create(:redmine_integration, project: project) }
let_it_be(:issue1) { ExternalIssue.new("#123", project) }
let_it_be(:issue2) { ExternalIssue.new("YT-123", project) }
before do
project.update!(issues_enabled: false)
end
it 'does not have N+1 per multiple references per project', :use_sql_query_cache do
single_reference = "External Issue #{issue1.to_reference}"
multiple_references = "External Issues #{issue1.to_reference} and #{issue2.to_reference}"
control_count = ActiveRecord::QueryRecorder.new { reference_filter(single_reference).to_html }.count
expect { reference_filter(multiple_references).to_html }.not_to exceed_query_limit(control_count)
end
end
end

View File

@ -34,10 +34,33 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
cleanup.run!
end
it 'finds artifacts on disk' do
it 'finds job artifacts on disk' do
artifact = create(:ci_job_artifact, :archive)
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
cleaned = []
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
original_method.call(*args) { |dir| cleaned << dir }
end
cleanup.run!
expect(cleaned).to include(/#{artifact_directory}/)
end
it 'does not find pipeline artifacts on disk' do
artifact = create(:ci_pipeline_artifact, :with_coverage_report)
# using 0...6 to match the -min/maxdepth 6 strictly, since this is one directory
# deeper than job artifacts, and .dirname would not match
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
# this can either _not_ yield at all, or yield with any other file
# except the one that we're explicitly excluding
original_method.call(*args) { |path| expect(path).not_to match(artifact_directory) }
end
expect(cleanup).to receive(:find_artifacts).and_yield(artifact.file.path)
cleanup.run!
end

View File

@ -8001,6 +8001,37 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '.not_hidden' do
it 'lists projects that are not hidden' do
project = create(:project)
hidden_project = create(:project, :hidden)
expect(described_class.not_hidden).to contain_exactly(project)
expect(described_class.not_hidden).not_to include(hidden_project)
end
end
describe '#pending_delete_or_hidden?' do
let_it_be(:project) { create(:project, name: 'test-project') }
where(:pending_delete, :hidden, :expected_result) do
true | false | true
true | true | true
false | true | true
false | false | false
end
with_them do
it 'returns true if project is pending delete or hidden' do
project.pending_delete = pending_delete
project.hidden = hidden
project.save!
expect(project.pending_delete_or_hidden?).to eq(expected_result)
end
end
end
private
def finish_job(export_job)

View File

@ -9,6 +9,7 @@ itself: # project
- external_webhook_token
- has_external_issue_tracker
- has_external_wiki
- hidden
- import_source
- import_type
- import_url

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ci::AssignRunnerService, '#execute' do
subject { described_class.new(runner, project, user).execute }
let_it_be(:runner) { build(:ci_runner) }
let_it_be(:project) { build(:project) }
context 'without user' do
let(:user) { nil }
it 'does not call assign_to on runner and returns false' do
expect(runner).not_to receive(:assign_to)
is_expected.to eq(false)
end
end
context 'with unauthorized user' do
let(:user) { build(:user) }
it 'does not call assign_to on runner and returns false' do
expect(runner).not_to receive(:assign_to)
is_expected.to eq(false)
end
end
context 'with admin user', :enable_admin_mode do
let(:user) { create_default(:user, :admin) }
it 'calls assign_to on runner and returns value unchanged' do
expect(runner).to receive(:assign_to).with(project, user).once.and_return('assign_to return value')
is_expected.to eq('assign_to return value')
end
end
end

View File

@ -178,6 +178,25 @@ RSpec.shared_examples 'rejects invalid recipe' do
end
end
RSpec.shared_examples 'handling validation error for package' do
context 'with validation error' do
before do
allow_next_instance_of(Packages::Package) do |instance|
instance.errors.add(:base, 'validation error')
allow(instance).to receive(:valid?).and_return(false)
end
end
it 'returns 400' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to include('Validation failed')
end
end
end
RSpec.shared_examples 'handling empty values for username and channel' do
using RSpec::Parameterized::TableSyntax
@ -678,6 +697,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
it_behaves_like 'handling empty values for username and channel'
it_behaves_like 'handling validation error for package'
end
RSpec.shared_examples 'workhorse package file upload endpoint' do
@ -700,6 +720,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
it_behaves_like 'handling empty values for username and channel'
it_behaves_like 'handling validation error for package'
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }