Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 15:09:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 15:09:23 +0300
commit0b194c4854f312e36616fccf7c610cb2b0ec6957 (patch)
treef4c3d8ed1cd799e50b979035506675a1d90ad4a9 /app
parentc1e7698dff17b737299127ecf484443e676cdd4f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table.vue7
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue10
-rw-r--r--app/assets/javascripts/repository/components/blob_button_group.vue30
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue4
-rw-r--r--app/assets/javascripts/repository/components/delete_blob_modal.vue151
-rw-r--r--app/assets/javascripts/repository/constants.js7
-rw-r--r--app/assets/javascripts/repository/queries/blob_info.query.graphql1
-rw-r--r--app/assets/javascripts/vuex_shared/bindings.js6
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss2
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss2
-rw-r--r--app/controllers/projects/blob_controller.rb16
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/helpers/clusters_helper.rb6
-rw-r--r--app/helpers/search_helper.rb15
-rw-r--r--app/models/award_emoji.rb14
-rw-r--r--app/models/issue.rb5
-rw-r--r--app/models/merge_request/cleanup_schedule.rb55
-rw-r--r--app/models/project.rb1
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/admin/application_settings/_usage.html.haml4
-rw-r--r--app/views/admin/dev_ops_report/_callout.html.haml2
-rw-r--r--app/views/projects/_invite_members_empty_project.html.haml (renamed from app/views/projects/_invite_members.html.haml)1
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/search/results/_issuable.html.haml33
-rw-r--r--app/workers/merge_request_cleanup_refs_worker.rb56
-rw-r--r--app/workers/schedule_merge_request_cleanup_refs_worker.rb13
26 files changed, 387 insertions, 62 deletions
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index 3daa5eebcb6..cb7e3ef9632 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -227,7 +227,12 @@ export default {
</template>
</gl-sprintf>
</span>
- <gl-search-box-by-click class="gl-ml-auto" @submit="filter = $event" @clear="filter = ''" />
+ <gl-search-box-by-click
+ class="gl-ml-auto"
+ :placeholder="s__('BulkImport|Filter by source group')"
+ @submit="filter = $event"
+ @clear="filter = ''"
+ />
</div>
<gl-loading-icon v-if="$apollo.loading" size="md" class="gl-mt-5" />
<template v-else>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
index 1c4413bef71..0b0560f63c1 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
@@ -225,11 +225,21 @@ export default {
{
name: 'success',
data: this.mergeLabelsAndValues(labels, success),
+ areaStyle: {
+ color: this.$options.successColor,
+ },
+ lineStyle: {
+ color: this.$options.successColor,
+ },
+ itemStyle: {
+ color: this.$options.successColor,
+ },
},
],
};
},
},
+ successColor: '#608b2f',
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
height: INNER_CHART_HEIGHT,
diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue
index 424dc4529ff..273825b996a 100644
--- a/app/assets/javascripts/repository/components/blob_button_group.vue
+++ b/app/assets/javascripts/repository/components/blob_button_group.vue
@@ -3,6 +3,7 @@ import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { sprintf, __ } from '~/locale';
import getRefMixin from '../mixins/get_ref';
+import DeleteBlobModal from './delete_blob_modal.vue';
import UploadBlobModal from './upload_blob_modal.vue';
export default {
@@ -15,6 +16,7 @@ export default {
GlButtonGroup,
GlButton,
UploadBlobModal,
+ DeleteBlobModal,
},
directives: {
GlModal: GlModalDirective,
@@ -41,10 +43,18 @@ export default {
type: String,
required: true,
},
+ deletePath: {
+ type: String,
+ required: true,
+ },
canPushCode: {
type: Boolean,
required: true,
},
+ emptyRepo: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
replaceModalId() {
@@ -53,6 +63,12 @@ export default {
replaceModalTitle() {
return sprintf(__('Replace %{name}'), { name: this.name });
},
+ deleteModalId() {
+ return uniqueId('delete-modal');
+ },
+ deleteModalTitle() {
+ return sprintf(__('Delete %{name}'), { name: this.name });
+ },
},
};
</script>
@@ -63,7 +79,9 @@ export default {
<gl-button v-gl-modal="replaceModalId">
{{ $options.i18n.replace }}
</gl-button>
- <gl-button>{{ $options.i18n.delete }}</gl-button>
+ <gl-button v-gl-modal="deleteModalId">
+ {{ $options.i18n.delete }}
+ </gl-button>
</gl-button-group>
<upload-blob-modal
:modal-id="replaceModalId"
@@ -76,5 +94,15 @@ export default {
:replace-path="replacePath"
:primary-btn-text="$options.i18n.replacePrimaryBtnText"
/>
+ <delete-blob-modal
+ :modal-id="deleteModalId"
+ :modal-title="deleteModalTitle"
+ :delete-path="deletePath"
+ :commit-message="deleteModalTitle"
+ :target-branch="targetBranch || ref"
+ :original-branch="originalBranch || ref"
+ :can-push-code="canPushCode"
+ :empty-repo="emptyRepo"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index c3876a77ec4..09ac60c94c7 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -69,6 +69,7 @@ export default {
pushCode: false,
},
repository: {
+ empty: true,
blobs: {
nodes: [
{
@@ -92,6 +93,7 @@ export default {
forkPath: '',
simpleViewer: {},
richViewer: null,
+ webPath: '',
},
],
},
@@ -174,7 +176,9 @@ export default {
:path="path"
:name="blobInfo.name"
:replace-path="blobInfo.replacePath"
+ :delete-path="blobInfo.webPath"
:can-push-code="project.userPermissions.pushCode"
+ :empty-repo="project.repository.empty"
/>
</template>
</blob-header>
diff --git a/app/assets/javascripts/repository/components/delete_blob_modal.vue b/app/assets/javascripts/repository/components/delete_blob_modal.vue
new file mode 100644
index 00000000000..6599d99d7bd
--- /dev/null
+++ b/app/assets/javascripts/repository/components/delete_blob_modal.vue
@@ -0,0 +1,151 @@
+<script>
+import { GlModal, GlFormGroup, GlFormInput, GlFormTextarea, GlToggle } from '@gitlab/ui';
+import csrf from '~/lib/utils/csrf';
+import { __ } from '~/locale';
+import {
+ SECONDARY_OPTIONS_TEXT,
+ COMMIT_LABEL,
+ TARGET_BRANCH_LABEL,
+ TOGGLE_CREATE_MR_LABEL,
+} from '../constants';
+
+export default {
+ csrf,
+ components: {
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlFormTextarea,
+ GlToggle,
+ },
+ i18n: {
+ PRIMARY_OPTIONS_TEXT: __('Delete file'),
+ SECONDARY_OPTIONS_TEXT,
+ COMMIT_LABEL,
+ TARGET_BRANCH_LABEL,
+ TOGGLE_CREATE_MR_LABEL,
+ },
+ props: {
+ modalId: {
+ type: String,
+ required: true,
+ },
+ modalTitle: {
+ type: String,
+ required: true,
+ },
+ deletePath: {
+ type: String,
+ required: true,
+ },
+ commitMessage: {
+ type: String,
+ required: true,
+ },
+ targetBranch: {
+ type: String,
+ required: true,
+ },
+ originalBranch: {
+ type: String,
+ required: true,
+ },
+ canPushCode: {
+ type: Boolean,
+ required: true,
+ },
+ emptyRepo: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ commit: this.commitMessage,
+ target: this.targetBranch,
+ createNewMr: true,
+ error: '',
+ };
+ },
+ computed: {
+ primaryOptions() {
+ return {
+ text: this.$options.i18n.PRIMARY_OPTIONS_TEXT,
+ attributes: [
+ {
+ variant: 'danger',
+ loading: this.loading,
+ disabled: !this.formCompleted || this.loading,
+ },
+ ],
+ };
+ },
+ cancelOptions() {
+ return {
+ text: this.$options.i18n.SECONDARY_OPTIONS_TEXT,
+ attributes: [
+ {
+ disabled: this.loading,
+ },
+ ],
+ };
+ },
+ showCreateNewMrToggle() {
+ return this.canPushCode && this.target !== this.originalBranch;
+ },
+ formCompleted() {
+ return this.commit && this.target;
+ },
+ },
+ methods: {
+ submitForm(e) {
+ e.preventDefault(); // Prevent modal from closing
+ this.loading = true;
+ this.$refs.form.submit();
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ :modal-id="modalId"
+ :title="modalTitle"
+ :action-primary="primaryOptions"
+ :action-cancel="cancelOptions"
+ @primary="submitForm"
+ >
+ <form ref="form" :action="deletePath" method="post">
+ <input type="hidden" name="_method" value="delete" />
+ <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
+ <template v-if="emptyRepo">
+ <!-- Once "empty_repo_upload_experiment" is made available, will need to add class 'js-branch-name'
+ Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/335721 -->
+ <input type="hidden" name="branch_name" :value="originalBranch" />
+ </template>
+ <template v-else>
+ <input type="hidden" name="original_branch" :value="originalBranch" />
+ <!-- Once "push to branch" permission is made available, will need to add to conditional
+ Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/335462 -->
+ <input v-if="createNewMr" type="hidden" name="create_merge_request" value="1" />
+ <gl-form-group :label="$options.i18n.COMMIT_LABEL" label-for="commit_message">
+ <gl-form-textarea v-model="commit" name="commit_message" :disabled="loading" />
+ </gl-form-group>
+ <gl-form-group
+ v-if="canPushCode"
+ :label="$options.i18n.TARGET_BRANCH_LABEL"
+ label-for="branch_name"
+ >
+ <gl-form-input v-model="target" :disabled="loading" name="branch_name" />
+ </gl-form-group>
+ <gl-toggle
+ v-if="showCreateNewMrToggle"
+ v-model="createNewMr"
+ :disabled="loading"
+ :label="$options.i18n.TOGGLE_CREATE_MR_LABEL"
+ />
+ </template>
+ </form>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index 22349261d3c..2d2faa8d9f3 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -1,3 +1,10 @@
+import { __ } from '~/locale';
+
export const TREE_PAGE_LIMIT = 1000; // the maximum amount of items per page
export const TREE_PAGE_SIZE = 100; // the amount of items to be fetched per (batch) request
export const TREE_INITIAL_FETCH_COUNT = TREE_PAGE_LIMIT / TREE_PAGE_SIZE; // the amount of (batch) requests to make
+
+export const SECONDARY_OPTIONS_TEXT = __('Cancel');
+export const COMMIT_LABEL = __('Commit message');
+export const TARGET_BRANCH_LABEL = __('Target branch');
+export const TOGGLE_CREATE_MR_LABEL = __('Start a new merge request with these changes');
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 1889f2269f5..a8f263941e2 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -4,6 +4,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) {
pushCode
}
repository {
+ empty
blobs(paths: [$filePath]) {
nodes {
webPath
diff --git a/app/assets/javascripts/vuex_shared/bindings.js b/app/assets/javascripts/vuex_shared/bindings.js
index 741690886b7..bc3741a3880 100644
--- a/app/assets/javascripts/vuex_shared/bindings.js
+++ b/app/assets/javascripts/vuex_shared/bindings.js
@@ -6,7 +6,7 @@
* @param {string} list[].getter - the name of the getter, leave it empty to not use a getter
* @param {string} list[].updateFn - the name of the action, leave it empty to use the default action
* @param {string} defaultUpdateFn - the default function to dispatch
- * @param {string} root - the key of the state where to search fo they keys described in list
+ * @param {string|function} root - the key of the state where to search for the keys described in list
* @returns {Object} a dictionary with all the computed properties generated
*/
export const mapComputed = (list, defaultUpdateFn, root) => {
@@ -21,6 +21,10 @@ export const mapComputed = (list, defaultUpdateFn, root) => {
if (getter) {
return this.$store.getters[getter];
} else if (root) {
+ if (typeof root === 'function') {
+ return root(this.$store.state)[key];
+ }
+
return this.$store.state[root][key];
}
return this.$store.state[key];
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 79b69e11b35..a497f56f3b8 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -1673,7 +1673,7 @@ body.gl-dark .nav-sidebar .fly-out-top-item a,
body.gl-dark .nav-sidebar .fly-out-top-item.active a,
body.gl-dark .nav-sidebar .fly-out-top-item .fly-out-top-item-container {
background-color: #2f2a6b;
- color: #333;
+ color: var(--black, #333);
}
body.gl-dark .logo-text svg {
fill: var(--gl-text-color);
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index 45fbd8607fc..a94169ab494 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -185,7 +185,7 @@
&.active a,
.fly-out-top-item-container {
background-color: $purple-900;
- color: $white;
+ color: var(--black, $white);
}
}
}
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index c6c9237292d..08066acb45c 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -23,6 +23,10 @@ class Projects::BlobController < Projects::ApplicationController
# We need to assign the blob vars before `authorize_edit_tree!` so we can
# validate access to a specific ref.
before_action :assign_blob_vars
+
+ # Since BlobController doesn't use assign_ref_vars, we have to call this explicitly
+ before_action :rectify_renamed_default_branch!, only: [:show]
+
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
before_action :commit, except: [:new, :create]
@@ -140,11 +144,15 @@ class Projects::BlobController < Projects::ApplicationController
end
def commit
- @commit = @repository.commit(@ref)
+ @commit ||= @repository.commit(@ref)
return render_404 unless @commit
end
+ def redirect_renamed_default_branch?
+ action_name == 'show'
+ end
+
def assign_blob_vars
@id = params[:id]
@ref, @path = extract_ref(@id)
@@ -152,6 +160,12 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
+ def rectify_renamed_default_branch!
+ @commit ||= @repository.commit(@ref)
+
+ super
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: params[:from_merge_request_iid])
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index b5cfc3990b2..475c9de2503 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -39,6 +39,10 @@ class Projects::TreeController < Projects::ApplicationController
private
+ def redirect_renamed_default_branch?
+ action_name == 'show'
+ end
+
def assign_dir_vars
@branch_name = params[:branch_name]
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 14783882f5e..e9a75babb97 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -20,7 +20,11 @@ module ClustersHelper
{
default_branch_name: clusterable_project.default_branch,
empty_state_image: image_path('illustrations/clusters_empty.svg'),
- project_path: clusterable_project.full_path
+ project_path: clusterable_project.full_path,
+ agent_docs_url: help_page_path('user/clusters/agent/index'),
+ install_docs_url: help_page_path('administration/clusters/kas'),
+ get_started_docs_url: help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository'),
+ integration_docs_url: help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent')
}
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 1cbde1871d4..ec8ed3d6e7f 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -131,7 +131,7 @@ module SearchHelper
end
def search_sort_options
- [
+ options = [
{
title: _('Created date'),
sortable: true,
@@ -149,6 +149,19 @@ module SearchHelper
}
}
]
+
+ if search_service.scope == 'issues' && Feature.enabled?(:search_sort_issues_by_popularity)
+ options << {
+ title: _('Popularity'),
+ sortable: true,
+ sortParam: {
+ asc: 'popularity_asc',
+ desc: 'popularity_desc'
+ }
+ }
+ end
+
+ options
end
private
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index dc37d73df85..c8f6b9aaedb 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -27,9 +27,6 @@ class AwardEmoji < ApplicationRecord
after_save :expire_cache
after_destroy :expire_cache
- after_save :update_awardable_upvotes_count
- after_destroy :update_awardable_upvotes_count
-
class << self
def votes_for_collection(ids, type)
select('name', 'awardable_id', 'COUNT(*) as count')
@@ -66,15 +63,6 @@ class AwardEmoji < ApplicationRecord
def expire_cache
awardable.try(:bump_updated_at)
awardable.try(:expire_etag_cache)
- end
-
- private
-
- def update_awardable_upvotes_count
- return unless upvote? && awardable.has_attribute?(:upvotes_count)
-
- awardable.update_column(:upvotes_count, awardable.upvotes)
+ awardable.try(:update_upvotes_count) if upvote?
end
end
-
-AwardEmoji.prepend_mod_with('AwardEmoji')
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 3b236620ed6..7926c4be489 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -520,6 +520,11 @@ class Issue < ApplicationRecord
issue_assignees.pluck(:user_id)
end
+ def update_upvotes_count
+ self.lock!
+ self.update_column(:upvotes_count, self.upvotes)
+ end
+
private
def spammable_attribute_changed?
diff --git a/app/models/merge_request/cleanup_schedule.rb b/app/models/merge_request/cleanup_schedule.rb
index 79817269be2..35194b2b318 100644
--- a/app/models/merge_request/cleanup_schedule.rb
+++ b/app/models/merge_request/cleanup_schedule.rb
@@ -1,14 +1,61 @@
# frozen_string_literal: true
class MergeRequest::CleanupSchedule < ApplicationRecord
+ STATUSES = {
+ unstarted: 0,
+ running: 1,
+ completed: 2,
+ failed: 3
+ }.freeze
+
belongs_to :merge_request, inverse_of: :cleanup_schedule
validates :scheduled_at, presence: true
- def self.scheduled_merge_request_ids(limit)
- where('completed_at IS NULL AND scheduled_at <= NOW()')
+ state_machine :status, initial: :unstarted do
+ state :unstarted, value: STATUSES[:unstarted]
+ state :running, value: STATUSES[:running]
+ state :completed, value: STATUSES[:completed]
+ state :failed, value: STATUSES[:failed]
+
+ event :run do
+ transition unstarted: :running
+ end
+
+ event :retry do
+ transition running: :unstarted
+ end
+
+ event :complete do
+ transition running: :completed
+ end
+
+ event :mark_as_failed do
+ transition running: :failed
+ end
+
+ before_transition to: [:completed] do |cleanup_schedule, _transition|
+ cleanup_schedule.completed_at = Time.current
+ end
+
+ before_transition from: :running, to: [:unstarted, :failed] do |cleanup_schedule, _transition|
+ cleanup_schedule.failed_count += 1
+ end
+ end
+
+ scope :scheduled_and_unstarted, -> {
+ where('completed_at IS NULL AND scheduled_at <= NOW() AND status = ?', STATUSES[:unstarted])
.order('scheduled_at DESC')
- .limit(limit)
- .pluck(:merge_request_id)
+ }
+
+ def self.start_next
+ MergeRequest::CleanupSchedule.transaction do
+ cleanup_schedule = scheduled_and_unstarted.lock('FOR UPDATE SKIP LOCKED').first
+
+ next if cleanup_schedule.blank?
+
+ cleanup_schedule.run!
+ cleanup_schedule
+ end
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e850494ab27..21d5b083476 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -416,6 +416,7 @@ class Project < ApplicationRecord
prefix: :import, to: :import_state, allow_nil: true
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
delegate :squash_option, to: :project_setting
+ delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 4351a66351d..d6e7f165d72 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -66,6 +66,8 @@ module Projects
previous_default_branch = project.default_branch
if project.change_head(params[:default_branch])
+ params[:previous_default_branch] = previous_default_branch
+
after_default_branch_change(previous_default_branch)
else
raise ValidationError, s_("UpdateProject|Could not set the default branch")
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index 206d5edbf84..f45e6c5e8e9 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -31,8 +31,8 @@
.js-text.d-inline= _('Preview payload')
%pre.service-data-payload-container.js-syntax-highlight.code.highlight.mt-2.d-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- else
- = _('Service ping is disabled, and cannot be configured through this form.')
- - deactivating_service_ping_path = help_page_path('development/service_ping/index.md', anchor: 'disable-service-ping')
+ = _('Service ping is disabled in your configuration file, and cannot be enabled through this form.')
+ - deactivating_service_ping_path = help_page_path('development/service_ping/index.md', anchor: 'disable-service-ping-using-the-configuration-file')
- deactivating_service_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_service_ping_path }
= s_('For more information, see the documentation on %{deactivating_service_ping_link_start}deactivating service ping%{deactivating_service_ping_link_end}.').html_safe % { deactivating_service_ping_link_start: deactivating_service_ping_link_start, deactivating_service_ping_link_end: '</a>'.html_safe }
.form-group
diff --git a/app/views/admin/dev_ops_report/_callout.html.haml b/app/views/admin/dev_ops_report/_callout.html.haml
index f313865478d..2b4c258a00c 100644
--- a/app/views/admin/dev_ops_report/_callout.html.haml
+++ b/app/views/admin/dev_ops_report/_callout.html.haml
@@ -8,6 +8,6 @@
%h4
= _('Introducing Your DevOps Report')
%p
- = _('Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers.')
+ = _('Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. Use it to view how you compare with other organizations.')
.svg-container.devops
= custom_icon('dev_ops_report_overview')
diff --git a/app/views/projects/_invite_members.html.haml b/app/views/projects/_invite_members_empty_project.html.haml
index fc292da6fcf..ee2215b0fbb 100644
--- a/app/views/projects/_invite_members.html.haml
+++ b/app/views/projects/_invite_members_empty_project.html.haml
@@ -6,6 +6,7 @@
.js-invite-members-trigger{ data: { variant: 'confirm',
classes: 'gl-mb-8 gl-xs-w-full',
display_text: s_('InviteMember|Invite members'),
+ trigger_source: 'project-empty-page',
event: 'click_button',
label: 'invite_members_empty_project' } }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 027b81d6c68..0fda74a3be5 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,7 +7,7 @@
= render "home_panel"
= render "archived_notice", project: @project
-= render "invite_members" if can_import_members?
+= render 'invite_members_empty_project' if can_import_members?
%h4.gl-mt-0.gl-mb-3
= _('The repository for this project is empty')
diff --git a/app/views/search/results/_issuable.html.haml b/app/views/search/results/_issuable.html.haml
index da0adba88db..551f5c048bc 100644
--- a/app/views/search/results/_issuable.html.haml
+++ b/app/views/search/results/_issuable.html.haml
@@ -1,14 +1,19 @@
-%div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' }
- %span.gl-display-flex.gl-align-items-center
- %span.badge.badge-pill.gl-badge.sm{ class: "badge-#{issuable_state_to_badge_class(issuable)}" }= issuable_state_text(issuable)
- = sprite_icon('eye-slash', css_class: 'gl-text-gray-500 gl-ml-2') if issuable.respond_to?(:confidential?) && issuable.confidential?
- = link_to issuable_path(issuable), data: { track_event: 'click_text', track_label: "#{issuable.class.name.downcase}_title", track_property: 'search_result' }, class: 'gl-w-full' do
- %span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issuable.title
- .gl-text-gray-500.gl-my-3
- = issuable_project_reference(issuable)
- &middot;
- = sprintf(s_('created %{issuable_created} by %{author}'), { issuable_created: time_ago_with_tooltip(issuable.created_at, placement: 'bottom'), author: link_to_member(@project, issuable.author, avatar: false) }).html_safe
- &middot;
- = sprintf(s_('updated %{time_ago}'), { time_ago: time_ago_with_tooltip(issuable.updated_at, placement: 'bottom') }).html_safe
- .description.term.col-sm-10.gl-px-0
- = highlight_and_truncate_issuable(issuable, @search_term, @search_highlight)
+%div{ class: 'search-result-row gl-display-flex gl-sm-flex-direction-row gl-flex-direction-column gl-align-items-center gl-pb-3! gl-mt-5 gl-mb-0!' }
+ .col-sm-9
+ %span.gl-display-flex.gl-align-items-center
+ %span.badge.badge-pill.gl-badge.sm{ class: "badge-#{issuable_state_to_badge_class(issuable)}" }= issuable_state_text(issuable)
+ = sprite_icon('eye-slash', css_class: 'gl-text-gray-500 gl-ml-2') if issuable.respond_to?(:confidential?) && issuable.confidential?
+ = link_to issuable_path(issuable), data: { track_event: 'click_text', track_label: "#{issuable.class.name.downcase}_title", track_property: 'search_result' }, class: 'gl-w-full' do
+ %span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issuable.title
+ .gl-text-gray-500.gl-my-3
+ = issuable_project_reference(issuable)
+ &middot;
+ = sprintf(s_('created %{issuable_created} by %{author}'), { issuable_created: time_ago_with_tooltip(issuable.created_at, placement: 'bottom'), author: link_to_member(@project, issuable.author, avatar: false) }).html_safe
+ .description.term.gl-px-0
+ = highlight_and_truncate_issuable(issuable, @search_term, @search_highlight)
+ .col-sm-3.gl-mt-3.gl-sm-mt-0.gl-text-right
+ - if Feature.enabled?(:search_sort_issues_by_popularity) && issuable.respond_to?(:upvotes_count) && issuable.upvotes_count > 0
+ %li.issuable-upvotes.gl-list-style-none.has-tooltip{ title: _('Upvotes') }
+ = sprite_icon('thumb-up', css_class: "gl-vertical-align-middle")
+ = issuable.upvotes_count
+ %span.gl-text-gray-500= sprintf(s_('updated %{time_ago}'), { time_ago: time_ago_with_tooltip(issuable.updated_at, placement: 'bottom') }).html_safe
diff --git a/app/workers/merge_request_cleanup_refs_worker.rb b/app/workers/merge_request_cleanup_refs_worker.rb
index 162c6dc2a88..408d070d56f 100644
--- a/app/workers/merge_request_cleanup_refs_worker.rb
+++ b/app/workers/merge_request_cleanup_refs_worker.rb
@@ -2,6 +2,8 @@
class MergeRequestCleanupRefsWorker
include ApplicationWorker
+ include LimitedCapacity::Worker
+ include Gitlab::Utils::StrongMemoize
sidekiq_options retry: 3
@@ -9,20 +11,60 @@ class MergeRequestCleanupRefsWorker
tags :exclude_from_kubernetes
idempotent!
- def perform(merge_request_id)
- return unless Feature.enabled?(:merge_request_refs_cleanup, default_enabled: false)
+ # Hard-coded to 4 for now. Will be configurable later on via application settings.
+ # This means, there can only be 4 jobs running at the same time at maximum.
+ MAX_RUNNING_JOBS = 4
+ FAILURE_THRESHOLD = 3
- merge_request = MergeRequest.find_by_id(merge_request_id)
+ def perform_work
+ return unless Feature.enabled?(:merge_request_refs_cleanup, default_enabled: false)
unless merge_request
- logger.error("Failed to find merge request with ID: #{merge_request_id}")
+ logger.error('No existing merge request to be cleaned up.')
return
end
- result = ::MergeRequests::CleanupRefsService.new(merge_request).execute
+ log_extra_metadata_on_done(:merge_request_id, merge_request.id)
+
+ result = MergeRequests::CleanupRefsService.new(merge_request).execute
+
+ if result[:status] == :success
+ merge_request_cleanup_schedule.complete!
+ else
+ if merge_request_cleanup_schedule.failed_count < FAILURE_THRESHOLD
+ merge_request_cleanup_schedule.retry!
+ else
+ merge_request_cleanup_schedule.mark_as_failed!
+ end
+
+ log_extra_metadata_on_done(:message, result[:message])
+ end
+
+ log_extra_metadata_on_done(:status, merge_request_cleanup_schedule.status)
+ end
+
+ def remaining_work_count
+ MergeRequest::CleanupSchedule
+ .scheduled_and_unstarted
+ .limit(max_running_jobs)
+ .count
+ end
+
+ def max_running_jobs
+ MAX_RUNNING_JOBS
+ end
+
+ private
- return if result[:status] == :success
+ def merge_request
+ strong_memoize(:merge_request) do
+ merge_request_cleanup_schedule&.merge_request
+ end
+ end
- logger.error("Failed cleanup refs of merge request (#{merge_request_id}): #{result[:message]}")
+ def merge_request_cleanup_schedule
+ strong_memoize(:merge_request_cleanup_schedule) do
+ MergeRequest::CleanupSchedule.start_next
+ end
end
end
diff --git a/app/workers/schedule_merge_request_cleanup_refs_worker.rb b/app/workers/schedule_merge_request_cleanup_refs_worker.rb
index b5ea5298879..40a773ca58f 100644
--- a/app/workers/schedule_merge_request_cleanup_refs_worker.rb
+++ b/app/workers/schedule_merge_request_cleanup_refs_worker.rb
@@ -10,21 +10,10 @@ class ScheduleMergeRequestCleanupRefsWorker
tags :exclude_from_kubernetes
idempotent!
- # Based on existing data, MergeRequestCleanupRefsWorker can run 3 jobs per
- # second. This means that 180 jobs can be performed but since there are some
- # spikes from time time, it's better to give it some allowance.
- LIMIT = 180
- DELAY = 10.seconds
- BATCH_SIZE = 30
-
def perform
return if Gitlab::Database.read_only?
return unless Feature.enabled?(:merge_request_refs_cleanup, default_enabled: false)
- ids = MergeRequest::CleanupSchedule.scheduled_merge_request_ids(LIMIT).map { |id| [id] }
-
- MergeRequestCleanupRefsWorker.bulk_perform_in(DELAY, ids, batch_size: BATCH_SIZE) # rubocop:disable Scalability/BulkPerformWithContext
-
- log_extra_metadata_on_done(:merge_requests_count, ids.size)
+ MergeRequestCleanupRefsWorker.perform_with_capacity
end
end