Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bf1a77ead7
commit
e9c3815d3d
|
@ -1 +1 @@
|
|||
13.23.2
|
||||
13.24.0
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ export default {
|
|||
},
|
||||
},
|
||||
render() {
|
||||
return this.$slots?.[getExperimentVariant(this.name)];
|
||||
return this.$scopedSlots?.[getExperimentVariant(this.name)]?.();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
02f7a38c7bc19b1a266ac1f1d6631f1922fc135518baeb19ee90bebd7c31c6b9
|
|
@ -0,0 +1 @@
|
|||
f63be8bd42cc1856c92f9073fdb39c58c45806b483d38b91db007a8661c49a97
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,6 +9,7 @@ itself: # project
|
|||
- external_webhook_token
|
||||
- has_external_issue_tracker
|
||||
- has_external_wiki
|
||||
- hidden
|
||||
- import_source
|
||||
- import_type
|
||||
- import_url
|
||||
|
|
|
@ -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
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue