diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-22 15:09:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-22 15:09:27 +0000 |
commit | e829ca213b25b49faa619c0363059c6ed0a5a112 (patch) | |
tree | 0db00d8f17cf6f278cc68fb6b3739fda8d44fea6 /app | |
parent | 6046a605fdbb6d180861c978d17fe3516b2e7507 (diff) | |
download | gitlab-ce-e829ca213b25b49faa619c0363059c6ed0a5a112.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
112 files changed, 467 insertions, 287 deletions
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index dccc0b024ba..9e889ff87f2 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -164,7 +164,7 @@ export default { <template> <form :class="{ 'was-validated': wasValidated }" - class="prepend-top-default append-bottom-default needs-validation" + class="prepend-top-default gl-mb-3 needs-validation" novalidate @submit.prevent.stop="onSubmit" > diff --git a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue index 195e1b7ec5c..be211c9ef87 100644 --- a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue +++ b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue @@ -96,7 +96,7 @@ export default { <preview-item :draft="draft" :is-last="isLast(index)" /> </li> </ul> - <gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" /> + <gl-loading-icon v-else size="lg" class="prepend-top-default gl-mb-3" /> </div> <div class="dropdown-footer"> <publish-button diff --git a/app/assets/javascripts/blob/notebook/notebook_viewer.vue b/app/assets/javascripts/blob/notebook/notebook_viewer.vue index 401fe9beb62..8ee012de2d7 100644 --- a/app/assets/javascripts/blob/notebook/notebook_viewer.vue +++ b/app/assets/javascripts/blob/notebook/notebook_viewer.vue @@ -62,9 +62,7 @@ export default { </script> <template> - <div - class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default" - > + <div class="js-notebook-viewer-mounted container-fluid md prepend-top-default gl-mb-3"> <div v-if="loading && !error" class="text-center loading"> <gl-loading-icon class="mt-5" size="lg" /> </div> diff --git a/app/assets/javascripts/blob/pdf/pdf_viewer.vue b/app/assets/javascripts/blob/pdf/pdf_viewer.vue index 5eaddfc099a..ed9a655fccb 100644 --- a/app/assets/javascripts/blob/pdf/pdf_viewer.vue +++ b/app/assets/javascripts/blob/pdf/pdf_viewer.vue @@ -34,7 +34,7 @@ export default { </script> <template> - <div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default"> + <div class="js-pdf-viewer container-fluid md prepend-top-default gl-mb-3"> <div v-if="loading && !error" class="text-center loading"> <gl-loading-icon class="mt-5" size="lg" /> </div> diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js index dbff03dc734..922f701fd59 100644 --- a/app/assets/javascripts/blob/sketch/index.js +++ b/app/assets/javascripts/blob/sketch/index.js @@ -56,7 +56,7 @@ export default class SketchLoader { error() { const errorMsg = document.createElement('p'); - errorMsg.className = 'prepend-top-default append-bottom-default text-center'; + errorMsg.className = 'prepend-top-default gl-mb-3 text-center'; errorMsg.textContent = __(` Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above. diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index df31ee65b27..c747b63473a 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -115,7 +115,7 @@ export default { </script> <template> - <div class="append-bottom-default deploy-keys"> + <div class="gl-mb-3 deploy-keys"> <gl-loading-icon v-if="isLoading && !hasKeys" :label="s__('DeployKeys|Loading deploy keys')" diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 941365d9d1d..406b0098d99 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -398,7 +398,7 @@ export default { class="files d-flex" > <div - v-show="showTreeList" + v-if="showTreeList" :style="{ width: `${treeWidth}px` }" class="diff-tree-list js-diff-tree-list mr-3" > diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue index 67d10b797fb..810ca7ac1bd 100644 --- a/app/assets/javascripts/issuable_suggestions/components/app.vue +++ b/app/assets/javascripts/issuable_suggestions/components/app.vue @@ -84,7 +84,7 @@ export default { v-for="(suggestion, index) in issues" :key="suggestion.id" :class="{ - 'append-bottom-default': index !== issues.length - 1, + 'gl-mb-3': index !== issues.length - 1, }" > <suggestion :suggestion="suggestion" /> diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index 588ae655de4..f6e9ff34b8f 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -63,7 +63,7 @@ export default { </script> <template> - <div class="prepend-top-default append-bottom-default clearfix"> + <div class="prepend-top-default gl-mb-3 clearfix"> <button :class="{ disabled: formState.updateLoading || !isSubmitEnabled }" :disabled="formState.updateLoading || !isSubmitEnabled" diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue index c34a3488dbd..010f588e58b 100644 --- a/app/assets/javascripts/jobs/components/environments_block.vue +++ b/app/assets/javascripts/jobs/components/environments_block.vue @@ -274,7 +274,7 @@ export default { }; </script> <template> - <div class="prepend-top-default append-bottom-default js-environment-container"> + <div class="prepend-top-default gl-mb-3 js-environment-container"> <div class="environment-information"> <ci-icon :status="iconStatus" /> <p class="inline gl-mb-0" v-html="environment"></p> diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index e7777d0d3af..9c9d9a46ae1 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -108,7 +108,7 @@ export default { /> </ci-header> - <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default append-bottom-default" /> + <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default gl-mb-3" /> <gl-modal :modal-id="$options.DELETE_MODAL_ID" diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue index fe5c289152d..46fe16e5580 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue @@ -42,7 +42,7 @@ export default { </div> </div> - <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table"> + <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-cases-table"> <div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray"> <div role="rowheader" class="table-section section-20"> {{ __('Class') }} diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index 4dfb67dd8e8..6d5d7b6450b 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -46,7 +46,7 @@ export default { </div> </div> - <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table"> + <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-suites-table"> <div role="row" class="gl-responsive-table-row table-row-header font-weight-bold"> <div role="rowheader" class="table-section section-25 pl-3"> {{ __('Suite') }} diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue index 2ac57ac5bcb..53c78b93254 100644 --- a/app/assets/javascripts/serverless/components/function_details.vue +++ b/app/assets/javascripts/serverless/components/function_details.vue @@ -71,7 +71,7 @@ export default { <template> <section id="serverless-function-details"> <h3 class="serverless-function-name">{{ name }}</h3> - <div class="append-bottom-default serverless-function-description"> + <div class="gl-mb-3 serverless-function-description"> <div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div> </div> <url :uri="funcUrl" /> diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue index 2b1291ac70f..0995c1a6b5e 100644 --- a/app/assets/javascripts/serverless/components/functions.vue +++ b/app/assets/javascripts/serverless/components/functions.vue @@ -75,11 +75,7 @@ export default { <template> <section id="serverless-functions" class="flex-grow"> - <gl-loading-icon - v-if="checkingInstalled" - size="lg" - class="prepend-top-default append-bottom-default" - /> + <gl-loading-icon v-if="checkingInstalled" size="lg" class="prepend-top-default gl-mb-3" /> <div v-else-if="isInstalled"> <div v-if="hasFunctionData"> @@ -98,7 +94,7 @@ export default { <gl-loading-icon v-if="isLoading" size="lg" - class="prepend-top-default append-bottom-default js-functions-loader" + class="prepend-top-default gl-mb-3 js-functions-loader" /> </div> <div v-else class="empty-state js-empty-state"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index 0464c4b9c15..d147c32b58b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -77,7 +77,7 @@ export default { }; </script> <template> - <div class="d-flex mr-source-target append-bottom-default"> + <div class="d-flex mr-source-target gl-mb-3"> <mr-widget-icon name="git-merge" /> <div class="git-merge-container d-flex"> <div class="normal"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue index 9942861d9e4..36413198a06 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue @@ -34,7 +34,7 @@ export default { }; </script> <template> - <div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest append-bottom-default"> + <div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest gl-mb-3"> <mr-widget-icon :name="$options.iconName" /> <div :id="$options.popoverTarget"> <gl-sprintf diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue index acd8037cfb2..44bdc4a3be8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue @@ -29,7 +29,7 @@ export default { <textarea :id="inputId" :value="value" - class="form-control js-gfm-input append-bottom-default commit-message-edit" + class="form-control js-gfm-input gl-mb-3 commit-message-edit" dir="auto" required="required" rows="7" diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 89844f07e7e..8dcfe9b4512 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -231,7 +231,7 @@ export default { <template> <div ref="gl-form" - :class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }" + :class="{ 'prepend-top-default gl-mb-3': addSpacingClasses }" class="js-vue-markdown-field md-area position-relative" > <markdown-header diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index aa1abb5adb6..049f5e71849 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -89,14 +89,13 @@ export default { <div class="md-header"> <ul class="nav-links clearfix"> <li :class="{ active: !previewMarkdown }" class="md-header-tab"> - <button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)"> + <button class="js-write-link" type="button" @click="writeMarkdownTab($event)"> {{ __('Write') }} </button> </li> <li :class="{ active: previewMarkdown }" class="md-header-tab"> <button class="js-preview-link js-md-preview-button" - tabindex="-1" type="button" @click="previewMarkdownTab($event)" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue index 94f78c0c085..f37dd9e171c 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue @@ -64,7 +64,6 @@ export default { :aria-label="buttonTitle" type="button" class="toolbar-btn js-md" - tabindex="-1" data-container="body" @click="() => $emit('click')" > diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 1d756210b9c..5166c4c640d 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -421,7 +421,6 @@ img.emoji { .append-bottom-10 { margin-bottom: 10px; } .append-bottom-15 { margin-bottom: 15px; } .append-bottom-20 { margin-bottom: 20px; } -.append-bottom-default { margin-bottom: $gl-padding; } .prepend-bottom-32 { margin-bottom: 32px; } .ml-10 { margin-left: 4.5rem; } .inline { display: inline-block; } diff --git a/app/controllers/concerns/known_sign_in.rb b/app/controllers/concerns/known_sign_in.rb index 2b73042a91b..cacc7e4628f 100644 --- a/app/controllers/concerns/known_sign_in.rb +++ b/app/controllers/concerns/known_sign_in.rb @@ -10,7 +10,7 @@ module KnownSignIn private def verify_known_sign_in - return unless current_user + return unless Gitlab::CurrentSettings.notify_on_unknown_sign_in? && current_user notify_user unless known_device? || known_remote_ip? diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 8a8064b24c2..db40b0bed77 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -3,11 +3,14 @@ class Dashboard::TodosController < Dashboard::ApplicationController include ActionView::Helpers::NumberHelper include PaginatedCollection + include Analytics::UniqueVisitsHelper before_action :authorize_read_project!, only: :index before_action :authorize_read_group!, only: :index before_action :find_todos, only: [:index, :destroy_all] + track_unique_visits :index, target_id: 'u_analytics_todos' + def index @sort = params[:sort] @todos = @todos.page(params[:page]) diff --git a/app/controllers/instance_statistics/cohorts_controller.rb b/app/controllers/instance_statistics/cohorts_controller.rb index 4b4e39db2e1..0de62a56b01 100644 --- a/app/controllers/instance_statistics/cohorts_controller.rb +++ b/app/controllers/instance_statistics/cohorts_controller.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController + include Analytics::UniqueVisitsHelper + before_action :authenticate_usage_ping_enabled_or_admin! + track_unique_visits :index, target_id: 'i_analytics_cohorts' + def index if Gitlab::CurrentSettings.usage_ping_enabled cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do diff --git a/app/controllers/instance_statistics/dev_ops_score_controller.rb b/app/controllers/instance_statistics/dev_ops_score_controller.rb index 238f7fa7707..b98a1bf7f99 100644 --- a/app/controllers/instance_statistics/dev_ops_score_controller.rb +++ b/app/controllers/instance_statistics/dev_ops_score_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class InstanceStatistics::DevOpsScoreController < InstanceStatistics::ApplicationController + include Analytics::UniqueVisitsHelper + + track_unique_visits :index, target_id: 'i_analytics_dev_ops_score' + # rubocop: disable CodeReuse/ActiveRecord def index @metric = DevOpsScore::Metric.order(:created_at).last&.present diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index f13c75ac4cc..898d888c978 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -4,10 +4,13 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController include ActionView::Helpers::DateHelper include ActionView::Helpers::TextHelper include CycleAnalyticsParams + include Analytics::UniqueVisitsHelper before_action :whitelist_query_limiting, only: [:show] before_action :authorize_read_cycle_analytics! + track_unique_visits :show, target_id: 'p_analytics_valuestream' + def show @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params)) diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index a8b90f8685f..9b889f9e837 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -2,12 +2,15 @@ class Projects::GraphsController < Projects::ApplicationController include ExtractsPath + include Analytics::UniqueVisitsHelper # Authorize before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_read_repository_graphs! + track_unique_visits :charts, target_id: 'p_analytics_repo' + def show respond_to do |format| format.html diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index a8189a82c56..f27ac8b53e4 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -2,6 +2,7 @@ class Projects::PipelinesController < Projects::ApplicationController include ::Gitlab::Utils::StrongMemoize + include Analytics::UniqueVisitsHelper before_action :whitelist_query_limiting, only: [:create, :retry] before_action :pipeline, except: [:index, :new, :create, :charts] @@ -20,6 +21,8 @@ class Projects::PipelinesController < Projects::ApplicationController around_action :allow_gitaly_ref_name_caching, only: [:index, :show] + track_unique_visits :charts, target_id: 'p_analytics_pipelines' + wrap_parameters Ci::Pipeline POLLING_INTERVAL = 10_000 diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb index 9e71e92b456..7347a83d294 100644 --- a/app/finders/ci/pipelines_finder.rb +++ b/app/finders/ci/pipelines_finder.rb @@ -71,7 +71,7 @@ module Ci # rubocop: disable CodeReuse/ActiveRecord def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) items.where(status: params[:status]) end diff --git a/app/finders/ci/runner_jobs_finder.rb b/app/finders/ci/runner_jobs_finder.rb index ffcdb407e7e..9dc3c2a2427 100644 --- a/app/finders/ci/runner_jobs_finder.rb +++ b/app/finders/ci/runner_jobs_finder.rb @@ -21,7 +21,7 @@ module Ci # rubocop: disable CodeReuse/ActiveRecord def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) items.where(status: params[:status]) end diff --git a/app/helpers/analytics/unique_visits_helper.rb b/app/helpers/analytics/unique_visits_helper.rb new file mode 100644 index 00000000000..ded7f54e44e --- /dev/null +++ b/app/helpers/analytics/unique_visits_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Analytics + module UniqueVisitsHelper + extend ActiveSupport::Concern + + def visitor_id + return cookies[:visitor_id] if cookies[:visitor_id].present? + return unless current_user + + uuid = SecureRandom.uuid + cookies[:visitor_id] = { value: uuid, expires: 24.months } + uuid + end + + def track_visit(target_id) + return unless Feature.enabled?(:track_unique_visits) + return unless Gitlab::CurrentSettings.usage_ping_enabled? + return unless visitor_id + + Gitlab::Analytics::UniqueVisits.new.track_visit(visitor_id, target_id) + end + + class_methods do + def track_unique_visits(controller_actions, target_id:) + after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do + track_visit(target_id) + end + end + end + end +end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index e709d15a946..cc652484be1 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -244,6 +244,7 @@ module ApplicationSettingsHelper :metrics_method_call_threshold, :minimum_password_length, :mirror_available, + :notify_on_unknown_sign_in, :pages_domain_verification_enabled, :password_authentication_enabled_for_web, :password_authentication_enabled_for_git, diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 7ab2b33de8c..ed8931fe0f2 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -244,7 +244,6 @@ module MarkupHelper content_tag :button, type: 'button', class: 'toolbar-btn js-md has-tooltip', - tabindex: -1, data: data, title: options[:title], aria: { label: options[:title] } do diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index d24136cc04a..05661f9643b 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -88,6 +88,7 @@ module ApplicationSettingImplementation max_attachment_size: Settings.gitlab['max_attachment_size'], max_import_size: 50, mirror_available: true, + notify_on_unknown_sign_in: true, outbound_local_requests_whitelist: [], password_authentication_enabled_for_git: true, password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'], diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 3bbd2e43a51..22f4dcf70b6 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -16,6 +16,7 @@ class AuditEvent < ApplicationRecord scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) } scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) } + scope :by_author_id, -> (author_id) { where(author_id: author_id) } after_initialize :initialize_details diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 8e8fd774310..669c80d5433 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,7 +3,7 @@ module Ci class Pipeline < ApplicationRecord extend Gitlab::Ci::Model - include HasStatus + include Ci::HasStatus include Importable include AfterCommitQueue include Presentable @@ -640,7 +640,7 @@ module Ci when 'manual' then block when 'scheduled' then delay else - raise HasStatus::UnknownStatusError, + raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`" end end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index a316b4718e0..41215601704 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -4,10 +4,10 @@ module Ci class Stage < ApplicationRecord extend Gitlab::Ci::Model include Importable - include HasStatus + include Ci::HasStatus include Gitlab::OptimisticLocking - enum status: HasStatus::STATUSES_ENUM + enum status: Ci::HasStatus::STATUSES_ENUM belongs_to :project belongs_to :pipeline @@ -98,7 +98,7 @@ module Ci when 'scheduled' then delay when 'skipped', nil then skip else - raise HasStatus::UnknownStatusError, + raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`" end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 475f82f23ca..cb22a9268fb 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class CommitStatus < ApplicationRecord - include HasStatus + include Ci::HasStatus include Importable include AfterCommitQueue include Presentable diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb new file mode 100644 index 00000000000..c52807ec501 --- /dev/null +++ b/app/models/concerns/ci/has_status.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +module Ci + module HasStatus + extend ActiveSupport::Concern + + DEFAULT_STATUS = 'created' + BLOCKED_STATUS = %w[manual scheduled].freeze + AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze + STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze + ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze + COMPLETED_STATUSES = %w[success failed canceled skipped].freeze + ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze + PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze + EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze + STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, + failed: 4, canceled: 5, skipped: 6, manual: 7, + scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze + + UnknownStatusError = Class.new(StandardError) + + class_methods do + def legacy_status_sql + scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all + scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none + + builds = scope_relevant.select('count(*)').to_sql + created = scope_relevant.created.select('count(*)').to_sql + success = scope_relevant.success.select('count(*)').to_sql + manual = scope_relevant.manual.select('count(*)').to_sql + scheduled = scope_relevant.scheduled.select('count(*)').to_sql + preparing = scope_relevant.preparing.select('count(*)').to_sql + waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql + pending = scope_relevant.pending.select('count(*)').to_sql + running = scope_relevant.running.select('count(*)').to_sql + skipped = scope_relevant.skipped.select('count(*)').to_sql + canceled = scope_relevant.canceled.select('count(*)').to_sql + warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false' + + Arel.sql( + "(CASE + WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success}) THEN 'success' + WHEN (#{builds})=(#{created}) THEN 'created' + WHEN (#{builds})=(#{preparing}) THEN 'preparing' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' + WHEN (#{running})+(#{pending})>0 THEN 'running' + WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource' + WHEN (#{manual})>0 THEN 'manual' + WHEN (#{scheduled})>0 THEN 'scheduled' + WHEN (#{preparing})>0 THEN 'preparing' + WHEN (#{created})>0 THEN 'running' + ELSE 'failed' + END)" + ) + end + + def legacy_status + all.pluck(legacy_status_sql).first + end + + # This method should not be used. + # This method performs expensive calculation of status: + # 1. By plucking all related objects, + # 2. Or executes expensive SQL query + def slow_composite_status(project:) + if ::Gitlab::Ci::Features.composite_status?(project) + Gitlab::Ci::Status::Composite + .new(all, with_allow_failure: columns_hash.key?('allow_failure')) + .status + else + legacy_status + end + end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.maximum(:finished_at) + end + + def all_state_names + state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) } + end + + def completed_statuses + COMPLETED_STATUSES.map(&:to_sym) + end + end + + included do + validates :status, inclusion: { in: AVAILABLE_STATUSES } + + state_machine :status, initial: :created do + state :created, value: 'created' + state :waiting_for_resource, value: 'waiting_for_resource' + state :preparing, value: 'preparing' + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + state :skipped, value: 'skipped' + state :manual, value: 'manual' + state :scheduled, value: 'scheduled' + end + + scope :created, -> { with_status(:created) } + scope :waiting_for_resource, -> { with_status(:waiting_for_resource) } + scope :preparing, -> { with_status(:preparing) } + scope :relevant, -> { without_status(:created) } + scope :running, -> { with_status(:running) } + scope :pending, -> { with_status(:pending) } + scope :success, -> { with_status(:success) } + scope :failed, -> { with_status(:failed) } + scope :canceled, -> { with_status(:canceled) } + scope :skipped, -> { with_status(:skipped) } + scope :manual, -> { with_status(:manual) } + scope :scheduled, -> { with_status(:scheduled) } + scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) } + scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) } + scope :created_or_pending, -> { with_status(:created, :pending) } + scope :running_or_pending, -> { with_status(:running, :pending) } + scope :finished, -> { with_status(:success, :failed, :canceled) } + scope :failed_or_canceled, -> { with_status(:failed, :canceled) } + scope :incomplete, -> { without_statuses(completed_statuses) } + + scope :cancelable, -> do + where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled]) + end + + scope :without_statuses, -> (names) do + with_status(all_state_names - names.to_a) + end + end + + def started? + STARTED_STATUSES.include?(status) && started_at + end + + def active? + ACTIVE_STATUSES.include?(status) + end + + def complete? + COMPLETED_STATUSES.include?(status) + end + + def blocked? + BLOCKED_STATUS.include?(status) + end + + private + + def calculate_duration + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.current - started_at + end + end + end +end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb deleted file mode 100644 index c885dea862f..00000000000 --- a/app/models/concerns/has_status.rb +++ /dev/null @@ -1,166 +0,0 @@ -# frozen_string_literal: true - -module HasStatus - extend ActiveSupport::Concern - - DEFAULT_STATUS = 'created' - BLOCKED_STATUS = %w[manual scheduled].freeze - AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze - STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze - ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze - COMPLETED_STATUSES = %w[success failed canceled skipped].freeze - ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze - PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze - EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze - STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7, - scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze - - UnknownStatusError = Class.new(StandardError) - - class_methods do - def legacy_status_sql - scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all - scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none - - builds = scope_relevant.select('count(*)').to_sql - created = scope_relevant.created.select('count(*)').to_sql - success = scope_relevant.success.select('count(*)').to_sql - manual = scope_relevant.manual.select('count(*)').to_sql - scheduled = scope_relevant.scheduled.select('count(*)').to_sql - preparing = scope_relevant.preparing.select('count(*)').to_sql - waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql - pending = scope_relevant.pending.select('count(*)').to_sql - running = scope_relevant.running.select('count(*)').to_sql - skipped = scope_relevant.skipped.select('count(*)').to_sql - canceled = scope_relevant.canceled.select('count(*)').to_sql - warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false' - - Arel.sql( - "(CASE - WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' - WHEN (#{builds})=(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{success}) THEN 'success' - WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{preparing}) THEN 'preparing' - WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' - WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' - WHEN (#{running})+(#{pending})>0 THEN 'running' - WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource' - WHEN (#{manual})>0 THEN 'manual' - WHEN (#{scheduled})>0 THEN 'scheduled' - WHEN (#{preparing})>0 THEN 'preparing' - WHEN (#{created})>0 THEN 'running' - ELSE 'failed' - END)" - ) - end - - def legacy_status - all.pluck(legacy_status_sql).first - end - - # This method should not be used. - # This method performs expensive calculation of status: - # 1. By plucking all related objects, - # 2. Or executes expensive SQL query - def slow_composite_status(project:) - if ::Gitlab::Ci::Features.composite_status?(project) - Gitlab::Ci::Status::Composite - .new(all, with_allow_failure: columns_hash.key?('allow_failure')) - .status - else - legacy_status - end - end - - def started_at - all.minimum(:started_at) - end - - def finished_at - all.maximum(:finished_at) - end - - def all_state_names - state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) } - end - - def completed_statuses - COMPLETED_STATUSES.map(&:to_sym) - end - end - - included do - validates :status, inclusion: { in: AVAILABLE_STATUSES } - - state_machine :status, initial: :created do - state :created, value: 'created' - state :waiting_for_resource, value: 'waiting_for_resource' - state :preparing, value: 'preparing' - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' - state :skipped, value: 'skipped' - state :manual, value: 'manual' - state :scheduled, value: 'scheduled' - end - - scope :created, -> { with_status(:created) } - scope :waiting_for_resource, -> { with_status(:waiting_for_resource) } - scope :preparing, -> { with_status(:preparing) } - scope :relevant, -> { without_status(:created) } - scope :running, -> { with_status(:running) } - scope :pending, -> { with_status(:pending) } - scope :success, -> { with_status(:success) } - scope :failed, -> { with_status(:failed) } - scope :canceled, -> { with_status(:canceled) } - scope :skipped, -> { with_status(:skipped) } - scope :manual, -> { with_status(:manual) } - scope :scheduled, -> { with_status(:scheduled) } - scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) } - scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) } - scope :created_or_pending, -> { with_status(:created, :pending) } - scope :running_or_pending, -> { with_status(:running, :pending) } - scope :finished, -> { with_status(:success, :failed, :canceled) } - scope :failed_or_canceled, -> { with_status(:failed, :canceled) } - scope :incomplete, -> { without_statuses(completed_statuses) } - - scope :cancelable, -> do - where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled]) - end - - scope :without_statuses, -> (names) do - with_status(all_state_names - names.to_a) - end - end - - def started? - STARTED_STATUSES.include?(status) && started_at - end - - def active? - ACTIVE_STATUSES.include?(status) - end - - def complete? - COMPLETED_STATUSES.include?(status) - end - - def blocked? - BLOCKED_STATUS.include?(status) - end - - private - - def calculate_duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.current - started_at - end - end -end diff --git a/app/models/environment.rb b/app/models/environment.rb index 8dae2d760f5..103d34f753d 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -147,7 +147,7 @@ class Environment < ApplicationRecord Ci::Build.joins(inner_join_stop_actions) .with(cte.to_arel) .where(ci_builds[:commit_id].in(pipeline_ids)) - .where(status: HasStatus::BLOCKED_STATUS) + .where(status: Ci::HasStatus::BLOCKED_STATUS) .preload_project_and_pipeline_project .preload(:user, :metadata, :deployment) end diff --git a/app/models/project.rb b/app/models/project.rb index e63eac02dca..1407c7b3820 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2414,6 +2414,11 @@ class Project < ApplicationRecord super || build_metrics_setting end + def service_desk_enabled + false + end + alias_method :service_desk_enabled?, :service_desk_enabled + private def find_service(services, name) diff --git a/app/models/user.rb b/app/models/user.rb index 99dfc9ee13d..68d8c449fd6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -656,6 +656,15 @@ class User < ApplicationRecord end end + def support_bot + email_pattern = "support%s@#{Settings.gitlab.host}" + + unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u| + u.bio = 'The GitLab support bot used for Service Desk' + u.name = 'GitLab Support Bot' + end + end + # Return true if there is only single non-internal user in the deployment, # ghost user is ignored. def single_user? diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 2c26ba565ab..f41d69c7a41 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -21,6 +21,10 @@ class BasePolicy < DeclarativePolicy::Base with_options scope: :user, score: 0 condition(:deactivated) { @user&.deactivated? } + desc "User is support bot" + with_options scope: :user, score: 0 + condition(:support_bot) { @user&.support_bot? } + desc "User email is unconfirmed or user account is locked" with_options scope: :user, score: 0 condition(:inactive) do diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb index f910e04d015..3073a2e5d10 100644 --- a/app/policies/concerns/policy_actor.rb +++ b/app/policies/concerns/policy_actor.rb @@ -45,6 +45,10 @@ module PolicyActor false end + def support_bot? + false + end + def deactivated? false end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 6ff16b71ea3..90f1f70d0de 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -123,6 +123,9 @@ class ProjectPolicy < BasePolicy !@subject.design_management_enabled? end + with_scope :subject + condition(:service_desk_enabled) { @subject.service_desk_enabled? } + # We aren't checking `:read_issue` or `:read_merge_request` in this case # because it could be possible for a user to see an issuable-iid # (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be @@ -578,6 +581,12 @@ class ProjectPolicy < BasePolicy enable :read_build_report_results end + rule { support_bot }.enable :guest_access + rule { support_bot & ~service_desk_enabled }.policy do + prevent :create_note + prevent :read_project + end + private def team_member? @@ -626,6 +635,7 @@ class ProjectPolicy < BasePolicy def lookup_access_level! return ::Gitlab::Access::REPORTER if alert_bot? + return ::Gitlab::Access::REPORTER if support_bot? && service_desk_enabled? # NOTE: max_member_access has its own cache project.team.max_member_access(@user.id) diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index 0b0454c5282..0aadcd01a43 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -59,13 +59,13 @@ class StageEntity < Grape::Entity end def latest_statuses - HasStatus::ORDERED_STATUSES.flat_map do |ordered_status| + Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status| grouped_statuses.fetch(ordered_status, []) end end def retried_statuses - HasStatus::ORDERED_STATUSES.flat_map do |ordered_status| + Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status| grouped_retried_statuses.fetch(ordered_status, []) end end diff --git a/app/services/authorized_project_update/periodic_recalculate_service.rb b/app/services/authorized_project_update/periodic_recalculate_service.rb new file mode 100644 index 00000000000..91c0f50e5e0 --- /dev/null +++ b/app/services/authorized_project_update/periodic_recalculate_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module AuthorizedProjectUpdate + class PeriodicRecalculateService + BATCH_SIZE = 480 + DELAY_INTERVAL = 30.seconds.to_i + + def execute + # Using this approach (instead of eg. User.each_batch) keeps the arguments + # the same for AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker + # even if the user list changes, so we can deduplicate these jobs. + (1..User.maximum(:id)).each_slice(BATCH_SIZE).with_index do |batch, index| + delay = DELAY_INTERVAL * index + AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker.perform_in(delay, *batch.minmax) + end + end + end +end diff --git a/app/services/authorized_project_update/recalculate_for_user_range_service.rb b/app/services/authorized_project_update/recalculate_for_user_range_service.rb new file mode 100644 index 00000000000..14b0f5d6117 --- /dev/null +++ b/app/services/authorized_project_update/recalculate_for_user_range_service.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module AuthorizedProjectUpdate + class RecalculateForUserRangeService + def initialize(start_user_id, end_user_id) + @start_user_id = start_user_id + @end_user_id = end_user_id + end + + def execute + User.where(id: start_user_id..end_user_id).select(:id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord + Users::RefreshAuthorizedProjectsService.new(user).execute + end + end + + private + + attr_reader :start_user_id, :end_user_id + end +end diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb index b01a9d2e3b8..a23d5d8941a 100644 --- a/app/services/ci/pipeline_processing/atomic_processing_service.rb +++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb @@ -77,7 +77,7 @@ module Ci def update_processable!(processable) status = processable_status(processable) - return unless HasStatus::COMPLETED_STATUSES.include?(status) + return unless Ci::HasStatus::COMPLETED_STATUSES.include?(status) # transition status if possible Gitlab::OptimisticLocking.retry_lock(processable) do |subject| diff --git a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb index 2228328882d..d0aa8b04775 100644 --- a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb +++ b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb @@ -80,7 +80,7 @@ module Ci # TODO: This is hack to support # the same exact behaviour for Atomic and Legacy processing # that DAG is blocked from executing if dependent is not "complete" - if dag && statuses.any? { |status| HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) } + if dag && statuses.any? { |status| Ci::HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) } return 'pending' end diff --git a/app/services/ci/pipeline_processing/legacy_processing_service.rb b/app/services/ci/pipeline_processing/legacy_processing_service.rb index c471f7f0011..56fbc7271da 100644 --- a/app/services/ci/pipeline_processing/legacy_processing_service.rb +++ b/app/services/ci/pipeline_processing/legacy_processing_service.rb @@ -35,7 +35,7 @@ module Ci def process_stage_for_stage_scheduling(index) current_status = status_for_prior_stages(index) - return unless HasStatus::COMPLETED_STATUSES.include?(current_status) + return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status) created_stage_scheduled_processables_in_stage(index).find_each.select do |build| process_build(build, current_status) @@ -73,7 +73,7 @@ module Ci def process_dag_build_with_needs(build) current_status = status_for_build_needs(build.needs.map(&:name)) - return unless HasStatus::COMPLETED_STATUSES.include?(current_status) + return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status) process_build(build, current_status) end diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb index fa8d4c5aa5f..f1a58562190 100644 --- a/app/services/projects/update_repository_storage_service.rb +++ b/app/services/projects/update_repository_storage_service.rb @@ -93,25 +93,25 @@ module Projects old_repository_storage = project.repository_storage new_project_path = moved_path(project.disk_path) - # Notice that the block passed to `run_after_commit` will run with `project` + # Notice that the block passed to `run_after_commit` will run with `repository_storage_move` # as its context - project.run_after_commit do + repository_storage_move.run_after_commit do GitlabShellWorker.perform_async(:mv_repository, old_repository_storage, - disk_path, + project.disk_path, new_project_path) - if wiki.repository_exists? + if project.wiki.repository_exists? GitlabShellWorker.perform_async(:mv_repository, old_repository_storage, - wiki.disk_path, + project.wiki.disk_path, "#{new_project_path}.wiki") end - if design_repository.exists? + if project.design_repository.exists? GitlabShellWorker.perform_async(:mv_repository, old_repository_storage, - design_repository.disk_path, + project.design_repository.disk_path, "#{new_project_path}.design") end end diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 0e7a4821bdf..621266f00e1 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -85,8 +85,6 @@ module Users # remove - The IDs of the authorization rows to remove. # add - Rows to insert in the form `[user id, project id, access level]` def update_authorizations(remove = [], add = []) - return if remove.empty? && add.empty? - User.transaction do user.remove_project_authorizations(remove) unless remove.empty? ProjectAuthorization.insert_authorizations(add) unless add.empty? diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index aa47daf4a57..c7efc4955ab 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -100,7 +100,7 @@ .hint = parsed_with_gfm - .prepend-top-default.append-bottom-default + .prepend-top-default.gl-mb-3 = f.submit 'Update appearance settings', class: 'btn btn-success' - if @appearance.persisted? || @appearance.updated_at .mt-4 diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml index 007cd343339..0972e10e12c 100644 --- a/app/views/admin/application_settings/_signin.html.haml +++ b/app/views/admin/application_settings/_signin.html.haml @@ -33,6 +33,15 @@ = f.label :require_two_factor_authentication, class: 'form-check-label' do Require all users to set up Two-factor authentication .form-group + = f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold' + .form-check + = f.check_box :notify_on_unknown_sign_in, class: 'form-check-input' + = f.label :notify_on_unknown_sign_in, class: 'form-check-label' do + = _('Notify users by email when sign-in location is not recognized') + = link_to icon('question-circle'), + 'https://docs.gitlab.com/ee/user/profile/unknown_sign_in_notification.html', + target: '_blank' + .form-group = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'label-bold' = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0' .form-text.text-muted Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index f295e5a06cb..ccb3f8594d4 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,7 +1,7 @@ - page_title _("Groups") .top-area - .prepend-top-default.append-bottom-default + .prepend-top-default.gl-mb-3 = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| = hidden_field_tag :sort, @sort .search-holder diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml index 841640efad2..eeeb58f9c76 100644 --- a/app/views/admin/hook_logs/_index.html.haml +++ b/app/views/admin/hook_logs/_index.html.haml @@ -1,4 +1,4 @@ -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-3 %h4.gl-mt-0 Recent Deliveries diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml index 636dd6bdfc1..360b85fd451 100644 --- a/app/views/admin/hooks/edit.html.haml +++ b/app/views/admin/hooks/edit.html.haml @@ -5,7 +5,7 @@ .col-lg-3 = render 'shared/web_hooks/title_and_docs', hook: @hook - .col-lg-9.append-bottom-default + .col-lg-9.gl-mb-3 = form_for @hook, as: :hook, url: admin_hook_path do |f| = render partial: 'form', locals: { form: f, hook: @hook } .form-actions diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 1c14291b58e..1d2850b2bee 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -4,7 +4,7 @@ .col-lg-4 = render 'shared/web_hooks/title_and_docs', hook: @hook - .col-lg-8.append-bottom-default + .col-lg-8.gl-mb-3 = form_for @hook, as: :hook, url: admin_hooks_path do |f| = render partial: 'form', locals: { form: f, hook: @hook } = f.submit _('Add system hook'), class: 'btn btn-success' diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index a218885a00e..3403e9e5abf 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -28,4 +28,4 @@ = link_to "Identities", admin_user_identities_path(@user) = nav_link(controller: :impersonation_tokens) do = link_to "Impersonation Tokens", admin_user_impersonation_tokens_path(@user) -.append-bottom-default +.gl-mb-3 diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml index 486625c790b..b9fff03b938 100644 --- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml @@ -1,5 +1,5 @@ - link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') -.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } +.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.gl-mb-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } %button.close.js-close{ type: "button" } × .gcp-signup-offer--content .gcp-signup-offer--icon.gl-mr-3 diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 9aab1556373..1ed59e97315 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -49,7 +49,7 @@ - else .settings-message.text-center = _("You don't have any applications") - .oauth-authorized-applications.prepend-top-20.append-bottom-default + .oauth-authorized-applications.prepend-top-20.gl-mb-3 - if user_oauth_applications? %h5 = _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size } diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml index 7970c3c73f6..77c84862316 100644 --- a/app/views/groups/settings/_lfs.html.haml +++ b/app/views/groups/settings/_lfs.html.haml @@ -5,7 +5,7 @@ %p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe } -.form-group.append-bottom-default +.form-group.gl-mb-3 .form-check = f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input', data: { qa_selector: 'lfs_checkbox' } = f.label :lfs_enabled, class: 'form-check-label' do diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index e886c99a656..4f5fb0afb1f 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -7,7 +7,7 @@ .form-group = render 'shared/allow_request_access', form: f - .form-group.append-bottom-default + .form-group.gl-mb-3 .form-check = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input' = f.label :share_with_group_lock, class: 'form-check-label' do @@ -16,14 +16,14 @@ = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link } %span.js-descr.text-muted= share_with_group_lock_help_text(@group) - .form-group.append-bottom-default + .form-group.gl-mb-3 .form-check = f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input' = f.label :emails_disabled, class: 'form-check-label' do %span.d-block= s_('GroupSettings|Disable email notifications') %span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.') - .form-group.append-bottom-default + .form-group.gl-mb-3 .form-check = f.check_box :mentions_disabled, checked: @group.mentions_disabled?, class: 'form-check-input' = f.label :mentions_disabled, class: 'form-check-label' do diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index f4a97206a19..e884d2d5df6 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -72,4 +72,4 @@ - else %p = s_("Profiles|You don't have access to delete this user.") -.append-bottom-default +.gl-mb-3 diff --git a/app/views/profiles/active_sessions/index.html.haml b/app/views/profiles/active_sessions/index.html.haml index 6d01d055f0c..62538908d8f 100644 --- a/app/views/profiles/active_sessions/index.html.haml +++ b/app/views/profiles/active_sessions/index.html.haml @@ -8,7 +8,7 @@ %p = _('This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.') .col-lg-8 - .append-bottom-default + .gl-mb-3 .card.border-0 %ul.list-group.list-group-flush diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index e90bda0e187..4b24fc7288a 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -19,7 +19,7 @@ %hr %h4.gl-mt-0 = _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 } - .account-well.append-bottom-default + .account-well.gl-mb-3 %ul %li = _('Your Primary Email will be used for avatar detection.') diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index 31610e7505b..852c67ed78f 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -17,5 +17,5 @@ %hr %h5 = _('Your GPG keys (%{count})') % { count:@gpg_keys.count} - .append-bottom-default + .gl-mb-3 = render 'key_table' diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 788c67b3704..f11a54bcbaa 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -20,5 +20,5 @@ %hr %h5 = _('Your SSH keys (%{count})') % { count:@keys.count } - .append-bottom-default + .gl-mb-3 = render 'key_table' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 498f80aed2b..522522c5023 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -47,7 +47,7 @@ = _('Projects (%{count})') % { count: @project_notifications.size } %p.account-well = _('To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.') - .append-bottom-default + .gl-mb-3 %ul.bordered-list - @project_notifications.each do |setting| = render 'project_settings', setting: setting, project: setting.source diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 9deaf7f84be..b6653048367 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -29,7 +29,7 @@ .form-group = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold' = f.password_field :password_confirmation, required: true, class: 'form-control', data: { qa_selector: 'confirm_password_field' } - .prepend-top-default.append-bottom-default + .prepend-top-default.gl-mb-3 = f.submit _('Save password'), class: "btn btn-success append-right-10", data: { qa_selector: 'save_password_button' } - unless @user.password_automatically_set? = link_to _('I forgot my password'), reset_profile_password_path, method: :put diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index f5ea805cf85..c7c01f0b623 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -25,7 +25,7 @@ .md = brand_profile_image_guidelines .col-lg-8 - .clearfix.avatar-image.append-bottom-default + .clearfix.avatar-image.gl-mb-3 = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160' %h5.gl-mt-0= s_("Profiles|Upload new avatar") @@ -118,7 +118,7 @@ = f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true .help-block = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") - .prepend-top-default.append-bottom-default + .prepend-top-default.gl-mb-3 = f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success' = link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel' diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 5d2ceacfe3e..47f91f719bc 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -18,7 +18,7 @@ = render 'shared/commit_well', commit: commit, ref: ref, project: project - if is_project_overview - .project-buttons.append-bottom-default{ class: ("js-show-on-project-root" if vue_file_list_enabled?) } + .project-buttons.gl-mb-3{ class: ("js-show-on-project-root" if vue_file_list_enabled?) } = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) - if vue_file_list_enabled? diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml index 6f90bf50b91..761af314469 100644 --- a/app/views/projects/_wiki.html.haml +++ b/app/views/projects/_wiki.html.haml @@ -1,6 +1,6 @@ - if @wiki_home.present? %div{ class: container_class } - .md.prepend-top-default.append-bottom-default + .md.prepend-top-default.gl-mb-3 = render_wiki_content(@wiki_home) - else - can_create_wiki = can?(current_user, :create_wiki, @project) diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 67c78620f15..d8158376009 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -2,7 +2,7 @@ - file_name = params[:id].split("/").last ||= "" - is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name) -.file-holder-bottom-radius.file-holder.file.append-bottom-default +.file-holder-bottom-radius.file-holder.file.gl-mb-3 .js-file-title.file-title.align-items-center.clearfix{ data: { current_action: action } } .editor-ref.block-truncated = sprite_icon('fork', size: 12) diff --git a/app/views/projects/blob/viewers/_loading.html.haml b/app/views/projects/blob/viewers/_loading.html.haml index df1f3e4e01b..606db3657a6 100644 --- a/app/views/projects/blob/viewers/_loading.html.haml +++ b/app/views/projects/blob/viewers/_loading.html.haml @@ -1,2 +1,2 @@ -.text-center.prepend-top-default.append-bottom-default +.text-center.prepend-top-default.gl-mb-3 = icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content…', class: 'qa-spinner') diff --git a/app/views/projects/blob/viewers/_sketch.html.haml b/app/views/projects/blob/viewers/_sketch.html.haml index b4b6492b92f..22b08a85b8d 100644 --- a/app/views/projects/blob/viewers/_sketch.html.haml +++ b/app/views/projects/blob/viewers/_sketch.html.haml @@ -1,3 +1,3 @@ .file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } } - .js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' } + .js-loading-icon.text-center.prepend-top-default.gl-mb-3.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' } = icon('spinner spin 2x', 'aria-hidden' => 'true'); diff --git a/app/views/projects/blob/viewers/_stl.html.haml b/app/views/projects/blob/viewers/_stl.html.haml index 55dd8cba7fe..cbae6057630 100644 --- a/app/views/projects/blob/viewers/_stl.html.haml +++ b/app/views/projects/blob/viewers/_stl.html.haml @@ -1,7 +1,7 @@ .file-content.is-stl-loading .text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } } - = icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading') - .text-center.prepend-top-default.append-bottom-default.stl-controls + = icon('spinner spin 2x', class: 'prepend-top-default gl-mb-3', 'aria-hidden' => 'true', 'aria-label' => 'Loading') + .text-center.prepend-top-default.gl-mb-3.stl-controls .btn-group %button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } } Wireframe diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml index efe80a4877c..f49431932f1 100644 --- a/app/views/projects/environments/_form.html.haml +++ b/app/views/projects/environments/_form.html.haml @@ -1,4 +1,4 @@ -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-3 %h4.gl-mt-0 = _("Environments") diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml index 70064722832..1b20a89ecb0 100644 --- a/app/views/projects/forks/_fork_button.html.haml +++ b/app/views/projects/forks/_fork_button.html.haml @@ -2,7 +2,7 @@ - can_create_project = current_user.can?(:create_projects, namespace) - if forked_project = namespace.find_fork_of(@project) - .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked + .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.gl-mb-3.forked = link_to project_path(forked_project) do - if /no_((\w*)_)*avatar/.match(avatar) = group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto") @@ -12,7 +12,7 @@ %h5.prepend-top-default = namespace.human_name - else - .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default{ class: ("disabled" unless can_create_project) } + .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.gl-mb-3{ class: ("disabled" unless can_create_project) } = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST", class: ("disabled has-tooltip" unless can_create_project), diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml index e7b924c65bf..a8a4eef65b3 100644 --- a/app/views/projects/hook_logs/_index.html.haml +++ b/app/views/projects/hook_logs/_index.html.haml @@ -1,4 +1,4 @@ -.row.gl-mt-7.append-bottom-default +.row.gl-mt-7.gl-mb-3 .col-lg-3 %h4.gl-mt-0 Recent Deliveries diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml index a6a3f56c28c..37330788ca4 100644 --- a/app/views/projects/hook_logs/show.html.haml +++ b/app/views/projects/hook_logs/show.html.haml @@ -2,7 +2,7 @@ - add_to_breadcrumbs _('Webhook Settings'), namespace_project_hooks_path - page_title _('Webhook Logs') -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-3 %h4.gl-mt-0 Request details diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml index 15100840c0a..066cf183ac9 100644 --- a/app/views/projects/hooks/edit.html.haml +++ b/app/views/projects/hooks/edit.html.haml @@ -6,7 +6,7 @@ .col-lg-3 = render 'shared/web_hooks/title_and_docs', hook: @hook - .col-lg-9.append-bottom-default + .col-lg-9.gl-mb-3 = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f| = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 169a5cc9d6b..56e60e747b2 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -6,7 +6,7 @@ .col-lg-4 = render 'shared/web_hooks/title_and_docs', hook: @hook - .col-lg-8.append-bottom-default + .col-lg-8.gl-mb-3 = form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f| = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } = f.submit 'Add webhook', class: 'btn btn-success' diff --git a/app/views/projects/issues/_by_email_description.html.haml b/app/views/projects/issues/_by_email_description.html.haml index f2d58534903..0ff852352e1 100644 --- a/app/views/projects/issues/_by_email_description.html.haml +++ b/app/views/projects/issues/_by_email_description.html.haml @@ -1,6 +1,6 @@ The subject will be used as the title of the new issue, and the message will be the description. -= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1 += link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank' and styling with -= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1 += link_to 'Markdown', help_page_path('user/markdown'), target: '_blank' are supported. diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 7ff6c0a2019..ebd43d2153b 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -1,4 +1,4 @@ -.account-well.prepend-top-default.append-bottom-default +.account-well.prepend-top-default.gl-mb-3 %ul %li = _('The repository must be accessible over <code>http://</code>, diff --git a/app/views/projects/pipelines/_stage.html.haml b/app/views/projects/pipelines/_stage.html.haml index 3feb99cfcd7..0651ad6fdb8 100644 --- a/app/views/projects/pipelines/_stage.html.haml +++ b/app/views/projects/pipelines/_stage.html.haml @@ -1,5 +1,5 @@ - grouped_statuses = @stage.statuses.latest_ordered.group_by(&:status) -- HasStatus::ORDERED_STATUSES.each do |ordered_status| +- Ci::HasStatus::ORDERED_STATUSES.each do |ordered_status| - grouped_statuses.fetch(ordered_status, []).each do |status| %li = render 'ci/status/dropdown_graph_badge', subject: status diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml index 06578c94079..0e22b1e3105 100644 --- a/app/views/projects/protected_branches/show.html.haml +++ b/app/views/projects/protected_branches/show.html.haml @@ -1,6 +1,6 @@ - page_title @protected_ref.name, _("Protected Branches") -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-3 %h4.gl-mt-0.ref-name = @protected_ref.name diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml index 227aac77583..aad9e5a9340 100644 --- a/app/views/projects/protected_tags/show.html.haml +++ b/app/views/projects/protected_tags/show.html.haml @@ -1,6 +1,6 @@ - page_title @protected_ref.name, _("Protected Tags") -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-3 %h4.gl-mt-0.ref-name = @protected_ref.name diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 91aa148dcb3..dd0a5aeb043 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -1,4 +1,4 @@ -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-4 %h4.gl-mt-0 = @service.title diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml index 1b5b794a7aa..c5b3fd31efa 100644 --- a/app/views/projects/services/prometheus/_help.html.haml +++ b/app/views/projects/services/prometheus/_help.html.haml @@ -1,7 +1,7 @@ - if @project = render 'projects/services/prometheus/configuration_banner', project: @project, service: @service -%h4.append-bottom-default +%h4.gl-mb-3 = s_('PrometheusService|Manual configuration') %p = s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.') diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 728a52f024f..9ce61ed5c13 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -3,7 +3,7 @@ %h4.gl-mt-0 = s_('PrometheusService|Metrics') -.row.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring +.row.gl-mb-3.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring = render 'projects/services/prometheus/metrics', project: @project = render 'projects/services/prometheus/external_alerts', project: @project diff --git a/app/views/projects/settings/operations/_prometheus.html.haml b/app/views/projects/settings/operations/_prometheus.html.haml index b0fa750e131..7ccc829662d 100644 --- a/app/views/projects/settings/operations/_prometheus.html.haml +++ b/app/views/projects/settings/operations/_prometheus.html.haml @@ -11,7 +11,7 @@ - if @project = render 'projects/settings/operations/configuration_banner', project: @project, service: service - %b.append-bottom-default + %b.gl-mb-3 = s_('PrometheusService|Manual configuration') %p = s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.') diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index b98906a6863..a3cbee98dcc 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -59,7 +59,7 @@ %pre.wrap{ data: { qa_selector: 'tag_message_content' } } = strip_signature(@tag.message) -.append-bottom-default.prepend-top-default +.gl-mb-3.prepend-top-default - if @release.description.present? .description.md{ data: { qa_selector: 'tag_release_notes_content' } } = markdown_field(@release, :description) diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml index 4ca070cb162..53f55442b01 100644 --- a/app/views/projects/triggers/_index.html.haml +++ b/app/views/projects/triggers/_index.html.haml @@ -1,4 +1,4 @@ -.row.prepend-top-default.append-bottom-default.triggers-container +.row.prepend-top-default.gl-mb-3.triggers-container .col-lg-12 .card .card-header @@ -21,7 +21,7 @@ %th = render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger - else - %p.settings-message.text-center.append-bottom-default + %p.settings-message.text-center.gl-mb-3 No triggers have been created yet. Add one using the form above. .card-footer diff --git a/app/views/projects/triggers/edit.html.haml b/app/views/projects/triggers/edit.html.haml index b1bc9b0f900..67ca4994146 100644 --- a/app/views/projects/triggers/edit.html.haml +++ b/app/views/projects/triggers/edit.html.haml @@ -1,6 +1,6 @@ - page_title _("Trigger") -.row.prepend-top-default.append-bottom-default +.row.prepend-top-default.gl-mb-3 .col-lg-12 %h4.gl-mt-0 Update trigger diff --git a/app/views/shared/_commit_well.html.haml b/app/views/shared/_commit_well.html.haml index 6f1fe9bfdc5..48fe258d01f 100644 --- a/app/views/shared/_commit_well.html.haml +++ b/app/views/shared/_commit_well.html.haml @@ -1,4 +1,4 @@ -.info-well.d-none.d-sm-block.project-last-commit.append-bottom-default +.info-well.d-none.d-sm-block.project-last-commit.gl-mb-3 .well-segment %ul.blob-commit-info = render 'projects/commits/commit', commit: commit, ref: ref, project: project diff --git a/app/views/shared/_md_preview.html.haml b/app/views/shared/_md_preview.html.haml index f5f24b2f0ce..c3818b9f7ae 100644 --- a/app/views/shared/_md_preview.html.haml +++ b/app/views/shared/_md_preview.html.haml @@ -11,10 +11,10 @@ .md-header %ul.nav.nav-tabs.nav-links.clearfix %li.md-header-tab.active - %button.js-md-write-button{ tabindex: -1 } + %button.js-md-write-button = _("Write") %li.md-header-tab - %button.js-md-preview-button{ tabindex: -1 } + %button.js-md-preview-button = _("Preview") %li.md-header-toolbar.active diff --git a/app/views/shared/file_hooks/_index.html.haml b/app/views/shared/file_hooks/_index.html.haml index 436bd305df1..3b585f037df 100644 --- a/app/views/shared/file_hooks/_index.html.haml +++ b/app/views/shared/file_hooks/_index.html.haml @@ -9,7 +9,7 @@ = link_to _('For more information, see the File Hooks documentation.'), help_page_path('administration/file_hooks') - .col-lg-8.append-bottom-default + .col-lg-8.gl-mb-3 - if file_hooks.any? .card .card-header diff --git a/app/views/shared/issuable/form/_default_templates.html.haml b/app/views/shared/issuable/form/_default_templates.html.haml index 49a5ce926b3..3dc244677e2 100644 --- a/app/views/shared/issuable/form/_default_templates.html.haml +++ b/app/views/shared/issuable/form/_default_templates.html.haml @@ -1,4 +1,4 @@ %p.form-text.text-muted Add - = link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1 + = link_to 'description templates', help_page_path('user/project/description_templates') to help your contributors communicate effectively! diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index 1b557214e02..2cd4e0ba761 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -9,7 +9,7 @@ = _('Merge options') .col-sm-10 - if issuable.can_remove_source_branch?(current_user) - .form-check.append-bottom-default + .form-check.gl-mb-3 = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input' = label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml index 75e9ab547ce..355a6627b8f 100644 --- a/app/views/shared/issuable/form/_title.html.haml +++ b/app/views/shared/issuable/form/_title.html.haml @@ -11,7 +11,7 @@ - if issuable.respond_to?(:work_in_progress?) .form-text.text-muted .js-wip-explanation - %a.js-toggle-wip{ href: '', tabindex: -1 } + %a.js-toggle-wip{ href: '' } Remove the %code WIP: prefix from the title @@ -22,7 +22,7 @@ - if has_wip_commits It looks like you have some WIP commits in this branch. %br - %a.js-toggle-wip{ href: '', tabindex: -1 } + %a.js-toggle-wip{ href: '' } Start the title with %code WIP: to prevent a diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml index 902a6e9b363..1dca2cffea6 100644 --- a/app/views/shared/notes/_hints.html.haml +++ b/app/views/shared/notes/_hints.html.haml @@ -1,10 +1,10 @@ - supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false) .comment-toolbar.clearfix .toolbar-text - = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', tabindex: -1 + = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank' - if supports_quick_actions and - = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1 + = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank' are - else is @@ -28,7 +28,7 @@ or %button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file") - %button.markdown-selector.button-attach-file.btn-link{ type: 'button', tabindex: '-1' } + %button.markdown-selector.button-attach-file.btn-link{ type: 'button' } = icon('file-image-o', class: 'toolbar-button-icon') %span.text-attach-file<> = _("Attach a file") diff --git a/app/views/shared/web_hooks/_index.html.haml b/app/views/shared/web_hooks/_index.html.haml index 149f4baeb21..51c99d9699b 100644 --- a/app/views/shared/web_hooks/_index.html.haml +++ b/app/views/shared/web_hooks/_index.html.haml @@ -10,5 +10,5 @@ - hooks.each do |hook| = render 'shared/web_hooks/hook', hook: hook - else - %p.text-center.prepend-top-default.append-bottom-default + %p.text-center.prepend-top-default.gl-mb-3 = _('No webhooks found, add one in the form above.') diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml index 8cfb95cdcf5..fd3d1e1e06a 100644 --- a/app/views/shared/wikis/_sidebar.html.haml +++ b/app/views/shared/wikis/_sidebar.html.haml @@ -1,6 +1,6 @@ %aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } } .sidebar-container - .block.wiki-sidebar-header.append-bottom-default.w-100 + .block.wiki-sidebar-header.gl-mb-3.w-100 %a.gutter-toggle.float-right.d-block.d-sm-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" } = icon('angle-double-right') diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml index a4f3996e5de..00beca36855 100644 --- a/app/views/shared/wikis/show.html.haml +++ b/app/views/shared/wikis/show.html.haml @@ -25,7 +25,7 @@ - history_link = link_to s_("WikiHistoricalPage|history"), wiki_page_path(@wiki, @page, action: :history) = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe -.prepend-top-default.append-bottom-default +.prepend-top-default.gl-mb-3 .md{ data: { qa_selector: 'wiki_page_content' } } = render_wiki_content(@page) diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml index 7bd2d30a35c..e1277d3723d 100644 --- a/app/views/users/_overview.html.haml +++ b/app/views/users/_overview.html.haml @@ -1,6 +1,6 @@ .row .col-12 - .calendar-block.prepend-top-default.append-bottom-default + .calendar-block.prepend-top-default.gl-mb-3 .user-calendar.d-none.d-sm-block{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } %h4.center.light .spinner.spinner-md diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 3a25ce2e870..8750223e2bf 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -5,7 +5,7 @@ --- - :name: authorized_project_update:authorized_project_update_project_create :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -13,6 +13,14 @@ :tags: [] - :name: authorized_project_update:authorized_project_update_project_group_link_create :feature_category: :authentication_and_authorization + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] +- :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range + :feature_category: :authentication_and_authorization :has_external_dependencies: :urgency: :low :resource_boundary: :unknown @@ -107,6 +115,14 @@ :weight: 1 :idempotent: :tags: [] +- :name: cronjob:authorized_project_update_periodic_recalculate + :feature_category: :source_code_management + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: cronjob:ci_archive_traces_cron :feature_category: :continuous_integration :has_external_dependencies: diff --git a/app/workers/authorized_project_update/periodic_recalculate_worker.rb b/app/workers/authorized_project_update/periodic_recalculate_worker.rb new file mode 100644 index 00000000000..0d1ad67d7bb --- /dev/null +++ b/app/workers/authorized_project_update/periodic_recalculate_worker.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module AuthorizedProjectUpdate + class PeriodicRecalculateWorker + include ApplicationWorker + # This worker does not perform work scoped to a context + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext + + feature_category :source_code_management + urgency :low + + idempotent! + + def perform + if ::Feature.enabled?(:periodic_project_authorization_recalculation, default_enabled: true) + AuthorizedProjectUpdate::PeriodicRecalculateService.new.execute + end + end + end +end diff --git a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb new file mode 100644 index 00000000000..336b1c5443e --- /dev/null +++ b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module AuthorizedProjectUpdate + class UserRefreshOverUserRangeWorker + include ApplicationWorker + + feature_category :authentication_and_authorization + urgency :low + queue_namespace :authorized_project_update + deduplicate :until_executing, including_scheduled: true + + idempotent! + + def perform(start_user_id, end_user_id) + if ::Feature.enabled?(:periodic_project_authorization_recalculation, default_enabled: true) + AuthorizedProjectUpdate::RecalculateForUserRangeService.new(start_user_id, end_user_id).execute + end + end + end +end |