Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d7b7232142
commit
b9f288cdfa
20 changed files with 351 additions and 109 deletions
|
@ -0,0 +1,41 @@
|
|||
import $ from 'jquery';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
|
||||
/**
|
||||
* This behavior collapses the right sidebar
|
||||
* if the window size changes
|
||||
*
|
||||
* @sentrify
|
||||
*/
|
||||
export default () => {
|
||||
const $sidebarGutterToggle = $('.js-sidebar-toggle');
|
||||
let bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
||||
$(window).on('resize.app', () => {
|
||||
const oldBootstrapBreakpoint = bootstrapBreakpoint;
|
||||
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
|
||||
const breakpointSizes = ['md', 'sm', 'xs'];
|
||||
|
||||
if (breakpointSizes.includes(bootstrapBreakpoint)) {
|
||||
const $gutterIcon = $sidebarGutterToggle.find('i');
|
||||
if ($gutterIcon.hasClass('fa-angle-double-right')) {
|
||||
$sidebarGutterToggle.trigger('click');
|
||||
}
|
||||
|
||||
const sidebarGutterVueToggleEl = document.querySelector('.js-sidebar-vue-toggle');
|
||||
|
||||
// Sidebar has an icon which corresponds to collapsing the sidebar
|
||||
// only then trigger the click.
|
||||
if (sidebarGutterVueToggleEl) {
|
||||
const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right');
|
||||
|
||||
if (collapseIcon) {
|
||||
collapseIcon.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -11,9 +11,13 @@ import './requires_input';
|
|||
import initPageShortcuts from './shortcuts';
|
||||
import './toggler_behavior';
|
||||
import './preview_markdown';
|
||||
import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resize';
|
||||
import initSelect2Dropdowns from './select2';
|
||||
|
||||
installGlEmojiElement();
|
||||
initGFMInput();
|
||||
initCopyAsGFM();
|
||||
initCopyToClipboard();
|
||||
initPageShortcuts();
|
||||
initCollapseSidebarOnWindowResize();
|
||||
initSelect2Dropdowns();
|
||||
|
|
23
app/assets/javascripts/behaviors/select2.js
Normal file
23
app/assets/javascripts/behaviors/select2.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export default () => {
|
||||
if ($('select.select2').length) {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
minimumResultsForSearch: 10,
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
|
||||
// Close select2 on escape
|
||||
$('.js-select2').on('select2-close', () => {
|
||||
setTimeout(() => {
|
||||
$('.select2-container-active').removeClass('select2-container-active');
|
||||
$(':focus').blur();
|
||||
}, 1);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
};
|
|
@ -62,12 +62,12 @@ function disableJQueryAnimations() {
|
|||
}
|
||||
|
||||
// Disable jQuery animations
|
||||
if (gon && gon.disable_animations) {
|
||||
if (gon?.disable_animations) {
|
||||
disableJQueryAnimations();
|
||||
}
|
||||
|
||||
// inject test utilities if necessary
|
||||
if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) {
|
||||
if (process.env.NODE_ENV !== 'production' && gon?.test_env) {
|
||||
disableJQueryAnimations();
|
||||
import(/* webpackMode: "eager" */ './test_utils/'); // eslint-disable-line no-unused-expressions
|
||||
}
|
||||
|
@ -132,27 +132,6 @@ function deferredInitialisation() {
|
|||
.fadeOut();
|
||||
});
|
||||
|
||||
// Initialize select2 selects
|
||||
if ($('select.select2').length) {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
minimumResultsForSearch: 10,
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
|
||||
// Close select2 on escape
|
||||
$('.js-select2').on('select2-close', () => {
|
||||
setTimeout(() => {
|
||||
$('.select2-container-active').removeClass('select2-container-active');
|
||||
$(':focus').blur();
|
||||
}, 1);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
|
||||
const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
|
||||
|
||||
|
@ -179,9 +158,7 @@ function deferredInitialisation() {
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $body = $('body');
|
||||
const $document = $(document);
|
||||
const $window = $(window);
|
||||
const $sidebarGutterToggle = $('.js-sidebar-toggle');
|
||||
let bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
|
||||
|
||||
|
@ -199,6 +176,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO: Apparently we are collapsing the right sidebar on certain screensizes per default
|
||||
* except on issue board pages. Why can't we do it with CSS?
|
||||
*
|
||||
* Proposal: Expose a global sidebar API, which we could import wherever we are manipulating
|
||||
* the visibility of the sidebar.
|
||||
*
|
||||
* Quick fix: Get rid of jQuery for this implementation
|
||||
*/
|
||||
const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page);
|
||||
if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) {
|
||||
const $rightSidebar = $('aside.right-sidebar');
|
||||
|
@ -225,14 +211,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
localTimeAgo($('abbr.timeago, .js-timeago'), true);
|
||||
|
||||
// Form submitter
|
||||
$('.trigger-submit').on('change', function triggerSubmitCallback() {
|
||||
$(this)
|
||||
.parents('form')
|
||||
.submit();
|
||||
});
|
||||
|
||||
// Disable form buttons while a form is submitting
|
||||
/**
|
||||
* This disables form buttons while a form is submitting
|
||||
* We do not difinitively know all of the places where this is used
|
||||
*
|
||||
* TODO: Defer execution, migrate to behaviors, and add sentry logging
|
||||
*/
|
||||
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) {
|
||||
const $buttons = $('[type="submit"], .js-disable-on-submit', this).not('.js-no-auto-disable');
|
||||
switch (e.type) {
|
||||
|
@ -259,7 +243,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
$('.header-content').toggleClass('menu-expanded');
|
||||
});
|
||||
|
||||
// Commit show suppressed diff
|
||||
/**
|
||||
* Show suppressed commit diff
|
||||
*
|
||||
* TODO: Move to commit diff pages
|
||||
*/
|
||||
$document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() {
|
||||
const $container = $(this).parent();
|
||||
$container.next('table').show();
|
||||
|
@ -290,39 +278,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
$(document).trigger('toggle.comments');
|
||||
});
|
||||
|
||||
$document.on('breakpoint:change', (e, breakpoint) => {
|
||||
const breakpointSizes = ['md', 'sm', 'xs'];
|
||||
if (breakpointSizes.includes(breakpoint)) {
|
||||
const $gutterIcon = $sidebarGutterToggle.find('i');
|
||||
if ($gutterIcon.hasClass('fa-angle-double-right')) {
|
||||
$sidebarGutterToggle.trigger('click');
|
||||
}
|
||||
|
||||
const sidebarGutterVueToggleEl = document.querySelector('.js-sidebar-vue-toggle');
|
||||
|
||||
// Sidebar has an icon which corresponds to collapsing the sidebar
|
||||
// only then trigger the click.
|
||||
if (sidebarGutterVueToggleEl) {
|
||||
const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right');
|
||||
|
||||
if (collapseIcon) {
|
||||
collapseIcon.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function fitSidebarForSize() {
|
||||
const oldBootstrapBreakpoint = bootstrapBreakpoint;
|
||||
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
|
||||
$document.trigger('breakpoint:change', [bootstrapBreakpoint]);
|
||||
}
|
||||
}
|
||||
|
||||
$window.on('resize.app', fitSidebarForSize);
|
||||
|
||||
$('form.filter-form').on('submit', function filterFormSubmitCallback(event) {
|
||||
const link = document.createElement('a');
|
||||
link.href = this.action;
|
||||
|
|
|
@ -7,7 +7,7 @@ module PerformanceMonitoring
|
|||
attr_accessor :dashboard, :panel_groups, :path, :environment, :priority, :templating, :links
|
||||
|
||||
validates :dashboard, presence: true
|
||||
validates :panel_groups, presence: true
|
||||
validates :panel_groups, array_members: { member_class: PerformanceMonitoring::PrometheusPanelGroup }
|
||||
|
||||
class << self
|
||||
def from_json(json_content)
|
||||
|
@ -35,9 +35,15 @@ module PerformanceMonitoring
|
|||
|
||||
new(
|
||||
dashboard: attributes['dashboard'],
|
||||
panel_groups: attributes['panel_groups']&.map { |group| PrometheusPanelGroup.from_json(group) }
|
||||
panel_groups: initialize_children_collection(attributes['panel_groups'])
|
||||
)
|
||||
end
|
||||
|
||||
def initialize_children_collection(children)
|
||||
return unless children.is_a?(Array)
|
||||
|
||||
children.map { |group| PerformanceMonitoring::PrometheusPanelGroup.from_json(group) }
|
||||
end
|
||||
end
|
||||
|
||||
def to_yaml
|
||||
|
@ -47,7 +53,7 @@ module PerformanceMonitoring
|
|||
# This method is planned to be refactored as a part of https://gitlab.com/gitlab-org/gitlab/-/issues/219398
|
||||
# implementation. For new existing logic was reused to faster deliver MVC
|
||||
def schema_validation_warnings
|
||||
self.class.from_json(self.as_json)
|
||||
self.class.from_json(reload_schema)
|
||||
nil
|
||||
rescue ActiveModel::ValidationError => exception
|
||||
exception.model.errors.map { |attr, error| "#{attr}: #{error}" }
|
||||
|
@ -55,6 +61,14 @@ module PerformanceMonitoring
|
|||
|
||||
private
|
||||
|
||||
# dashboard finder methods are somehow limited, #find includes checking if
|
||||
# user is authorised to view selected dashboard, but modifies schema, which in some cases may
|
||||
# cause false positives returned from validation, and #find_raw does not authorise users
|
||||
def reload_schema
|
||||
project = environment&.project
|
||||
project.nil? ? self.as_json : Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: path)
|
||||
end
|
||||
|
||||
def yaml_valid_attributes
|
||||
%w(panel_groups panels metrics group priority type title y_label weight id unit label query query_range dashboard)
|
||||
end
|
||||
|
|
|
@ -7,7 +7,8 @@ module PerformanceMonitoring
|
|||
attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis, :max_value
|
||||
|
||||
validates :title, presence: true
|
||||
validates :metrics, presence: true
|
||||
validates :metrics, array_members: { member_class: PerformanceMonitoring::PrometheusMetric }
|
||||
|
||||
class << self
|
||||
def from_json(json_content)
|
||||
build_from_hash(json_content).tap(&:validate!)
|
||||
|
@ -23,9 +24,15 @@ module PerformanceMonitoring
|
|||
title: attributes['title'],
|
||||
y_label: attributes['y_label'],
|
||||
weight: attributes['weight'],
|
||||
metrics: attributes['metrics']&.map { |metric| PrometheusMetric.from_json(metric) }
|
||||
metrics: initialize_children_collection(attributes['metrics'])
|
||||
)
|
||||
end
|
||||
|
||||
def initialize_children_collection(children)
|
||||
return unless children.is_a?(Array)
|
||||
|
||||
children.map { |metrics| PerformanceMonitoring::PrometheusMetric.from_json(metrics) }
|
||||
end
|
||||
end
|
||||
|
||||
def id(group_title)
|
||||
|
|
|
@ -7,7 +7,8 @@ module PerformanceMonitoring
|
|||
attr_accessor :group, :priority, :panels
|
||||
|
||||
validates :group, presence: true
|
||||
validates :panels, presence: true
|
||||
validates :panels, array_members: { member_class: PerformanceMonitoring::PrometheusPanel }
|
||||
|
||||
class << self
|
||||
def from_json(json_content)
|
||||
build_from_hash(json_content).tap(&:validate!)
|
||||
|
@ -21,9 +22,15 @@ module PerformanceMonitoring
|
|||
new(
|
||||
group: attributes['group'],
|
||||
priority: attributes['priority'],
|
||||
panels: attributes['panels']&.map { |panel| PrometheusPanel.from_json(panel) }
|
||||
panels: initialize_children_collection(attributes['panels'])
|
||||
)
|
||||
end
|
||||
|
||||
def initialize_children_collection(children)
|
||||
return unless children.is_a?(Array)
|
||||
|
||||
children.map { |panels| PerformanceMonitoring::PrometheusPanel.from_json(panels) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
21
app/validators/array_members_validator.rb
Normal file
21
app/validators/array_members_validator.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# ArrayMembersValidator
|
||||
#
|
||||
# Custom validator that checks if validated
|
||||
# attribute contains non empty array, which every
|
||||
# element is an instances of :member_class
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Config::Root < ActiveRecord::Base
|
||||
# validates :nodes, member_class: Config::Node
|
||||
# end
|
||||
#
|
||||
class ArrayMembersValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
if !value.is_a?(Array) || value.empty? || value.any? { |child| !child.instance_of?(options[:member_class]) }
|
||||
record.errors.add(attribute, _("should be an array of %{object_name} objects") % { object_name: options.fetch(:object_name, attribute) })
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix 500 errors and false positive warnings during metrics dashboard validation.
|
||||
merge_request: 34166
|
||||
author:
|
||||
type: fixed
|
|
@ -322,6 +322,8 @@ application server, or a Gitaly node.
|
|||
}
|
||||
```
|
||||
|
||||
1. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2013) in GitLab 13.1 and later, enable [distribution of reads](#distributed-reads).
|
||||
|
||||
1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure
|
||||
Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure):
|
||||
|
||||
|
@ -712,6 +714,43 @@ To get started quickly:
|
|||
Congratulations! You've configured an observable highly available Praefect
|
||||
cluster.
|
||||
|
||||
## Distributed reads
|
||||
|
||||
> Introduced in GitLab 13.1 in [beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga) with feature flag `gitaly_distributed_reads` set to disabled.
|
||||
|
||||
Praefect supports distribution of read operations across Gitaly nodes that are
|
||||
configured for the virtual node.
|
||||
|
||||
To allow for [performance testing](https://gitlab.com/gitlab-org/quality/performance/-/issues/231),
|
||||
distributed reads are currently in
|
||||
[beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga) and disabled by
|
||||
default. To enable distributed reads, the `gitaly_distributed_reads`
|
||||
[feature flag](../feature_flags.md) must be enabled in a Ruby console:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:gitaly_distributed_reads)
|
||||
```
|
||||
|
||||
If enabled, all RPCs marked with `ACCESSOR` option like
|
||||
[GetBlob](https://gitlab.com/gitlab-org/gitaly/-/blob/v12.10.6/proto/blob.proto#L16)
|
||||
are redirected to an up to date and healthy Gitaly node.
|
||||
|
||||
_Up to date_ in this context means that:
|
||||
|
||||
- There is no replication operations scheduled for this node.
|
||||
- The last replication operation is in _completed_ state.
|
||||
|
||||
If there is no such nodes, or any other error occurs during node selection, the primary
|
||||
node will be chosen to serve the request.
|
||||
|
||||
To track distribution of read operations, you can use the `gitaly_praefect_read_distribution`
|
||||
Prometheus counter metric. It has two labels:
|
||||
|
||||
- `virtual_storage`.
|
||||
- `storage`.
|
||||
|
||||
They reflect configuration defined for this instance of Praefect.
|
||||
|
||||
## Automatic failover and leader election
|
||||
|
||||
Praefect regularly checks the health of each backend Gitaly node. This
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
doc/user/admin_area/img/scope_mr_approval_settings_v13_1.png
Normal file
BIN
doc/user/admin_area/img/scope_mr_approval_settings_v13_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
|
@ -34,3 +34,37 @@ Merge request approval rules that can be set at an instance level are:
|
|||
- **Prevent users from modifying merge request approvers list**. Prevents project
|
||||
maintainers from allowing users to modify the approvers list in project settings
|
||||
or in individual merge requests.
|
||||
|
||||
## Scope rules to compliance-labeled projects
|
||||
|
||||
> Introduced in [GitLab Premium](https://gitlab.com/groups/gitlab-org/-/epics/3432) 13.1.
|
||||
|
||||
Merge request approval rules can be further scoped to specific compliance frameworks.
|
||||
|
||||
When the compliance framework label is selected and the project is assigned the compliance
|
||||
label, the instance-level MR approval settings will take effect and
|
||||
[project-level settings](../project/merge_requests/merge_request_approvals.md#adding--editing-a-default-approval-rule)
|
||||
is locked for modification.
|
||||
|
||||
When the compliance framework label is not selected or the project is not assigned the
|
||||
compliance label, the project-level MR approval settings will take effect and the users with
|
||||
Maintainer role and above can modify these.
|
||||
|
||||
| Instance-level | Project-level |
|
||||
| -------------- | ------------- |
|
||||
| ![Scope MR approval settings to compliance frameworks](img/scope_mr_approval_settings_v13_1.png) | ![MR approval settings on compliance projects](img/mr_approval_settings_compliance_project_v13_1.png) |
|
||||
|
||||
### Enabling the feature
|
||||
|
||||
This feature comes with two feature flags which are disabled by default.
|
||||
|
||||
- The configuration in Admin area is controlled via `admin_merge_request_approval_settings`
|
||||
- The application of these rules is controlled via `project_merge_request_approval_settings`
|
||||
|
||||
These feature flags can be managed by feature flag [API endpoint](../../api/features.md#set-or-create-a-feature) or
|
||||
by [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) with the following commands:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:admin_merge_request_approval_settings)
|
||||
Feature.enable(:project_merge_request_approval_settings)
|
||||
```
|
||||
|
|
|
@ -353,11 +353,11 @@ and files with invalid syntax display **Metrics Dashboard YAML definition is inv
|
|||
When **Metrics Dashboard YAML definition is invalid** at least one of the following messages is displayed:
|
||||
|
||||
1. `dashboard: can't be blank` [learn more](#dashboard-top-level-properties)
|
||||
1. `panel_groups: can't be blank` [learn more](#dashboard-top-level-properties)
|
||||
1. `panel_groups: should be an array of panel_groups objects` [learn more](#dashboard-top-level-properties)
|
||||
1. `group: can't be blank` [learn more](#panel-group-panel_groups-properties)
|
||||
1. `panels: can't be blank` [learn more](#panel-group-panel_groups-properties)
|
||||
1. `metrics: can't be blank` [learn more](#panel-panels-properties)
|
||||
1. `panels: should be an array of panels objects` [learn more](#panel-group-panel_groups-properties)
|
||||
1. `title: can't be blank` [learn more](#panel-panels-properties)
|
||||
1. `metrics: should be an array of metrics objects` [learn more](#panel-panels-properties)
|
||||
1. `query: can't be blank` [learn more](#metrics-metrics-properties)
|
||||
1. `query_range: can't be blank` [learn more](#metrics-metrics-properties)
|
||||
1. `unit: can't be blank` [learn more](#metrics-metrics-properties)
|
||||
|
|
|
@ -1,25 +1,45 @@
|
|||
# Slack Notifications Service
|
||||
|
||||
The Slack Notifications Service allows your GitLab project to send events (e.g. issue created) to your existing Slack team as notifications. This requires configurations in both Slack and GitLab.
|
||||
The Slack Notifications Service allows your GitLab project to send events
|
||||
(such as issue creation) to your existing Slack team as notifications. Setting up
|
||||
Slack notifications requires configuration changes for both Slack and GitLab.
|
||||
|
||||
> Note: You can also use Slack slash commands to control GitLab inside Slack. This is the separately configured [Slack slash commands](slack_slash_commands.md).
|
||||
NOTE: **Note:**
|
||||
You can also use Slack slash commands to control GitLab inside Slack. This is the
|
||||
separately configured [Slack slash commands](slack_slash_commands.md).
|
||||
|
||||
## Slack Configuration
|
||||
## Slack configuration
|
||||
|
||||
1. Sign in to your Slack team and [start a new Incoming WebHooks configuration](https://my.slack.com/services/new/incoming-webhook).
|
||||
1. Select the Slack channel where notifications will be sent to by default. Click the **Add Incoming WebHooks integration** button to add the configuration.
|
||||
1. Copy the **Webhook URL**, which we'll use later in the GitLab configuration.
|
||||
1. Select the Slack channel where notifications will be sent to by default.
|
||||
Click the **Add Incoming WebHooks integration** button to add the configuration.
|
||||
1. Copy the **Webhook URL**, which we will use later in the GitLab configuration.
|
||||
|
||||
## GitLab Configuration
|
||||
## GitLab configuration
|
||||
|
||||
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
|
||||
1. Open your project's page, and navigate to your project's
|
||||
[Integrations page](overview.md#accessing-integrations) at
|
||||
**{settings}** **Settings > Integrations**.
|
||||
1. Select the **Slack notifications** integration to configure it.
|
||||
1. Ensure that the **Active** toggle is enabled.
|
||||
1. Check the checkboxes corresponding to the GitLab events you want to send to Slack as a notification.
|
||||
1. For each event, optionally enter the Slack channel names where you want to send the event, separated by a comma. If left empty, the event will be sent to the default channel that you configured in the Slack Configuration step. **Note:** Usernames and private channels are not supported. To send direct messages, use the Member ID found under user's Slack profile.
|
||||
1. Paste the **Webhook URL** that you copied from the Slack Configuration step.
|
||||
1. Optionally customize the Slack bot username that will be sending the notifications.
|
||||
1. Configure the remaining options and click `Save changes`.
|
||||
1. Click **Enable integration**.
|
||||
1. In **Trigger**, select the checkboxes for each type of GitLab event to send to Slack as a
|
||||
notification. See [Triggers available for Slack notifications](#triggers-available-for-slack-notifications)
|
||||
for a full list. By default, messages are sent to the channel you configured during
|
||||
[Slack integration](#slack-configuration).
|
||||
1. (Optional) To send messages to a different channel, multiple channels, or as a direct message:
|
||||
- To send messages to channels, enter the Slack channel names, separated by commas.
|
||||
- To send direct messages, use the Member ID found in the user's Slack profile.
|
||||
|
||||
NOTE: **Note:**
|
||||
Usernames and private channels are not supported.
|
||||
|
||||
1. In **Webhook**, provide the webhook URL that you copied from the
|
||||
[Slack integration](#slack-configuration) step.
|
||||
1. (Optional) In **Username**, provide the username of the Slack bot that sends the notifications.
|
||||
1. Select the **Notify only broken pipelines** check box to only notify on failures.
|
||||
1. In the **Branches to be notified** select box, choose which types of branches
|
||||
to send notifications for.
|
||||
1. Click **Test settings and save changes**.
|
||||
|
||||
Your Slack team will now start receiving GitLab event notifications as configured.
|
||||
|
||||
|
@ -43,14 +63,14 @@ The following triggers are available for Slack notifications:
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
If you're having trouble with the Slack integration not working, then start by
|
||||
If your Slack integration is not working, start troubleshooting by
|
||||
searching through the [Sidekiq logs](../../../administration/logs.md#sidekiqlog)
|
||||
for errors relating to your Slack service.
|
||||
|
||||
### Something went wrong on our end
|
||||
|
||||
This is a generic error shown in the GitLab UI and doesn't mean much by itself.
|
||||
You'll need to look in [the logs](../../../administration/logs.md#productionlog) to find
|
||||
This is a generic error shown in the GitLab UI and does not mean much by itself.
|
||||
Review [the logs](../../../administration/logs.md#productionlog) to find
|
||||
an error message and keep troubleshooting from there.
|
||||
|
||||
### `certificate verify failed`
|
||||
|
@ -83,10 +103,10 @@ result = Net::HTTP.get(URI('https://<SLACK URL>'));0
|
|||
result = Net::HTTP.get(URI('https://<GITLAB URL>'));0
|
||||
```
|
||||
|
||||
If it's an issue with GitLab not trusting HTTPS connections to itself, then you may simply
|
||||
If GitLab is not trusting HTTPS connections to itself, then you may
|
||||
need to [add your certificate to GitLab's trusted certificates](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
|
||||
|
||||
If it's an issue with GitLab not trusting connections to Slack, then the GitLab
|
||||
OpenSSL trust store probably got messed up somehow. Typically this is from overriding
|
||||
the trust store with `gitlab_rails['env'] = {"SSL_CERT_FILE" => "/path/to/file.pem"}`
|
||||
If GitLab is not trusting connections to Slack, then the GitLab
|
||||
OpenSSL trust store is incorrect. Some typical causes: overriding
|
||||
the trust store with `gitlab_rails['env'] = {"SSL_CERT_FILE" => "/path/to/file.pem"}`,
|
||||
or by accidentally modifying the default CA bundle `/opt/gitlab/embedded/ssl/certs/cacert.pem`.
|
||||
|
|
|
@ -27624,6 +27624,9 @@ msgstr ""
|
|||
msgid "severity|Unknown"
|
||||
msgstr ""
|
||||
|
||||
msgid "should be an array of %{object_name} objects"
|
||||
msgstr ""
|
||||
|
||||
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -593,7 +593,7 @@ RSpec.describe 'File blob', :js do
|
|||
aggregate_failures do
|
||||
# shows that dashboard yaml is invalid
|
||||
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
|
||||
expect(page).to have_content("panel_groups: can't be blank")
|
||||
expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
|
||||
|
||||
# shows a learn more link
|
||||
expect(page).to have_link('Learn more')
|
||||
|
|
|
@ -50,19 +50,19 @@ describe PerformanceMonitoring::PrometheusDashboard do
|
|||
context 'dashboard content is missing' do
|
||||
let(:json_content) { nil }
|
||||
|
||||
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'dashboard content is NOT a hash' do
|
||||
let(:json_content) { YAML.safe_load("'test'") }
|
||||
|
||||
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'content is an array' do
|
||||
let(:json_content) { [{ "dashboard" => "Dashboard Title" }] }
|
||||
|
||||
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'dashboard definition is missing panels_groups and dashboard keys' do
|
||||
|
@ -72,7 +72,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
|
|||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'group definition is missing panels and group keys' do
|
||||
|
@ -88,7 +88,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
|
|||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'validation failed', panels: ["can't be blank"], group: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', panels: ["should be an array of panels objects"], group: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'panel definition is missing metrics and title keys' do
|
||||
|
@ -110,7 +110,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
|
|||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'validation failed', metrics: ["can't be blank"], title: ["can't be blank"]
|
||||
it_behaves_like 'validation failed', metrics: ["should be an array of metrics objects"], title: ["can't be blank"]
|
||||
end
|
||||
|
||||
context 'metrics definition is missing unit, query and query_range keys' do
|
||||
|
@ -180,7 +180,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
|
|||
describe '.find_for' do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let(:user) { build_stubbed(:user) }
|
||||
let(:environment) { build_stubbed(:environment) }
|
||||
let(:environment) { build_stubbed(:environment, project: project) }
|
||||
let(:path) { ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH }
|
||||
|
||||
context 'dashboard has been found' do
|
||||
|
|
|
@ -62,12 +62,12 @@ describe 'Getting Metrics Dashboard' do
|
|||
|
||||
context 'invalid dashboard' do
|
||||
let(:path) { '.gitlab/dashboards/metrics.yml' }
|
||||
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndasboard: ''" }) }
|
||||
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
|
||||
|
||||
it 'returns metrics dashboard' do
|
||||
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
|
||||
|
||||
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
|
||||
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -78,7 +78,7 @@ describe 'Getting Metrics Dashboard' do
|
|||
it 'returns metrics dashboard' do
|
||||
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
|
||||
|
||||
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
|
||||
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
69
spec/validators/array_members_validator_spec.rb
Normal file
69
spec/validators/array_members_validator_spec.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ArrayMembersValidator do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
child_class = Class.new
|
||||
|
||||
subject(:test_class) do
|
||||
Class.new do
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Validations
|
||||
attr_accessor :children
|
||||
validates :children, array_members: { member_class: child_class }
|
||||
end
|
||||
end
|
||||
|
||||
where(:children, :is_valid) do
|
||||
[child_class.new] | true
|
||||
[Class.new.new] | false
|
||||
[child_class.new, Class.new.new] | false
|
||||
[] | false
|
||||
child_class.new | false
|
||||
[Class.new(child_class).new] | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'only accepts valid children nodes' do
|
||||
expect(test_class.new(children: children).valid?).to eq(is_valid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation message' do
|
||||
subject(:test_class) do
|
||||
Class.new do
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Validations
|
||||
attr_accessor :children
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default object name' do
|
||||
it 'uses attribute name', :aggregate_failures do
|
||||
test_class.class_eval do
|
||||
validates :children, array_members: { member_class: child_class }
|
||||
end
|
||||
|
||||
object = test_class.new(children: [])
|
||||
|
||||
expect(object.valid?).to be_falsey
|
||||
expect(object.errors.messages).to eql(children: ['should be an array of children objects'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom object name' do
|
||||
it 'uses that name', :aggregate_failures do
|
||||
test_class.class_eval do
|
||||
validates :children, array_members: { member_class: child_class, object_name: 'test' }
|
||||
end
|
||||
|
||||
object = test_class.new(children: [])
|
||||
|
||||
expect(object.valid?).to be_falsey
|
||||
expect(object.errors.messages).to eql(children: ['should be an array of test objects'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue