diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-24 12:09:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-24 12:09:44 +0300 |
commit | b1b7c2f9a744197a111c81719c546a474adab4e8 (patch) | |
tree | 2fe9d392110fb1fee8a8e6eac1f520425fee1c9c | |
parent | 81c305759174e2f55176356e98d264ea1c4b747d (diff) |
Add latest changes from gitlab-org/gitlab@master
79 files changed, 915 insertions, 332 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index db8a15176bf..5bc5fa8b9cc 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -1,5 +1,12 @@ Please view this file on the master branch, on stable branches it's out of date. +## 12.10.1 (2020-04-24) + +### Changed (1 change) + +- Move project deploy tokens section back to Repository settings. !29280 + + ## 12.10.0 (2020-04-22) ### Fixed (6 changes, 1 of them is from the community) diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue index f7910e5d3fa..3448a1e15ae 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_list.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue @@ -1,17 +1,62 @@ <script> -import { GlEmptyState, GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { mapState } from 'vuex'; +import { GlEmptyState, GlButton, GlLoadingIcon, GlTable, GlAlert } from '@gitlab/ui'; +import { __ } from '~/locale'; + +const tdClass = 'table-col d-flex'; export default { + fields: [ + { + key: 'severity', + label: __('Severity'), + tdClass, + }, + { + key: 'start_time', + label: __('Start Time'), + tdClass, + }, + { + key: 'end_time', + label: __('End Time'), + tdClass, + }, + { + key: 'alert', + label: __('Alert'), + thClass: 'w-30p', + tdClass, + }, + { + key: 'events', + label: __('Events'), + tdClass, + }, + { + key: 'status', + label: __('Status'), + tdClass, + }, + ], components: { GlEmptyState, GlButton, GlLoadingIcon, + GlTable, + GlAlert, }, props: { indexPath: { type: String, required: true, }, + // TODO: Handle alertManagementEnabled depending on resolution - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30024. + alertManagementEnabled: { + type: Boolean, + required: false, + default: true, + }, enableAlertManagementPath: { type: String, required: true, @@ -23,19 +68,45 @@ export default { }, data() { return { - alerts: [], - loading: false, + isAlertDismissed: false, }; }, + computed: { + ...mapState('list', ['alerts', 'loading']), + showNoAlertsMsg() { + return !this.alerts.length && !this.isAlertDismissed; + }, + }, }; </script> <template> <div> - <div v-if="alerts.length > 0" class="alert-management-list"> + <div v-if="alertManagementEnabled" class="alert-management-list"> + <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true"> + {{ + __( + `No alerts available to display. If you think you're seeing this message in error, refresh the page.`, + ) + }} + </gl-alert> <div v-if="loading" class="py-3"> <gl-loading-icon size="md" /> </div> + + <gl-table + class="mt-3" + :items="alerts" + :fields="$options.fields" + :show-empty="true" + fixed + stacked="sm" + tbody-tr-class="table-row mb-4" + > + <template #empty> + {{ __('No alerts to display.') }} + </template> + </gl-table> </div> <template v-else> <gl-empty-state :title="__('Surface alerts in GitLab')" :svg-path="emptyAlertSvgPath"> @@ -51,9 +122,9 @@ export default { </a> </div> <div class="d-block center pt-4"> - <gl-button category="primary" variant="success" :href="enableAlertManagementPath">{{ - __('Authorize external service') - }}</gl-button> + <gl-button category="primary" variant="success" :href="enableAlertManagementPath"> + {{ __('Authorize external service') }} + </gl-button> </div> </template> </gl-empty-state> diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js index 9f0efbc999a..53d8601cdfc 100644 --- a/app/assets/javascripts/alert_management/list.js +++ b/app/assets/javascripts/alert_management/list.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import store from './store'; import AlertManagementList from './components/alert_management_list.vue'; export default () => { @@ -12,6 +13,7 @@ export default () => { components: { AlertManagementList, }, + store, render(createElement) { return createElement('alert-management-list', { props: { diff --git a/app/assets/javascripts/alert_management/store/index.js b/app/assets/javascripts/alert_management/store/index.js new file mode 100644 index 00000000000..3a4157d5364 --- /dev/null +++ b/app/assets/javascripts/alert_management/store/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; + +import * as listActions from './list/actions'; +import listMutations from './list/mutations'; +import listState from './list/state'; + +Vue.use(Vuex); + +export const createStore = () => + new Vuex.Store({ + modules: { + list: { + namespaced: true, + state: listState(), + actions: listActions, + mutations: listMutations, + }, + }, + }); + +export default createStore(); diff --git a/app/assets/javascripts/alert_management/store/list/actions.js b/app/assets/javascripts/alert_management/store/list/actions.js new file mode 100644 index 00000000000..ac37e730bae --- /dev/null +++ b/app/assets/javascripts/alert_management/store/list/actions.js @@ -0,0 +1,9 @@ +import * as types from './mutation_types'; + +export const setAlerts = ({ commit }, alerts) => { + commit(types.SET_ALERTS, alerts); +}; + +export const setLoading = ({ commit }, loading) => { + commit(types.SET_LOADING, loading); +}; diff --git a/app/assets/javascripts/alert_management/store/list/mutation_types.js b/app/assets/javascripts/alert_management/store/list/mutation_types.js new file mode 100644 index 00000000000..1efbcbefd1c --- /dev/null +++ b/app/assets/javascripts/alert_management/store/list/mutation_types.js @@ -0,0 +1,2 @@ +export const SET_ALERTS = 'SET_ALERTS'; +export const SET_LOADING = 'SET_LOADING'; diff --git a/app/assets/javascripts/alert_management/store/list/mutations.js b/app/assets/javascripts/alert_management/store/list/mutations.js new file mode 100644 index 00000000000..559103524ef --- /dev/null +++ b/app/assets/javascripts/alert_management/store/list/mutations.js @@ -0,0 +1,10 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_ALERTS](state, alerts) { + state.alerts = alerts; + }, + [types.SET_LOADING](state, loading) { + state.loading = loading; + }, +}; diff --git a/app/assets/javascripts/alert_management/store/list/state.js b/app/assets/javascripts/alert_management/store/list/state.js new file mode 100644 index 00000000000..56e74b85c92 --- /dev/null +++ b/app/assets/javascripts/alert_management/store/list/state.js @@ -0,0 +1,4 @@ +export default () => ({ + alerts: [], + loading: false, +}); diff --git a/app/assets/javascripts/integrations/edit/components/active_toggle.vue b/app/assets/javascripts/integrations/edit/components/active_toggle.vue index 2b0aa2586e4..8b95b04d93c 100644 --- a/app/assets/javascripts/integrations/edit/components/active_toggle.vue +++ b/app/assets/javascripts/integrations/edit/components/active_toggle.vue @@ -12,10 +12,6 @@ export default { type: Boolean, required: true, }, - disabled: { - type: Boolean, - required: true, - }, }, data() { return { @@ -41,12 +37,7 @@ export default { <div class="form-group row" role="group"> <label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label> <div class="col-sm-10 pt-1"> - <gl-toggle - v-model="activated" - :disabled="disabled" - name="service[active]" - @change="onToggle" - /> + <gl-toggle v-model="activated" name="service[active]" @change="onToggle" /> </div> </div> </div> diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index a2ba581d429..25ae6015f1d 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -7,10 +7,9 @@ export default el => { return null; } - const { showActive: showActiveStr, activated: activatedStr, disabled: disabledStr } = el.dataset; + const { showActive: showActiveStr, activated: activatedStr } = el.dataset; const showActive = parseBoolean(showActiveStr); const activated = parseBoolean(activatedStr); - const disabled = parseBoolean(disabledStr); if (!showActive) { return null; @@ -22,7 +21,6 @@ export default el => { return createElement(ActiveToggle, { props: { initialActivated: activated, - disabled, }, }); }, diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 9e2231922b7..6d045baae2e 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -256,7 +256,7 @@ export default class Notes { discussionNoteForm = $textarea.closest('.js-discussion-note-form'); if (discussionNoteForm.length) { if ($textarea.val() !== '') { - if (!window.confirm(__('Are you sure you want to cancel creating this comment?'))) { + if (!window.confirm(__('Your comment will be discarded.'))) { return; } } @@ -268,7 +268,7 @@ export default class Notes { originalText = $textarea.closest('form').data('originalNote'); newText = $textarea.val(); if (originalText !== newText) { - if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) { + if (!window.confirm(__('Are you sure you want to discard this comment?'))) { return; } } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index a368e29d086..5b578e42f91 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -52,7 +52,6 @@ export default { .then(res => res.data) .then(data => { eventHub.$emit('UpdateWidgetData', data); - eventHub.$emit('MRWidgetUpdateRequested'); }) .catch(() => { this.isCancellingAutoMerge = false; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 9f001dda540..65ef89f7ed3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -123,15 +123,13 @@ export default class MergeRequestStore { const currentUser = data.current_user; - if (currentUser) { - this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path; - this.revertInForkPath = currentUser.revert_in_fork_path; - - this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; - this.canCreateIssue = currentUser.can_create_issue || false; - this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; - this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; - } + this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path; + this.revertInForkPath = currentUser.revert_in_fork_path; + + this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; + this.canCreateIssue = currentUser.can_create_issue || false; + this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; + this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; this.setState(data); } diff --git a/app/assets/stylesheets/pages/alerts_list.scss b/app/assets/stylesheets/pages/alerts_list.scss new file mode 100644 index 00000000000..74a5031a343 --- /dev/null +++ b/app/assets/stylesheets/pages/alerts_list.scss @@ -0,0 +1,26 @@ +// these styles need to be deleted once GlTable component looks in GitLab same as in @gitlab/ui +.alert-management-list { + table { + color: $gray-700; + + tr { + td, + th { + @include gl-p-4; + } + + th { + background-color: transparent; + font-weight: $gl-font-weight-bold; + color: $gl-gray-600; + @include gl-border-b-1; + @include gl-border-b-solid; + border-color: $gray-100; + } + + td { + @include gl-border-0; + } + } + } +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 2c0ca792ec3..d26c07ce51b 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -335,15 +335,6 @@ } } -.deprecated-service { - cursor: default; - - a { - font-weight: $gl-font-weight-bold; - color: $white; - } -} - .personal-access-tokens-never-expires-label { color: $note-disabled-comment-color; } @@ -401,4 +392,3 @@ } } } - diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 91f8bc33e3e..a6c3c97a873 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -49,6 +49,10 @@ module GroupsHelper can?(current_user, :change_visibility_level, group) end + def can_update_default_branch_protection?(group) + can?(current_user, :update_default_branch_protection, group) + end + def can_change_share_with_group_lock?(group) can?(current_user, :change_share_with_group_lock, group) end diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index f3f4cdc857f..ec60c62794a 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -51,17 +51,13 @@ module ServicesHelper end end - def service_save_button(service) - button_tag(class: 'btn btn-success', type: 'submit', disabled: service.deprecated?, data: { qa_selector: 'save_changes_button' }) do + def service_save_button + button_tag(class: 'btn btn-success', type: 'submit', data: { qa_selector: 'save_changes_button' }) do icon('spinner spin', class: 'hidden js-btn-spinner') + content_tag(:span, 'Save changes', class: 'js-btn-label') end end - def disable_fields_service?(service) - !current_controller?("admin/services") && service.deprecated? - end - def scoped_integrations_path if @project.present? project_settings_integrations_path(@project) diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 0a536a01f72..856f86201ec 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -105,7 +105,10 @@ class BroadcastMessage < ApplicationRecord def matches_current_path(current_path) return true if current_path.blank? || target_path.blank? - current_path.match(Regexp.escape(target_path).gsub('\\*', '.*')) + escaped = Regexp.escape(target_path).gsub('\\*', '.*') + regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE + + regexp.match(current_path) end def flush_redis_cache diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index 4b472cfdf45..d294563139c 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -8,6 +8,11 @@ module ReactiveCaching InvalidateReactiveCache = Class.new(StandardError) ExceededReactiveCacheLimit = Class.new(StandardError) + WORK_TYPE = { + default: ReactiveCachingWorker, + external_dependency: ExternalServiceReactiveCachingWorker + }.freeze + included do extend ActiveModel::Naming @@ -16,6 +21,7 @@ module ReactiveCaching class_attribute :reactive_cache_refresh_interval class_attribute :reactive_cache_lifetime class_attribute :reactive_cache_hard_limit + class_attribute :reactive_cache_work_type class_attribute :reactive_cache_worker_finder # defaults @@ -24,6 +30,7 @@ module ReactiveCaching self.reactive_cache_refresh_interval = 1.minute self.reactive_cache_lifetime = 10.minutes self.reactive_cache_hard_limit = 1.megabyte + self.reactive_cache_work_type = :default self.reactive_cache_worker_finder = ->(id, *_args) do find_by(primary_key => id) end @@ -112,7 +119,7 @@ module ReactiveCaching def refresh_reactive_cache!(*args) clear_reactive_cache!(*args) keep_alive_reactive_cache!(*args) - ReactiveCachingWorker.perform_async(self.class, id, *args) + worker_class.perform_async(self.class, id, *args) end def keep_alive_reactive_cache!(*args) @@ -145,7 +152,11 @@ module ReactiveCaching def enqueuing_update(*args) yield - ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args) + worker_class.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args) + end + + def worker_class + WORK_TYPE.fetch(self.class.reactive_cache_work_type.to_sym) end def check_exceeded_reactive_cache_limit!(data) diff --git a/app/models/service.rb b/app/models/service.rb index 543869c71d6..f314d119a8e 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -345,14 +345,6 @@ class Service < ApplicationRecord service end - def deprecated? - false - end - - def deprecation_message - nil - end - # override if needed def supports_data_fields? false diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 9353b361c2a..b6cf945bf5a 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -74,6 +74,10 @@ class GlobalPolicy < BasePolicy enable :create_group end + rule { can?(:create_group) }.policy do + enable :create_group_with_default_branch_protection + end + rule { can_create_fork }.policy do enable :create_fork end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 728c4b76498..7d503b49c14 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -123,6 +123,7 @@ class GroupPolicy < BasePolicy enable :set_note_created_at enable :set_emails_disabled + enable :update_default_branch_protection end rule { can?(:read_nested_project_resources) }.policy do diff --git a/app/services/discussions/capture_diff_note_position_service.rb b/app/services/discussions/capture_diff_note_position_service.rb index 273a60f7e55..8f12470d9e8 100644 --- a/app/services/discussions/capture_diff_note_position_service.rb +++ b/app/services/discussions/capture_diff_note_position_service.rb @@ -17,6 +17,7 @@ module Discussions return unless result position = result[:position] + return unless position # Currently position data is copied across all notes of a discussion # It makes sense to store a position only for the first note instead diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 8cc31200689..eb1b8d4fcc0 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -38,6 +38,10 @@ module Groups # overridden in EE end + def remove_unallowed_params + params.delete(:default_branch_protection) unless can?(current_user, :create_group_with_default_branch_protection) + end + def create_chat_team? Gitlab.config.mattermost.enabled && @chat_team && group.chat_team.nil? end diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 8635b82461b..948540619ae 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -66,6 +66,7 @@ module Groups # overridden in EE def remove_unallowed_params params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, group) + params.delete(:default_branch_protection) unless can?(current_user, :update_default_branch_protection, group) end def valid_share_with_group_lock_change? diff --git a/app/views/admin/services/_deprecated_message.html.haml b/app/views/admin/services/_deprecated_message.html.haml deleted file mode 100644 index fea9506a4bb..00000000000 --- a/app/views/admin/services/_deprecated_message.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.flash-container.flash-container-page - .flash-alert.deprecated-service - %span= @service.deprecation_message diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 79f5ab0d77d..00ed5464a44 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -2,6 +2,4 @@ - breadcrumb_title @service.title - page_title @service.title, "Service Templates" -= render 'deprecated_message' if @service.deprecation_message - = render 'form' diff --git a/app/views/groups/settings/_default_branch_protection.html.haml b/app/views/groups/settings/_default_branch_protection.html.haml new file mode 100644 index 00000000000..e0e901cbc4a --- /dev/null +++ b/app/views/groups/settings/_default_branch_protection.html.haml @@ -0,0 +1,3 @@ +- return unless can_update_default_branch_protection?(group) + += render 'shared/default_branch_protection', f: f, selected_level: group.default_branch_protection diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 1ddaa855e62..e886c99a656 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -33,7 +33,7 @@ = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group = render 'groups/settings/lfs', f: f - = render 'shared/default_branch_protection', f: f, selected_level: @group.default_branch_protection + = render 'groups/settings/default_branch_protection', f: f, group: @group = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/subgroup_creation_level', f: f, group: @group = render 'groups/settings/two_factor_auth', f: f diff --git a/app/views/projects/services/_deprecated_message.html.haml b/app/views/projects/services/_deprecated_message.html.haml deleted file mode 100644 index fea9506a4bb..00000000000 --- a/app/views/projects/services/_deprecated_message.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.flash-container.flash-container-page - .flash-alert.deprecated-service - %span= @service.deprecation_message diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 0dbd6a48ec5..fbdb3e7941e 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -13,7 +13,7 @@ = form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = render 'shared/service_settings', form: form, service: @service .footer-block.row-content-block - = service_save_button(@service) + = service_save_button = link_to _('Cancel'), project_settings_integrations_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 4195dce7780..03e7cadc552 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -2,8 +2,6 @@ - add_to_breadcrumbs _('Integration Settings'), project_settings_integrations_path(@project) - page_title @service.title, _('Integrations') -= render 'deprecated_message' if @service.deprecation_message - = render 'form' - if @web_hook_logs = render partial: 'projects/hook_logs/index', locals: { hook: @service.service_hook, hook_logs: @web_hook_logs, project: @project } diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml index a7ad6d6f2c4..53718b4c001 100644 --- a/app/views/shared/_field.html.haml +++ b/app/views/shared/_field.html.haml @@ -7,7 +7,6 @@ - choices = field[:choices] - default_choice = field[:default_choice] - help = field[:help] -- disabled = disable_fields_service?(@service) .form-group.row - if type == "password" && value.present? @@ -16,14 +15,14 @@ = form.label name, title, class: "col-form-label col-sm-2" .col-sm-10 - if type == 'text' - = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" } + = form.text_field name, class: "form-control", placeholder: placeholder, required: required, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" } - elsif type == 'textarea' - = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required, disabled: disabled + = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required - elsif type == 'checkbox' - = form.check_box name, disabled: disabled + = form.check_box name - elsif type == 'select' - = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control", disabled: disabled} + = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control"} - elsif type == 'password' - = form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, disabled: disabled, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" } + = form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" } - if help %span.form-text.text-muted= help diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 3da4b77b5eb..7262c47f9d3 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -8,7 +8,7 @@ = markdown @service.help .service-settings - .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, disabled: disable_fields_service?(@service).to_s } } + .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s } } - if @service.configurable_events.present? .form-group.row diff --git a/app/views/shared/integrations/_form.html.haml b/app/views/shared/integrations/_form.html.haml index 0ddab1368c2..4ec7f286c7a 100644 --- a/app/views/shared/integrations/_form.html.haml +++ b/app/views/shared/integrations/_form.html.haml @@ -10,5 +10,5 @@ - if integration.editable? .footer-block.row-content-block - = service_save_button(integration) + = service_save_button = link_to _('Cancel'), scoped_integration_path(integration), class: 'btn btn-cancel' diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 694f2c23e1e..27e69f5d7d5 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1060,6 +1060,13 @@ :resource_boundary: :cpu :weight: 1 :idempotent: +- :name: external_service_reactive_caching + :feature_category: :not_owned + :has_external_dependencies: true + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: - :name: file_hook :feature_category: :integrations :has_external_dependencies: diff --git a/app/workers/concerns/reactive_cacheable_worker.rb b/app/workers/concerns/reactive_cacheable_worker.rb new file mode 100644 index 00000000000..e73707c2b43 --- /dev/null +++ b/app/workers/concerns/reactive_cacheable_worker.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ReactiveCacheableWorker + extend ActiveSupport::Concern + + included do + include ApplicationWorker + + feature_category_not_owned! + + def self.context_for_arguments(arguments) + class_name, *_other_args = arguments + Gitlab::ApplicationContext.new(related_class: class_name.to_s) + end + end + + def perform(class_name, id, *args) + klass = begin + class_name.constantize + rescue NameError + nil + end + + return unless klass + + klass + .reactive_cache_worker_finder + .call(id, *args) + .try(:exclusively_update_reactive_cache!, *args) + rescue ReactiveCaching::ExceededReactiveCacheLimit => e + Gitlab::ErrorTracking.track_exception(e) + end +end diff --git a/app/workers/external_service_reactive_caching_worker.rb b/app/workers/external_service_reactive_caching_worker.rb new file mode 100644 index 00000000000..e3104b44a7f --- /dev/null +++ b/app/workers/external_service_reactive_caching_worker.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ExternalServiceReactiveCachingWorker # rubocop:disable Scalability/IdempotentWorker + include ReactiveCacheableWorker + + worker_has_external_dependencies! +end diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb index 513033281e5..f112a22419d 100644 --- a/app/workers/reactive_caching_worker.rb +++ b/app/workers/reactive_caching_worker.rb @@ -1,36 +1,8 @@ # frozen_string_literal: true class ReactiveCachingWorker # rubocop:disable Scalability/IdempotentWorker - include ApplicationWorker + include ReactiveCacheableWorker - feature_category_not_owned! - - # TODO: The reactive caching worker should be split into - # two different workers, one for high urgency jobs without external dependencies - # and another worker without high urgency, but with external dependencies - # https://gitlab.com/gitlab-com/gl-infra/scalability/issues/34 - # This worker should also have `worker_has_external_dependencies!` enabled urgency :high worker_resource_boundary :cpu - - def self.context_for_arguments(arguments) - class_name, *_other_args = arguments - Gitlab::ApplicationContext.new(related_class: class_name.to_s) - end - - def perform(class_name, id, *args) - klass = begin - class_name.constantize - rescue NameError - nil - end - return unless klass - - klass - .reactive_cache_worker_finder - .call(id, *args) - .try(:exclusively_update_reactive_cache!, *args) - rescue ReactiveCaching::ExceededReactiveCacheLimit => e - Gitlab::ErrorTracking.track_exception(e) - end end diff --git a/changelogs/unreleased/211944-provide-instance-level-setting-to-enable-or-disable-default-branch-add-policies.yml b/changelogs/unreleased/211944-provide-instance-level-setting-to-enable-or-disable-default-branch-add-policies.yml new file mode 100644 index 00000000000..e8762cf6dcd --- /dev/null +++ b/changelogs/unreleased/211944-provide-instance-level-setting-to-enable-or-disable-default-branch-add-policies.yml @@ -0,0 +1,5 @@ +--- +title: Add policies for managing 'default_branch_protection' setting in groups +merge_request: 29879 +author: +type: added diff --git a/changelogs/unreleased/213892-Alerts-empty-state.yml b/changelogs/unreleased/213892-Alerts-empty-state.yml new file mode 100644 index 00000000000..5421297c324 --- /dev/null +++ b/changelogs/unreleased/213892-Alerts-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Empty state for alerts list +merge_request: 30215 +author: +type: added diff --git a/changelogs/unreleased/215352-update-cancel-comment-note-text.yml b/changelogs/unreleased/215352-update-cancel-comment-note-text.yml new file mode 100644 index 00000000000..7a9417e4295 --- /dev/null +++ b/changelogs/unreleased/215352-update-cancel-comment-note-text.yml @@ -0,0 +1,5 @@ +--- +title: Update the cancel comment note text to a less ambiguous statement. +merge_request: 30189 +author: +type: changed diff --git a/changelogs/unreleased/nicolasdular-improve-target-path-regex.yml b/changelogs/unreleased/nicolasdular-improve-target-path-regex.yml new file mode 100644 index 00000000000..4b007c572d4 --- /dev/null +++ b/changelogs/unreleased/nicolasdular-improve-target-path-regex.yml @@ -0,0 +1,5 @@ +--- +title: Use stricter regex for broadcast target path +merge_request: 30210 +author: +type: changed diff --git a/changelogs/unreleased/sh-cleanup-mwps-refresh.yml b/changelogs/unreleased/sh-cleanup-mwps-refresh.yml new file mode 100644 index 00000000000..8c2489a6d7d --- /dev/null +++ b/changelogs/unreleased/sh-cleanup-mwps-refresh.yml @@ -0,0 +1,5 @@ +--- +title: Clean up refresh fix for cancel automatic merge +merge_request: 29844 +author: +type: other diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 9e446cd1b9a..5d5096215ff 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -100,6 +100,8 @@ - 1 - - export_csv - 1 +- - external_service_reactive_caching + - 1 - - file_hook - 1 - - gcp_cluster diff --git a/db/post_migrate/20200310215714_migrate_saml_identities_to_scim_identities.rb b/db/post_migrate/20200310215714_migrate_saml_identities_to_scim_identities.rb new file mode 100644 index 00000000000..e2ec7b62d31 --- /dev/null +++ b/db/post_migrate/20200310215714_migrate_saml_identities_to_scim_identities.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class MigrateSamlIdentitiesToScimIdentities < ActiveRecord::Migration[6.0] + DOWNTIME = false + + class Identity < ActiveRecord::Base + self.table_name = 'identities' + + include ::EachBatch + end + + def up + Identity + .joins('INNER JOIN saml_providers ON saml_providers.id = identities.saml_provider_id') + .where('saml_providers.group_id IN (SELECT group_id FROM scim_oauth_access_tokens)') + .select('identities.extern_uid, identities.user_id, saml_providers.group_id, TRUE AS active, + identities.created_at, CURRENT_TIMESTAMP AS updated_at') + .each_batch do |batch| + data_to_insert = batch.map do |record| + record.attributes.extract!("extern_uid", "user_id", "group_id", "active", "created_at", "updated_at") + end + + Gitlab::Database.bulk_insert(:scim_identities, data_to_insert, on_conflict: :do_nothing) + end + end + + def down + end +end diff --git a/db/structure.sql b/db/structure.sql index 982af99b2a0..96211531462 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13285,6 +13285,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200310135818 20200310135823 20200310145304 +20200310215714 20200311074438 20200311082301 20200311084025 diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 3f0d61ed791..cd951c29e23 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -209,6 +209,7 @@ Piwik PgBouncer plaintext PostgreSQL +precompile preconfigure preconfigured preconfigures diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index f55c6e8e86b..fd869548fb4 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -1,6 +1,8 @@ -# Integrity Check Rake Task +# Integrity check Rake task -## Repository Integrity +GitLab provides Rake tasks to check the integrity of various components. + +## Repository integrity Even though Git is very resilient and tries to prevent data integrity issues, there are times when things go wrong. The following Rake tasks intend to @@ -43,7 +45,7 @@ sudo gitlab-rake gitlab:git:fsck sudo -u git -H bundle exec rake gitlab:git:fsck RAILS_ENV=production ``` -## Uploaded Files Integrity +## Uploaded files integrity Various types of files can be uploaded to a GitLab installation by users. These integrity checks can detect missing files. Additionally, for locally @@ -127,7 +129,7 @@ Checking integrity of Uploads Done! ``` -## LDAP Check +## LDAP check The LDAP check Rake task will test the bind DN and password credentials (if configured) and will list a sample of LDAP users. This task is also diff --git a/doc/administration/raketasks/geo.md b/doc/administration/raketasks/geo.md index 707ed1dbee0..71e4f922348 100644 --- a/doc/administration/raketasks/geo.md +++ b/doc/administration/raketasks/geo.md @@ -1,9 +1,11 @@ # Geo Rake Tasks **(PREMIUM ONLY)** +The following Rake tasks are for [Geo installations](../geo/replication/index.md). + ## Git housekeeping There are few tasks you can run to schedule a Git housekeeping to start at the -next repository sync in a **Secondary node**: +next repository sync in a **secondary** node: ### Incremental Repack diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md index 4c6521f38ec..2c2046de564 100644 --- a/doc/administration/raketasks/github_import.md +++ b/doc/administration/raketasks/github_import.md @@ -9,7 +9,7 @@ which will become the owner of the project. You can resume an import with the same command. Bear in mind that the syntax is very specific. Remove any spaces within the argument block and -before/after the brackets. Also, Some shells (e.g., zsh) can interpret the open/close brackets +before/after the brackets. Also, some shells (for example, `zsh`) can interpret the open/close brackets (`[]`) separately. You may need to either escape the brackets or use double quotes. ## Importing multiple projects diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md index 361ab28f6d4..ba08a41d92b 100644 --- a/doc/administration/raketasks/ldap.md +++ b/doc/administration/raketasks/ldap.md @@ -1,4 +1,6 @@ -# LDAP Rake Tasks +# LDAP Rake tasks + +The following are LDAP-related Rake tasks. ## Check @@ -26,7 +28,7 @@ limit by passing a number to the check task: rake gitlab:ldap:check[50] ``` -## Run a Group Sync +## Run a group sync > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/14735) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.2. diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index c79a1aa6ba1..56d5d239d15 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -1,8 +1,11 @@ -# Maintenance Rake Tasks +# Maintenance Rake tasks -## Gather information about GitLab and the system it runs on +GitLab provides Rake tasks for general maintenance. -This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues. +## Gather GitLab and system information + +This command gathers information about your GitLab installation and the system it runs on. +These may be useful when asking for help or reporting issues. **Omnibus Installation** @@ -50,20 +53,23 @@ Git: /usr/bin/git ## Check GitLab configuration -Runs the following Rake tasks: +The `gitlab:check` Rake task runs the following Rake tasks: - `gitlab:gitlab_shell:check` - `gitlab:gitaly:check` - `gitlab:sidekiq:check` - `gitlab:app:check` -It will check that each component was set up according to the installation guide and suggest fixes for issues found. -This command must be run from your app server and will not work correctly on component servers like [Gitaly](../gitaly/index.md#running-gitaly-on-its-own-server). +It will check that each component was set up according to the installation guide and suggest fixes +for issues found. This command must be run from your application server and will not work correctly on +component servers like [Gitaly](../gitaly/index.md#running-gitaly-on-its-own-server). + +You may also have a look at our troubleshooting guides for: -You may also have a look at our Troubleshooting Guides: +- [GitLab](../index.md#troubleshooting) +- [Omnibus GitLab](https://docs.gitlab.com/omnibus/README.html#troubleshooting) -- [Troubleshooting Guide (GitLab)](../index.md#troubleshooting) -- [Troubleshooting Guide (Omnibus GitLab)](https://docs.gitlab.com/omnibus/README.html#troubleshooting) +To run `gitlab:check`, run: **Omnibus Installation** @@ -77,7 +83,8 @@ sudo gitlab-rake gitlab:check bundle exec rake gitlab:check RAILS_ENV=production ``` -NOTE: Use `SANITIZE=true` for `gitlab:check` if you want to omit project names from the output. +NOTE: **Note:** +Use `SANITIZE=true` for `gitlab:check` if you want to omit project names from the output. Example output: @@ -126,7 +133,7 @@ Checking GitLab ... Finished ## Rebuild authorized_keys file -In some case it is necessary to rebuild the `authorized_keys` file. +In some case it is necessary to rebuild the `authorized_keys` file. To do this, run: **Omnibus Installation** @@ -141,6 +148,8 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production ``` +Example output: + ```plaintext This will rebuild an authorized_keys file. You will lose any data stored in authorized_keys file. @@ -149,8 +158,8 @@ Do you want to continue (yes/no)? yes ## Clear Redis cache -If for some reason the dashboard shows wrong information you might want to -clear Redis' cache. +If for some reason the dashboard displays the wrong information, you might want to +clear Redis' cache. To do this, run: **Omnibus Installation** @@ -170,7 +179,7 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production Sometimes during version upgrades you might end up with some wrong CSS or missing some icons. In that case, try to precompile the assets again. -Note that this only applies to source installations and does NOT apply to +This only applies to source installations and does NOT apply to Omnibus packages. **Source Installation** @@ -193,6 +202,8 @@ GitLab provides a Rake task that lets you track deployments in GitLab Performance Monitoring. This Rake task simply stores the current GitLab version in the GitLab Performance Monitoring database. +To run `gitlab:track_deployment`: + **Omnibus Installation** ```shell diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md index 6d874d596e1..2ab8b13e29e 100644 --- a/doc/administration/raketasks/project_import_export.md +++ b/doc/administration/raketasks/project_import_export.md @@ -3,11 +3,13 @@ > - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/3050) in GitLab 8.9. > - From GitLab 11.3, import/export can use object storage automatically. -See also: +GitLab provides Rake tasks relating to project import and export. For more information, see: - [Project import/export documentation](../../user/project/settings/import_export.md). - [Project import/export API](../../api/project_import_export.md). +## Import/export tasks + The GitLab import/export version can be checked by using the following command: ```shell @@ -28,8 +30,6 @@ sudo gitlab-rake gitlab:import_export:data bundle exec rake gitlab:import_export:data RAILS_ENV=production ``` -## Important notes - Note the following: - Importing is only possible if the version of the import and export GitLab instances are diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md index e5ed9cd0dc1..5d55b6cb9fe 100644 --- a/doc/administration/raketasks/storage.md +++ b/doc/administration/raketasks/storage.md @@ -1,4 +1,4 @@ -# Repository Storage Rake Tasks +# Repository storage Rake tasks This is a collection of Rake tasks you can use to help you list and migrate existing projects and attachments associated with it from Legacy storage to @@ -6,7 +6,7 @@ the new Hashed storage type. You can read more about the storage types [here](../repository_storage_types.md). -## Migrate existing projects to Hashed storage +## Migrate existing projects to hashed storage Before migrating your existing projects, you should [enable hashed storage](../repository_storage_types.md#how-to-migrate-to-hashed-storage) for the new projects as well. @@ -34,9 +34,9 @@ export ID_FROM=20 export ID_TO=50 ``` -You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page. -There is a specific Queue you can watch to see how long it will take to finish: -`hashed_storage:hashed_storage_project_migrate` +You can monitor the progress in the **{admin}** **Admin Area > Monitoring > Background Jobs** page. +There is a specific queue you can watch to see how long it will take to finish: +`hashed_storage:hashed_storage_project_migrate`. After it reaches zero, you can confirm every project has been migrated by running the commands bellow. If you find it necessary, you can run this migration script again to schedule missing projects. @@ -44,16 +44,18 @@ If you find it necessary, you can run this migration script again to schedule mi Any error or warning will be logged in Sidekiq's log file. NOTE: **Note:** -If Geo is enabled, each project that is successfully migrated generates an event to replicate the changes on any **secondary** nodes. +If [Geo](../geo/replication/index.md) is enabled, each project that is successfully migrated +generates an event to replicate the changes on any **secondary** nodes. You only need the `gitlab:storage:migrate_to_hashed` Rake task to migrate your repositories, but we have additional commands below that helps you inspect projects and attachments in both legacy and hashed storage. -## Rollback from Hashed storage to Legacy storage +## Rollback from hashed storage to legacy storage If you need to rollback the storage migration for any reason, you can follow the steps described here. -NOTE: **Note:** Hashed Storage will be required in future version of GitLab. +NOTE: **Note:** +Hashed storage will be required in future version of GitLab. To prevent new projects from being created in the Hashed storage, you need to undo the [enable hashed storage](../repository_storage_types.md#how-to-migrate-to-hashed-storage) changes. @@ -81,7 +83,7 @@ export ID_FROM=20 export ID_TO=50 ``` -You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page. +You can monitor the progress in the **{admin}** **Admin Area > Monitoring > Background Jobs** page. On the **Queues** tab, you can watch the `hashed_storage:hashed_storage_project_rollback` queue to see how long the process will take to finish. After it reaches zero, you can confirm every project has been rolled back by running the commands bellow. @@ -89,9 +91,13 @@ If some projects weren't rolled back, you can run this rollback script again to Any error or warning will be logged in Sidekiq's log file. -## List projects on Legacy storage +## List projects + +The following are Rake tasks for listing projects. + +### List projects on legacy storage -To have a simple summary of projects using **Legacy** storage: +To have a simple summary of projects using legacy storage: **Omnibus Installation** @@ -105,7 +111,7 @@ sudo gitlab-rake gitlab:storage:legacy_projects sudo -u git -H bundle exec rake gitlab:storage:legacy_projects RAILS_ENV=production ``` -To list projects using **Legacy** storage: +To list projects using legacy storage: **Omnibus Installation** @@ -120,9 +126,9 @@ sudo -u git -H bundle exec rake gitlab:storage:list_legacy_projects RAILS_ENV=pr ``` -## List projects on Hashed storage +### List projects on hashed storage -To have a simple summary of projects using **Hashed** storage: +To have a simple summary of projects using hashed storage: **Omnibus Installation** @@ -136,7 +142,7 @@ sudo gitlab-rake gitlab:storage:hashed_projects sudo -u git -H bundle exec rake gitlab:storage:hashed_projects RAILS_ENV=production ``` -To list projects using **Hashed** storage: +To list projects using hashed storage: **Omnibus Installation** @@ -150,9 +156,13 @@ sudo gitlab-rake gitlab:storage:list_hashed_projects sudo -u git -H bundle exec rake gitlab:storage:list_hashed_projects RAILS_ENV=production ``` -## List attachments on Legacy storage +## List attachments + +The following are Rake tasks for listing attachments. + +### List attachments on legacy storage -To have a simple summary of project attachments using **Legacy** storage: +To have a simple summary of project attachments using legacy storage: **Omnibus Installation** @@ -166,7 +176,7 @@ sudo gitlab-rake gitlab:storage:legacy_attachments sudo -u git -H bundle exec rake gitlab:storage:legacy_attachments RAILS_ENV=production ``` -To list project attachments using **Legacy** storage: +To list project attachments using legacy storage: **Omnibus Installation** @@ -180,9 +190,9 @@ sudo gitlab-rake gitlab:storage:list_legacy_attachments sudo -u git -H bundle exec rake gitlab:storage:list_legacy_attachments RAILS_ENV=production ``` -## List attachments on Hashed storage +### List attachments on hashed storage -To have a simple summary of project attachments using **Hashed** storage: +To have a simple summary of project attachments using hashed storage: **Omnibus Installation** @@ -196,7 +206,7 @@ sudo gitlab-rake gitlab:storage:hashed_attachments sudo -u git -H bundle exec rake gitlab:storage:hashed_attachments RAILS_ENV=production ``` -To list project attachments using **Hashed** storage: +To list project attachments using hashed storage: **Omnibus Installation** diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index f6d00988c56..a7acc0c2b55 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -2,7 +2,7 @@ ## List all deploy keys -Get a list of all deploy keys across all projects of the GitLab instance. This endpoint requires admin access. +Get a list of all deploy keys across all projects of the GitLab instance. This endpoint requires admin access and is not available on GitLab.com. ```plaintext GET /deploy_keys diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 16dda494ff1..4dfe174ea18 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -97,7 +97,6 @@ The following table lists available parameters for jobs: | [`services`](#services) | Use docker services images. Also available: `services:name`, `services:alias`, `services:entrypoint`, and `services:command`. | | [`before_script`](#before_script-and-after_script) | Override a set of commands that are executed before job. | | [`after_script`](#before_script-and-after_script) | Override a set of commands that are executed after job. | -| [`stages`](#stages) | Define stages in a pipeline. | | [`stage`](#stage) | Defines a job stage (default: `test`). | | [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). | | [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). | diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md index 0021dceefc8..f3386305e93 100644 --- a/doc/development/reactive_caching.md +++ b/doc/development/reactive_caching.md @@ -85,6 +85,9 @@ The ReactiveCaching concern can be used in models as well as `project_services` 1. Implement the `calculate_reactive_cache` method in your model/service. 1. Call `with_reactive_cache` in your model/service where the cached value is needed. +1. If the `calculate_reactive_cache` method above submits requests to external services +(e.g. Prometheus, K8s), make sure to change the +[`reactive_cache_work_type` accordingly](#selfreactive_cache_work_type). ### In controllers @@ -244,6 +247,13 @@ and will silently raise `ReactiveCaching::ExceededReactiveCacheLimit` on Sentry. self.reactive_cache_hard_limit = 5.megabytes ``` +#### `self.reactive_cache_work_type` + +- This is the type of work performed by the `calculate_reactive_cache` method. Based on this attribute, +it's able to pick the right worker to process the caching job. Make sure to +set it as `:external_dependency` if the work performs any external request +(e.g. Kubernetes, Sentry). + #### `self.reactive_cache_worker_finder` - This is the method used by the background worker to find or generate the object on diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index cda742b6077..b6847dfbdb1 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,61 +1,63 @@ -# Import bare repositories into your GitLab instance +# Import bare repositories -## Notes +Rake tasks are available to import bare repositories into a GitLab instance. -- The owner of the project will be the first admin -- The groups will be created as needed, including subgroups -- The owner of the group will be the first admin -- Existing projects will be skipped -- Projects in hashed storage may be skipped (see [Importing bare repositories from hashed storage](#importing-bare-repositories-from-hashed-storage)) -- The existing Git repos will be moved from disk (removed from the original path) +Note that: -## How to use +- The owner of the project will be the first administrator. +- The groups will be created as needed, including subgroups. +- The owner of the group will be the first administrator. +- Existing projects will be skipped. +- Projects in hashed storage may be skipped. For more information, see + [Importing bare repositories from hashed storage](#importing-bare-repositories-from-hashed-storage). +- The existing Git repositories will be moved from disk (removed from the original path). -### Create a new folder to import your Git repositories from +To import bare repositories into a GitLab instance: -The new folder needs to have Git user ownership and read/write/execute access for Git user and its group: +1. Create a new folder to import your Git repositories from. The new folder needs to have Git user + ownership and read/write/execute access for Git user and its group: -```shell -sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group -``` - -### Copy your bare repositories inside this newly created folder + ```shell + sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group + ``` -- Any `.git` repositories found on any of the subfolders will be imported as projects -- Groups will be created as needed, these could be nested folders. Example: +1. Copy your bare repositories inside this newly created folder. Note: -If we copy the repos to `/var/opt/gitlab/git-data/repository-import-<date>`, and repo A needs to be under the groups G1 and G2, it will -have to be created under those folders: `/var/opt/gitlab/git-data/repository-import-<date>/G1/G2/A.git`. + - Any `.git` repositories found on any of the subfolders will be imported as projects. + - Groups will be created as needed, these could be nested folders. -```shell -sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/new_group/ + For example, if we copy the repositories to `/var/opt/gitlab/git-data/repository-import-<date>`, + and repository `A` needs to be under the groups `G1` and `G2`, it must be created under those folders: + `/var/opt/gitlab/git-data/repository-import-<date>/G1/G2/A.git`. -# Do this once when you are done copying git repositories -sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date> -``` + ```shell + sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/new_group/ -`foo.git` needs to be owned by the `git` user and `git` users group. + # Do this once when you are done copying git repositories + sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date> + ``` -If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`. + `foo.git` needs to be owned by the `git` user and `git` users group. -### Run the command below depending on your type of installation + If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`. -#### Omnibus Installation +1. Run the following command depending on your type of installation: -```shell -sudo gitlab-rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>'] -``` + - Omnibus Installation -#### Installation from source + ```shell + sudo gitlab-rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>'] + ``` -Before running this command you need to change the directory to where your GitLab installation is located: + - Installation from source. Before running this command you need to change to the directory where + your GitLab installation is located: -```shell -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>'] RAILS_ENV=production -``` + ```shell + cd /home/git/gitlab + sudo -u git -H bundle exec rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>'] RAILS_ENV=production + ``` -#### Example output +## Example output ```plaintext Processing /var/opt/gitlab/git-data/repository-import-1/a/b/c/blah.git @@ -73,8 +75,6 @@ Processing /var/opt/gitlab/git-data/repository-import-1/group/xyz.git ## Importing bare repositories from hashed storage -### Background - Projects in legacy storage have a directory structure that mirrors their full project path in GitLab, including their namespace structure. This information is leveraged by the bare repository importer to import projects into their proper @@ -86,17 +86,17 @@ improved performance and data integrity. See [Repository Storage Types](../administration/repository_storage_types.md) for more details. -### Which repositories are importable? +The repositories that are importable depends on the version of GitLab. -#### GitLab 10.3 or earlier +### GitLab 10.3 or earlier Importing bare repositories from hashed storage is unsupported. -#### GitLab 10.4 and later +### GitLab 10.4 and later To support importing bare repositories from hashed storage, GitLab 10.4 and later stores the full project path with each repository, in a special section of -the Git repository's config file. This section is formatted as follows: +the Git repository's configuration file. This section is formatted as follows: ```ini [gitlab] diff --git a/doc/raketasks/list_repos.md b/doc/raketasks/list_repos.md index 10e6cb04bfa..6437ff583ce 100644 --- a/doc/raketasks/list_repos.md +++ b/doc/raketasks/list_repos.md @@ -1,7 +1,8 @@ # Listing repository directories -You can print a list of all Git repositories on disk managed by -GitLab with the following command: +You can print a list of all Git repositories on disk managed by GitLab. + +To print a list, run the following command: ```shell # Omnibus @@ -12,10 +13,13 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production ``` -If you only want to list projects with recent activity you can pass -a date with the 'SINCE' environment variable. The time you specify -is parsed by the Rails [TimeZone#parse -function](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse). +NOTE: **Note:** +The results use the default ordering of the GitLab Rails application. + +## Limit search results + +To list only projects with recent activity, pass a date with the `SINCE` environment variable. The +time you specify is parsed by the Rails [TimeZone#parse function](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse). ```shell # Omnibus @@ -25,6 +29,3 @@ sudo gitlab-rake gitlab:list_repos SINCE='Sep 1 2015' cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production SINCE='Sep 1 2015' ``` - -Note that the projects listed are NOT sorted by activity; they use -the default ordering of the GitLab Rails application. diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 8bf7fcf6624..f6e924c8d2f 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -128,9 +128,9 @@ before deploying one. NOTE: **Note:** The [`runner/gitlab-runner`](https://gitlab.com/gitlab-org/charts/gitlab-runner) -chart is used to install this application with a -[`values.yaml`](https://gitlab.com/gitlab-org/gitlab/blob/master/vendor/runner/values.yaml) -file. Customizing installation by modifying this file is not supported. +chart is used to install this application, using +[a preconfigured `values.yaml`](https://gitlab.com/gitlab-org/charts/gitlab-runner/-/blob/master/values.yaml) +file. Customizing the installation by modifying this file is not supported. ### Ingress diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1e55090bc8f..9a6d3ab1bed 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2420,9 +2420,6 @@ msgstr "" msgid "Are you sure that you want to unarchive this project?" msgstr "" -msgid "Are you sure you want to cancel creating this comment?" -msgstr "" - msgid "Are you sure you want to cancel editing this comment?" msgstr "" @@ -2456,6 +2453,9 @@ msgstr "" msgid "Are you sure you want to deploy this environment?" msgstr "" +msgid "Are you sure you want to discard this comment?" +msgstr "" + msgid "Are you sure you want to erase this build?" msgstr "" @@ -7835,6 +7835,9 @@ msgstr "" msgid "Encountered an error while rendering: %{err}" msgstr "" +msgid "End Time" +msgstr "" + msgid "End date" msgstr "" @@ -13661,6 +13664,12 @@ msgstr "" msgid "No activities found" msgstr "" +msgid "No alerts available to display. If you think you're seeing this message in error, refresh the page." +msgstr "" + +msgid "No alerts to display." +msgstr "" + msgid "No application_settings found" msgstr "" @@ -18768,6 +18777,9 @@ msgstr "" msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings." msgstr "" +msgid "Severity" +msgstr "" + msgid "Severity: %{severity}" msgstr "" @@ -19550,6 +19562,9 @@ msgstr "" msgid "Start GitLab Ultimate trial" msgstr "" +msgid "Start Time" +msgstr "" + msgid "Start Web Terminal" msgstr "" @@ -24252,6 +24267,9 @@ msgstr "" msgid "Your comment could not be updated! Please check your network connection and try again." msgstr "" +msgid "Your comment will be discarded." +msgstr "" + msgid "Your custom stage '%{title}' was created" msgstr "" diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 93478bbff1d..354c9e047c8 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -270,6 +270,37 @@ describe GroupsController do it { expect(subject).to render_template(:new) } end + + context 'when creating a group with `default_branch_protection` attribute' do + before do + sign_in(user) + end + + subject do + post :create, params: { group: { name: 'new_group', path: 'new_group', default_branch_protection: Gitlab::Access::PROTECTION_NONE } } + end + + context 'for users who have the ability to create a group with `default_branch_protection`' do + it 'creates group with the specified branch protection level' do + subject + + expect(response).to have_gitlab_http_status(:found) + expect(Group.last.default_branch_protection).to eq(Gitlab::Access::PROTECTION_NONE) + end + end + + context 'for users who do not have the ability to create a group with `default_branch_protection`' do + it 'does not create the group with the specified branch protection level' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :create_group_with_default_branch_protection) { false } + + subject + + expect(response).to have_gitlab_http_status(:found) + expect(Group.last.default_branch_protection).not_to eq(Gitlab::Access::PROTECTION_NONE) + end + end + end end describe 'GET #index' do @@ -423,11 +454,31 @@ describe GroupsController do expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) end - it 'updates the default_branch_protection successfully' do - post :update, params: { id: group.to_param, group: { default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE } } + context 'updating default_branch_protection' do + subject do + put :update, params: { id: group.to_param, group: { default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE } } + end + + context 'for users who have the ability to update default_branch_protection' do + it 'updates the attribute' do + subject - expect(response).to have_gitlab_http_status(:found) - expect(group.reload.default_branch_protection).to eq(::Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + expect(response).to have_gitlab_http_status(:found) + expect(group.reload.default_branch_protection).to eq(::Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + end + end + + context 'for users who do not have the ability to update default_branch_protection' do + it 'does not update the attribute' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :update_default_branch_protection, group) { false } + + subject + + expect(response).to have_gitlab_http_status(:found) + expect(group.reload.default_branch_protection).not_to eq(::Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + end + end end context 'when a project inside the group has container repositories' do diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace index e76712782be..e9d1e79fc71 100644 --- a/spec/fixtures/trace/sample_trace +++ b/spec/fixtures/trace/sample_trace @@ -2768,10 +2768,6 @@ Service updates the has_external_issue_tracker boolean on update updates the has_external_issue_tracker boolean - #deprecated? - should return false by default - #deprecation_message - should be empty by default #api_field_names filters out sensitive fields diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js index 2bfbf37d7f0..09d67a4e860 100644 --- a/spec/frontend/alert_management/components/alert_management_list_spec.js +++ b/spec/frontend/alert_management/components/alert_management_list_spec.js @@ -1,17 +1,28 @@ -import { mount } from '@vue/test-utils'; -import { GlEmptyState } from '@gitlab/ui'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { GlEmptyState, GlTable, GlAlert } from '@gitlab/ui'; +import Vuex from 'vuex'; import stubChildren from 'helpers/stub_children'; import AlertManagementList from '~/alert_management/components/alert_management_list.vue'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('AlertManagementList', () => { let wrapper; + let store; + + const findAlertsTable = () => wrapper.find(GlTable); + const findAlert = () => wrapper.find(GlAlert); - function mountComponent({ stubs = {} } = {}) { + function mountComponent({ stubs = {}, alertManagementEnabled = false } = {}) { wrapper = mount(AlertManagementList, { + localVue, + store, propsData: { indexPath: '/path', enableAlertManagementPath: '/link', emptyAlertSvgPath: 'illustration/path', + alertManagementEnabled, }, stubs: { ...stubChildren(AlertManagementList), @@ -20,19 +31,43 @@ describe('AlertManagementList', () => { }); } + beforeEach(() => { + store = new Vuex.Store({ + modules: { + list: { + namespaced: true, + }, + }, + }); + mountComponent(); + }); + afterEach(() => { if (wrapper) { wrapper.destroy(); + store = null; } }); describe('alert management feature renders empty state', () => { - beforeEach(() => { - mountComponent(); - }); - it('shows empty state', () => { expect(wrapper.find(GlEmptyState).exists()).toBe(true); }); }); + + describe('Alerts table', () => { + it('shows empty list', () => { + store.state.list = { + alerts: [], + loading: false, + }; + + mountComponent({ alertManagementEnabled: true }); + + return wrapper.vm.$nextTick().then(() => { + expect(findAlertsTable().exists()).toBe(true); + expect(findAlert().text()).toContain('No alerts available to display'); + }); + }); + }); }); diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index ac2f028f937..5be247c5b49 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -340,4 +340,31 @@ describe GroupsHelper do end end end + + describe '#can_update_default_branch_protection?' do + let(:current_user) { create(:user) } + let(:group) { create(:group) } + + subject { helper.can_update_default_branch_protection?(group) } + + before do + allow(helper).to receive(:current_user) { current_user } + end + + context 'for users who can update default branch protection of the group' do + before do + allow(helper).to receive(:can?).with(current_user, :update_default_branch_protection, group) { true } + end + + it { is_expected.to be_truthy } + end + + context 'for users who cannot update default branch protection of the group' do + before do + allow(helper).to receive(:can?).with(current_user, :update_default_branch_protection, group) { false } + end + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 6cef81d6e44..127faa5e8e2 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -143,6 +143,24 @@ describe BroadcastMessage do expect(subject.call('/group/groupname/issues').length).to eq(0) end + + it 'does not return message if target path has no wild card at the end' do + create(:broadcast_message, target_path: "*/issues", broadcast_type: broadcast_type) + + expect(subject.call('/group/issues/test').length).to eq(0) + end + + it 'does not return message if target path has wild card at the end' do + create(:broadcast_message, target_path: "/issues/*", broadcast_type: broadcast_type) + + expect(subject.call('/group/issues/test').length).to eq(0) + end + + it 'does return message if target path has wild card at the beginning and the end' do + create(:broadcast_message, target_path: "*/issues/*", broadcast_type: broadcast_type) + + expect(subject.call('/group/issues/test').length).to eq(1) + end end describe '.current', :use_clean_rails_memory_store_caching do diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 96a9c317fb8..cfca383e0b0 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -6,39 +6,47 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do include ExclusiveLeaseHelpers include ReactiveCachingHelpers - class CacheTest - include ReactiveCaching + let(:cache_class_test) do + Class.new do + include ReactiveCaching - self.reactive_cache_key = ->(thing) { ["foo", thing.id] } + self.reactive_cache_key = ->(thing) { ["foo", thing.id] } - self.reactive_cache_lifetime = 5.minutes - self.reactive_cache_refresh_interval = 15.seconds + self.reactive_cache_lifetime = 5.minutes + self.reactive_cache_refresh_interval = 15.seconds - attr_reader :id + attr_reader :id - def self.primary_key - :id - end + def self.primary_key + :id + end - def initialize(id, &blk) - @id = id - @calculator = blk - end + def initialize(id, &blk) + @id = id + @calculator = blk + end - def calculate_reactive_cache - @calculator.call - end + def calculate_reactive_cache + @calculator.call + end - def result - with_reactive_cache do |data| - data + def result + with_reactive_cache do |data| + data + end end end end + let(:external_dependency_cache_class_test) do + Class.new(cache_class_test) do + self.reactive_cache_work_type = :external_dependency + end + end + let(:calculation) { -> { 2 + 2 } } let(:cache_key) { "foo:666" } - let(:instance) { CacheTest.new(666, &calculation) } + let(:instance) { cache_class_test.new(666, &calculation) } describe '#with_reactive_cache' do before do @@ -47,6 +55,18 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do subject(:go!) { instance.result } + shared_examples 'reactive worker call' do |worker_class| + let(:instance) do + test_class.new(666, &calculation) + end + + it 'performs caching with correct worker' do + expect(worker_class).to receive(:perform_async).with(test_class, 666) + + go! + end + end + shared_examples 'a cacheable value' do |cached_value| before do stub_reactive_cache(instance, cached_value) @@ -73,10 +93,12 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do it { is_expected.to be_nil } - it 'refreshes cache' do - expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666) + it_behaves_like 'reactive worker call', ReactiveCachingWorker do + let(:test_class) { cache_class_test } + end - instance.with_reactive_cache { raise described_class::InvalidateReactiveCache } + it_behaves_like 'reactive worker call', ExternalServiceReactiveCachingWorker do + let(:test_class) { external_dependency_cache_class_test } end end end @@ -84,10 +106,12 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do context 'when cache is empty' do it { is_expected.to be_nil } - it 'enqueues a background worker to bootstrap the cache' do - expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666) + it_behaves_like 'reactive worker call', ReactiveCachingWorker do + let(:test_class) { cache_class_test } + end - go! + it_behaves_like 'reactive worker call', ExternalServiceReactiveCachingWorker do + let(:test_class) { external_dependency_cache_class_test } end it 'updates the cache lifespan' do @@ -168,12 +192,14 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do context 'with custom reactive_cache_worker_finder' do let(:args) { %w(arg1 arg2) } - let(:instance) { CustomFinderCacheTest.new(666, &calculation) } + let(:instance) { custom_finder_cache_test.new(666, &calculation) } - class CustomFinderCacheTest < CacheTest - self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } + let(:custom_finder_cache_test) do + Class.new(cache_class_test) do + self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } - def self.from_cache(*args); end + def self.from_cache(*args); end + end end before do @@ -234,6 +260,18 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do go! end + context 'when :external_dependency cache' do + let(:instance) do + external_dependency_cache_class_test.new(666, &calculation) + end + + it 'enqueues a repeat worker' do + expect_reactive_cache_update_queued(instance, worker_klass: ExternalServiceReactiveCachingWorker) + + go! + end + end + it 'calls a reactive_cache_updated only once if content did not change on subsequent update' do expect(instance).to receive(:calculate_reactive_cache).twice expect(instance).to receive(:reactive_cache_updated).once @@ -262,7 +300,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do it_behaves_like 'ExceededReactiveCacheLimit' context 'when reactive_cache_hard_limit is overridden' do - let(:test_class) { Class.new(CacheTest) { self.reactive_cache_hard_limit = 3.megabytes } } + let(:test_class) { Class.new(cache_class_test) { self.reactive_cache_hard_limit = 3.megabytes } } let(:instance) { test_class.new(666, &calculation) } it_behaves_like 'successful cache' diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index cb8122b6573..9662ea31960 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -523,24 +523,6 @@ describe Service do end end - describe "#deprecated?" do - let(:project) { create(:project, :repository) } - - it 'returns false by default' do - service = create(:service, project: project) - expect(service.deprecated?).to be_falsy - end - end - - describe "#deprecation_message" do - let(:project) { create(:project, :repository) } - - it 'is empty by default' do - service = create(:service, project: project) - expect(service.deprecation_message).to be_nil - end - end - describe '#api_field_names' do let(:fake_service) do Class.new(Service) do diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 5e77b64a408..ac7f5db980c 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -80,6 +80,34 @@ describe GlobalPolicy do end end + describe 'create group' do + context 'when user has the ability to create group' do + let(:current_user) { create(:user, can_create_group: true) } + + it { is_expected.to be_allowed(:create_group) } + end + + context 'when user does not have the ability to create group' do + let(:current_user) { create(:user, can_create_group: false) } + + it { is_expected.not_to be_allowed(:create_group) } + end + end + + describe 'create group with default branch protection' do + context 'when user has the ability to create group' do + let(:current_user) { create(:user, can_create_group: true) } + + it { is_expected.to be_allowed(:create_group_with_default_branch_protection) } + end + + context 'when user does not have the ability to create group' do + let(:current_user) { create(:user, can_create_group: false) } + + it { is_expected.not_to be_allowed(:create_group_with_default_branch_protection) } + end + end + describe 'custom attributes' do context 'regular user' do it { is_expected.not_to be_allowed(:read_custom_attribute) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 30c1f99569b..bca7d54d3a8 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -642,6 +642,33 @@ describe API::Groups do expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) end + context 'updating the `default_branch_protection` attribute' do + subject do + put api("/groups/#{group1.id}", user1), params: { default_branch_protection: ::Gitlab::Access::PROTECTION_NONE } + end + + context 'for users who have the ability to update default_branch_protection' do + it 'updates the attribute' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_NONE) + end + end + + context 'for users who does not have the ability to update default_branch_protection`' do + it 'does not update the attribute' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user1, :update_default_branch_protection, group1) { false } + + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['default_branch_protection']).not_to eq(Gitlab::Access::PROTECTION_NONE) + end + end + end + context 'malicious group name' do subject { put api("/groups/#{group1.id}", user1), params: { name: "<SCRIPT>alert('DOUBLE-ATTACK!')</SCRIPT>" } } @@ -1111,6 +1138,33 @@ describe API::Groups do it { expect { subject }.not_to change { Group.count } } end + context 'when creating a group with `default_branch_protection` attribute' do + let(:params) { attributes_for_group_api default_branch_protection: Gitlab::Access::PROTECTION_NONE } + + subject { post api("/groups", user3), params: params } + + context 'for users who have the ability to create a group with `default_branch_protection`' do + it 'creates group with the specified branch protection level' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_NONE) + end + end + + context 'for users who do not have the ability to create a group with `default_branch_protection`' do + it 'does not create the group with the specified branch protection level' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user3, :create_group_with_default_branch_protection) { false } + + subject + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['default_branch_protection']).not_to eq(Gitlab::Access::PROTECTION_NONE) + end + end + end + it "does not create group, duplicate" do post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path } diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb index fced2eb7fce..bc71e170e92 100644 --- a/spec/services/discussions/capture_diff_note_position_service_spec.rb +++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb @@ -3,10 +3,11 @@ require 'spec_helper' describe Discussions::CaptureDiffNotePositionService do + subject { described_class.new(note.noteable, paths) } + context 'image note on diff' do let!(:note) { create(:image_diff_note_on_merge_request) } - - subject { described_class.new(note.noteable, ['files/images/any_image.png']) } + let(:paths) { ['files/images/any_image.png'] } it 'is note affected by the service' do expect(Gitlab::Diff::PositionTracer).not_to receive(:new) @@ -18,8 +19,7 @@ describe Discussions::CaptureDiffNotePositionService do context 'when empty paths are passed as a param' do let!(:note) { create(:diff_note_on_merge_request) } - - subject { described_class.new(note.noteable, []) } + let(:paths) { [] } it 'does not calculate positons' do expect(Gitlab::Diff::PositionTracer).not_to receive(:new) @@ -28,4 +28,19 @@ describe Discussions::CaptureDiffNotePositionService do expect(note.diff_note_positions).to be_empty end end + + context 'when position tracer returned nil position' do + let!(:note) { create(:diff_note_on_merge_request) } + let(:paths) { ['files/any_file.txt'] } + + it 'does not create diff note position' do + expect(note.noteable).to receive(:merge_ref_head).and_return(double.as_null_object) + expect_next_instance_of(Gitlab::Diff::PositionTracer) do |tracer| + expect(tracer).to receive(:trace).and_return({ position: nil }) + end + + expect(subject.execute(note.discussion)).to eq(nil) + expect(note.diff_note_positions).to be_empty + end + end end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 5cde9a3ed45..c0e876cce33 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -24,6 +24,27 @@ describe Groups::CreateService, '#execute' do end end + context 'creating a group with `default_branch_protection` attribute' do + let(:params) { group_params.merge(default_branch_protection: Gitlab::Access::PROTECTION_NONE) } + let(:service) { described_class.new(user, params) } + let(:created_group) { service.execute } + + context 'for users who have the ability to create a group with `default_branch_protection`' do + it 'creates group with the specified branch protection level' do + expect(created_group.default_branch_protection).to eq(Gitlab::Access::PROTECTION_NONE) + end + end + + context 'for users who do not have the ability to create a group with `default_branch_protection`' do + it 'does not create the group with the specified branch protection level' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :create_group_with_default_branch_protection) { false } + + expect(created_group.default_branch_protection).not_to eq(Gitlab::Access::PROTECTION_NONE) + end + end + end + describe 'creating a top level group' do let(:service) { described_class.new(user, group_params) } diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 1aa7e06182b..b17d78505d1 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -148,6 +148,26 @@ describe Groups::UpdateService do end end + context 'updating default_branch_protection' do + let(:service) do + described_class.new(internal_group, user, default_branch_protection: Gitlab::Access::PROTECTION_NONE) + end + + context 'for users who have the ability to update default_branch_protection' do + it 'updates the attribute' do + internal_group.add_owner(user) + + expect { service.execute }.to change { internal_group.default_branch_protection }.to(Gitlab::Access::PROTECTION_NONE) + end + end + + context 'for users who do not have the ability to update default_branch_protection' do + it 'does not update the attribute' do + expect { service.execute }.not_to change { internal_group.default_branch_protection } + end + end + end + context 'rename group' do let!(:service) { described_class.new(internal_group, user, path: SecureRandom.hex) } diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb index aa9d3b3a199..0b0b0622696 100644 --- a/spec/support/helpers/reactive_caching_helpers.rb +++ b/spec/support/helpers/reactive_caching_helpers.rb @@ -10,8 +10,11 @@ module ReactiveCachingHelpers end def stub_reactive_cache(subject = nil, data = nil, *qualifiers) - allow(ReactiveCachingWorker).to receive(:perform_async) - allow(ReactiveCachingWorker).to receive(:perform_in) + ReactiveCaching::WORK_TYPE.values.each do |worker| + allow(worker).to receive(:perform_async) + allow(worker).to receive(:perform_in) + end + write_reactive_cache(subject, data, *qualifiers) unless subject.nil? end @@ -42,8 +45,8 @@ module ReactiveCachingHelpers Rails.cache.write(alive_reactive_cache_key(subject, *qualifiers), true) end - def expect_reactive_cache_update_queued(subject) - expect(ReactiveCachingWorker) + def expect_reactive_cache_update_queued(subject, worker_klass: ReactiveCachingWorker) + expect(worker_klass) .to receive(:perform_in) .with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id) end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index c2797c49c02..de64cea6474 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -35,7 +35,8 @@ RSpec.shared_context 'GroupPolicy context' do :change_visibility_level, :set_note_created_at, :create_subgroup, - :read_statistics + :read_statistics, + :update_default_branch_protection ].compact end diff --git a/spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb b/spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb new file mode 100644 index 00000000000..0bbd0e2a90d --- /dev/null +++ b/spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'reactive cacheable worker' do + describe '#perform' do + context 'when reactive cache worker class is found' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + let!(:environment) { create(:environment, project: project) } + + it 'calls #exclusively_update_reactive_cache!' do + expect_any_instance_of(Environment).to receive(:exclusively_update_reactive_cache!) + + described_class.new.perform("Environment", environment.id) + end + + context 'when ReactiveCaching::ExceededReactiveCacheLimit is raised' do + it 'avoids failing the job and tracks via Gitlab::ErrorTracking' do + allow_any_instance_of(Environment).to receive(:exclusively_update_reactive_cache!) + .and_raise(ReactiveCaching::ExceededReactiveCacheLimit) + + expect(Gitlab::ErrorTracking).to receive(:track_exception) + .with(kind_of(ReactiveCaching::ExceededReactiveCacheLimit)) + + described_class.new.perform("Environment", environment.id) + end + end + end + + context 'when reactive cache worker class is not found' do + it 'raises no error' do + expect { described_class.new.perform("Environment", -1) }.not_to raise_error + end + end + + context 'when reactive cache worker class is invalid' do + it 'raises no error' do + expect { described_class.new.perform("FooBarKux", -1) }.not_to raise_error + end + end + end + + describe 'worker context' do + it 'sets the related class on the job' do + described_class.perform_async('Environment', 1, 'other', 'argument') + + scheduled_job = described_class.jobs.first + + expect(scheduled_job).to include('meta.related_class' => 'Environment') + end + + it 'sets the related class on the job when it was passed as a class' do + described_class.perform_async(Project, 1, 'other', 'argument') + + scheduled_job = described_class.jobs.first + + expect(scheduled_job).to include('meta.related_class' => 'Project') + end + end +end diff --git a/spec/workers/external_service_reactive_caching_worker_spec.rb b/spec/workers/external_service_reactive_caching_worker_spec.rb new file mode 100644 index 00000000000..45cce71b75b --- /dev/null +++ b/spec/workers/external_service_reactive_caching_worker_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ExternalServiceReactiveCachingWorker do + it_behaves_like 'reactive cacheable worker' +end diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb index 603ce6160ce..dcb804a7e6e 100644 --- a/spec/workers/reactive_caching_worker_spec.rb +++ b/spec/workers/reactive_caching_worker_spec.rb @@ -3,47 +3,5 @@ require 'spec_helper' describe ReactiveCachingWorker do - describe '#perform' do - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - let!(:environment) { create(:environment, project: project) } - - it 'calls #exclusively_update_reactive_cache!' do - expect_any_instance_of(Environment).to receive(:exclusively_update_reactive_cache!) - - described_class.new.perform("Environment", environment.id) - end - - context 'when ReactiveCaching::ExceededReactiveCacheLimit is raised' do - it 'avoids failing the job and tracks via Gitlab::ErrorTracking' do - allow_any_instance_of(Environment).to receive(:exclusively_update_reactive_cache!) - .and_raise(ReactiveCaching::ExceededReactiveCacheLimit) - - expect(Gitlab::ErrorTracking).to receive(:track_exception) - .with(kind_of(ReactiveCaching::ExceededReactiveCacheLimit)) - - described_class.new.perform("Environment", environment.id) - end - end - end - end - - describe 'worker context' do - it 'sets the related class on the job' do - described_class.perform_async('Environment', 1, 'other', 'argument') - - scheduled_job = described_class.jobs.first - - expect(scheduled_job).to include('meta.related_class' => 'Environment') - end - - it 'sets the related class on the job when it was passed as a class' do - described_class.perform_async(Project, 1, 'other', 'argument') - - scheduled_job = described_class.jobs.first - - expect(scheduled_job).to include('meta.related_class' => 'Project') - end - end + it_behaves_like 'reactive cacheable worker' end |