Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
81c3057591
commit
b1b7c2f9a7
79 changed files with 917 additions and 334 deletions
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: {
|
||||
|
|
22
app/assets/javascripts/alert_management/store/index.js
Normal file
22
app/assets/javascripts/alert_management/store/index.js
Normal file
|
@ -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();
|
|
@ -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);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export const SET_ALERTS = 'SET_ALERTS';
|
||||
export const SET_LOADING = 'SET_LOADING';
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export default () => ({
|
||||
alerts: [],
|
||||
loading: false,
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ export default {
|
|||
.then(res => res.data)
|
||||
.then(data => {
|
||||
eventHub.$emit('UpdateWidgetData', data);
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
})
|
||||
.catch(() => {
|
||||
this.isCancellingAutoMerge = false;
|
||||
|
|
|
@ -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.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.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);
|
||||
}
|
||||
|
|
26
app/assets/stylesheets/pages/alerts_list.scss
Normal file
26
app/assets/stylesheets/pages/alerts_list.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.flash-container.flash-container-page
|
||||
.flash-alert.deprecated-service
|
||||
%span= @service.deprecation_message
|
|
@ -2,6 +2,4 @@
|
|||
- breadcrumb_title @service.title
|
||||
- page_title @service.title, "Service Templates"
|
||||
|
||||
= render 'deprecated_message' if @service.deprecation_message
|
||||
|
||||
= render 'form'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.flash-container.flash-container-page
|
||||
.flash-alert.deprecated-service
|
||||
%span= @service.deprecation_message
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
|
33
app/workers/concerns/reactive_cacheable_worker.rb
Normal file
33
app/workers/concerns/reactive_cacheable_worker.rb
Normal file
|
@ -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
|
7
app/workers/external_service_reactive_caching_worker.rb
Normal file
7
app/workers/external_service_reactive_caching_worker.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExternalServiceReactiveCachingWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ReactiveCacheableWorker
|
||||
|
||||
worker_has_external_dependencies!
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add policies for managing 'default_branch_protection' setting in groups
|
||||
merge_request: 29879
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/213892-Alerts-empty-state.yml
Normal file
5
changelogs/unreleased/213892-Alerts-empty-state.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Empty state for alerts list
|
||||
merge_request: 30215
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the cancel comment note text to a less ambiguous statement.
|
||||
merge_request: 30189
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use stricter regex for broadcast target path
|
||||
merge_request: 30210
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/sh-cleanup-mwps-refresh.yml
Normal file
5
changelogs/unreleased/sh-cleanup-mwps-refresh.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Clean up refresh fix for cancel automatic merge
|
||||
merge_request: 29844
|
||||
author:
|
||||
type: other
|
|
@ -100,6 +100,8 @@
|
|||
- 1
|
||||
- - export_csv
|
||||
- 1
|
||||
- - external_service_reactive_caching
|
||||
- 1
|
||||
- - file_hook
|
||||
- 1
|
||||
- - gcp_cluster
|
||||
|
|
|
@ -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
|
|
@ -13285,6 +13285,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200310135818
|
||||
20200310135823
|
||||
20200310145304
|
||||
20200310215714
|
||||
20200311074438
|
||||
20200311082301
|
||||
20200311084025
|
||||
|
|
|
@ -209,6 +209,7 @@ Piwik
|
|||
PgBouncer
|
||||
plaintext
|
||||
PostgreSQL
|
||||
precompile
|
||||
preconfigure
|
||||
preconfigured
|
||||
preconfigures
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
You may also have a look at our troubleshooting guides for:
|
||||
|
||||
- [Troubleshooting Guide (GitLab)](../index.md#troubleshooting)
|
||||
- [Troubleshooting Guide (Omnibus GitLab)](https://docs.gitlab.com/omnibus/README.html#troubleshooting)
|
||||
- [GitLab](../index.md#troubleshooting)
|
||||
- [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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
To have a simple summary of projects using **Legacy** storage:
|
||||
The following are Rake tasks for listing projects.
|
||||
|
||||
### List projects on 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
|
||||
|
||||
To have a simple summary of project attachments using **Legacy** storage:
|
||||
The following are Rake tasks for listing attachments.
|
||||
|
||||
### List attachments on 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**
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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). |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
```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
|
||||
1. Copy your bare repositories inside this newly created folder. Note:
|
||||
|
||||
- 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:
|
||||
- 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.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
```shell
|
||||
sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/new_group/
|
||||
```shell
|
||||
sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/new_group/
|
||||
|
||||
# Do this once when you are done copying git repositories
|
||||
sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date>
|
||||
```
|
||||
# Do this once when you are done copying git repositories
|
||||
sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date>
|
||||
```
|
||||
|
||||
`foo.git` needs to be owned by the `git` user and `git` users group.
|
||||
`foo.git` needs to be owned by the `git` user and `git` users group.
|
||||
|
||||
If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`.
|
||||
If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`.
|
||||
|
||||
### Run the command below depending on your type of installation
|
||||
1. Run the following command depending on your type of installation:
|
||||
|
||||
#### Omnibus Installation
|
||||
- Omnibus Installation
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>']
|
||||
```
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-<date>']
|
||||
```
|
||||
|
||||
#### Installation from source
|
||||
- Installation from source. Before running this command you need to change to the directory where
|
||||
your GitLab installation is located:
|
||||
|
||||
Before running this command you need to change the directory to 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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
expect(group.reload.default_branch_protection).to eq(::Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
|
||||
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)
|
||||
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
|
||||
|
|
4
spec/fixtures/trace/sample_trace
vendored
4
spec/fixtures/trace/sample_trace
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
function mountComponent({ stubs = {} } = {}) {
|
||||
const findAlertsTable = () => wrapper.find(GlTable);
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 initialize(id, &blk)
|
||||
@id = id
|
||||
@calculator = blk
|
||||
end
|
||||
|
||||
def calculate_reactive_cache
|
||||
@calculator.call
|
||||
end
|
||||
|
||||
def result
|
||||
with_reactive_cache do |data|
|
||||
data
|
||||
def self.primary_key
|
||||
:id
|
||||
end
|
||||
|
||||
def initialize(id, &blk)
|
||||
@id = id
|
||||
@calculator = blk
|
||||
end
|
||||
|
||||
def calculate_reactive_cache
|
||||
@calculator.call
|
||||
end
|
||||
|
||||
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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ExternalServiceReactiveCachingWorker do
|
||||
it_behaves_like 'reactive cacheable worker'
|
||||
end
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue