Merge branch 'master' into 19703-direct-link-pipelines
* master: (30 commits) Add GitLab host to 2FA QR and manual info Fix broken test Fix rubocop Fix specs in Ruby 2.1 Clearer comment as to why the procedure is needed Ensure issuable state changes only fire webhooks once Improve performance on RemoveDuplicatesFromRoutes migration Fix the AddNameIndexToNamespace migration to be reversible Use optimized query to fill the routes table when running PostgreSQL Don't use the Route model in migrations Added KaTeX license and procedure to build it for Gitlab [ci skip] UX Guide: add guidance on cursor usage Changes after review Add missing group policy spec Limit description container for mrs while viewing side by side diff Refactor Namespace#parents method Change SlackService to SlackNotificationsService Made Ci::Builds to have same ref as Ci::Pipeline in dev fixtures Mattermost Notifications Service Replace static fixture for abuse_reports_spec (!7644) ...
|
@ -1 +1 @@
|
|||
4.0.3
|
||||
4.1.0
|
||||
|
|
2
Gemfile
|
@ -67,7 +67,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
|
|||
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
||||
|
||||
# API
|
||||
gem 'grape', '~> 0.15.0'
|
||||
gem 'grape', '~> 0.18.0'
|
||||
gem 'grape-entity', '~> 0.6.0'
|
||||
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||
|
||||
|
|
15
Gemfile.lock
|
@ -284,15 +284,15 @@ GEM
|
|||
json
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
grape (0.15.0)
|
||||
grape (0.18.0)
|
||||
activesupport
|
||||
builder
|
||||
hashie (>= 2.1.0)
|
||||
multi_json (>= 1.3.2)
|
||||
multi_xml (>= 0.5.2)
|
||||
mustermann-grape (~> 0.4.0)
|
||||
rack (>= 1.3.0)
|
||||
rack-accept
|
||||
rack-mount
|
||||
virtus (>= 1.0.0)
|
||||
grape-entity (0.6.0)
|
||||
activesupport
|
||||
|
@ -400,6 +400,10 @@ GEM
|
|||
multi_json (1.12.1)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
mustermann (0.4.0)
|
||||
tool (~> 0.2)
|
||||
mustermann-grape (0.4.0)
|
||||
mustermann (= 0.4.0)
|
||||
mysql2 (0.3.20)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
|
@ -505,14 +509,12 @@ GEM
|
|||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
rack (1.6.4)
|
||||
rack (1.6.5)
|
||||
rack-accept (0.4.5)
|
||||
rack (>= 0.4)
|
||||
rack-attack (4.4.1)
|
||||
rack
|
||||
rack-cors (0.4.0)
|
||||
rack-mount (0.8.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-oauth2 (1.2.3)
|
||||
activesupport (>= 2.3)
|
||||
attr_required (>= 0.0.5)
|
||||
|
@ -743,6 +745,7 @@ GEM
|
|||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tool (0.2.3)
|
||||
truncato (0.7.8)
|
||||
htmlentities (~> 4.3.1)
|
||||
nokogiri (~> 1.6.1)
|
||||
|
@ -861,7 +864,7 @@ DEPENDENCIES
|
|||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
gon (~> 6.1.0)
|
||||
grape (~> 0.15.0)
|
||||
grape (~> 0.18.0)
|
||||
grape-entity (~> 0.6.0)
|
||||
haml_lint (~> 0.18.2)
|
||||
hamlit (~> 2.6.1)
|
||||
|
|
|
@ -26,6 +26,10 @@ body {
|
|||
|
||||
.container-limited {
|
||||
max-width: $fixed-layout-width;
|
||||
|
||||
&.limit-container-width {
|
||||
max-width: $limited-layout-width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -154,6 +154,8 @@ $row-hover-border: #b2d7ff;
|
|||
$progress-color: #c0392b;
|
||||
$header-height: 50px;
|
||||
$fixed-layout-width: 1280px;
|
||||
$limited-layout-width: 990px;
|
||||
$gl-avatar-size: 40px;
|
||||
$error-exclamation-point: #e62958;
|
||||
$border-radius-default: 2px;
|
||||
$settings-icon-size: 18px;
|
||||
|
|
|
@ -1,3 +1,50 @@
|
|||
// Limit MR description for side-by-side diff view
|
||||
.limit-container-width {
|
||||
.detail-page-header {
|
||||
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.issuable-details {
|
||||
.detail-page-description,
|
||||
.mr-source-target,
|
||||
.mr-state-widget,
|
||||
.merge-manually {
|
||||
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.merge-request-tabs-holder {
|
||||
&.affix {
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
.nav-links {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diffs {
|
||||
.mr-version-controls,
|
||||
.files-changed {
|
||||
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-details {
|
||||
section {
|
||||
.issuable-discussion {
|
||||
|
@ -9,7 +56,6 @@
|
|||
.description img:not(.emoji) {
|
||||
border: 1px solid $white-normal;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -383,10 +383,6 @@ ul.notes {
|
|||
.note-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-actions {
|
||||
|
|
|
@ -616,14 +616,10 @@
|
|||
li {
|
||||
padding-top: 2px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
padding-bottom: 6px;
|
||||
padding-left: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
end
|
||||
|
||||
@qr_code = build_qr_code
|
||||
@account_string = account_string
|
||||
setup_u2f_registration
|
||||
end
|
||||
|
||||
|
@ -78,11 +79,14 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
private
|
||||
|
||||
def build_qr_code
|
||||
issuer = "#{issuer_host} | #{current_user.email}"
|
||||
uri = current_user.otp_provisioning_uri(current_user.email, issuer: issuer)
|
||||
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host)
|
||||
RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
|
||||
end
|
||||
|
||||
def account_string
|
||||
"#{issuer_host}:#{current_user.email}"
|
||||
end
|
||||
|
||||
def issuer_host
|
||||
Gitlab.config.gitlab.host
|
||||
end
|
||||
|
|
|
@ -12,11 +12,18 @@ module GroupsHelper
|
|||
end
|
||||
|
||||
def group_title(group, name = nil, url = nil)
|
||||
full_title = link_to(simple_sanitize(group.name), group_path(group))
|
||||
full_title = ''
|
||||
|
||||
group.parents.each do |parent|
|
||||
full_title += link_to(simple_sanitize(parent.name), group_path(parent))
|
||||
full_title += ' / '.html_safe
|
||||
end
|
||||
|
||||
full_title += link_to(simple_sanitize(group.name), group_path(group))
|
||||
full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name
|
||||
|
||||
content_tag :span do
|
||||
full_title
|
||||
full_title.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ module ProjectsHelper
|
|||
def project_title(project)
|
||||
namespace_link =
|
||||
if project.group
|
||||
link_to(simple_sanitize(project.group.name), group_path(project.group))
|
||||
group_title(project.group)
|
||||
else
|
||||
owner = project.namespace.owner
|
||||
link_to(simple_sanitize(owner.name), user_path(owner))
|
||||
|
@ -390,7 +390,7 @@ module ProjectsHelper
|
|||
"success"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def readme_cache_key
|
||||
sha = @project.commit.try(:sha) || 'nil'
|
||||
[@project.path_with_namespace, sha, "readme"].join('-')
|
||||
|
|
|
@ -83,7 +83,7 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def human_name
|
||||
name
|
||||
full_name
|
||||
end
|
||||
|
||||
def visibility_level_field
|
||||
|
|
|
@ -161,6 +161,19 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def full_name
|
||||
@full_name ||=
|
||||
if parent
|
||||
parent.full_name + ' / ' + name
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
def parents
|
||||
@parents ||= parent ? parent.parents + [parent] : []
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_storage_paths
|
||||
|
|
|
@ -95,7 +95,8 @@ class Project < ActiveRecord::Base
|
|||
has_one :asana_service, dependent: :destroy
|
||||
has_one :gemnasium_service, dependent: :destroy
|
||||
has_one :mattermost_slash_commands_service, dependent: :destroy
|
||||
has_one :slack_service, dependent: :destroy
|
||||
has_one :mattermost_notification_service, dependent: :destroy
|
||||
has_one :slack_notification_service, dependent: :destroy
|
||||
has_one :buildkite_service, dependent: :destroy
|
||||
has_one :bamboo_service, dependent: :destroy
|
||||
has_one :teamcity_service, dependent: :destroy
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'slack-notifier'
|
||||
|
||||
class SlackService
|
||||
module ChatMessage
|
||||
class BaseMessage
|
||||
def initialize(params)
|
||||
raise NotImplementedError
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class BuildMessage < BaseMessage
|
||||
attr_reader :sha
|
||||
attr_reader :ref_type
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class IssueMessage < BaseMessage
|
||||
attr_reader :user_name
|
||||
attr_reader :title
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class MergeMessage < BaseMessage
|
||||
attr_reader :user_name
|
||||
attr_reader :project_name
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class NoteMessage < BaseMessage
|
||||
attr_reader :message
|
||||
attr_reader :user_name
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class PipelineMessage < BaseMessage
|
||||
attr_reader :ref_type, :ref, :status, :project_name, :project_url,
|
||||
:user_name, :duration, :pipeline_id
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class PushMessage < BaseMessage
|
||||
attr_reader :after
|
||||
attr_reader :before
|
|
@ -1,4 +1,4 @@
|
|||
class SlackService
|
||||
module ChatMessage
|
||||
class WikiPageMessage < BaseMessage
|
||||
attr_reader :user_name
|
||||
attr_reader :title
|
|
@ -1,6 +1,13 @@
|
|||
class SlackService < Service
|
||||
# Base class for Chat notifications services
|
||||
# This class is not meant to be used directly, but only to inherit from.
|
||||
class ChatNotificationService < Service
|
||||
include ChatMessage
|
||||
|
||||
default_value_for :category, 'chat'
|
||||
|
||||
prop_accessor :webhook, :username, :channel
|
||||
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
|
||||
|
||||
validates :webhook, presence: true, url: true, if: :activated?
|
||||
|
||||
def initialize_properties
|
||||
|
@ -14,35 +21,8 @@ class SlackService < Service
|
|||
end
|
||||
end
|
||||
|
||||
def title
|
||||
'Slack'
|
||||
end
|
||||
|
||||
def description
|
||||
'A team communication tool for the 21st century'
|
||||
end
|
||||
|
||||
def to_param
|
||||
'slack'
|
||||
end
|
||||
|
||||
def help
|
||||
'This service sends notifications to your Slack channel.<br/>
|
||||
To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel,
|
||||
and enter the Webhook URL below.'
|
||||
end
|
||||
|
||||
def fields
|
||||
default_fields =
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
|
||||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'text', name: 'channel', placeholder: "#general" },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
]
|
||||
|
||||
default_fields + build_event_channels
|
||||
def can_test?
|
||||
valid?
|
||||
end
|
||||
|
||||
def supported_events
|
||||
|
@ -67,21 +47,16 @@ class SlackService < Service
|
|||
|
||||
message = get_message(object_kind, data)
|
||||
|
||||
if message
|
||||
opt = {}
|
||||
return false unless message
|
||||
|
||||
event_channel = get_channel_field(object_kind) || channel
|
||||
opt = {}
|
||||
|
||||
opt[:channel] = event_channel if event_channel
|
||||
opt[:username] = username if username
|
||||
opt[:channel] = get_channel_field(object_kind).presence || channel || default_channel
|
||||
opt[:username] = username if username
|
||||
notifier = Slack::Notifier.new(webhook, opt)
|
||||
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
|
||||
|
||||
notifier = Slack::Notifier.new(webhook, opt)
|
||||
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
|
||||
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def event_channel_names
|
||||
|
@ -96,6 +71,10 @@ class SlackService < Service
|
|||
fields.reject { |field| field[:name].end_with?('channel') }
|
||||
end
|
||||
|
||||
def default_channel
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_message(object_kind, data)
|
||||
|
@ -124,7 +103,7 @@ class SlackService < Service
|
|||
|
||||
def build_event_channels
|
||||
supported_events.reduce([]) do |channels, event|
|
||||
channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
|
||||
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -166,11 +145,3 @@ class SlackService < Service
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "slack_service/issue_message"
|
||||
require "slack_service/push_message"
|
||||
require "slack_service/merge_message"
|
||||
require "slack_service/note_message"
|
||||
require "slack_service/build_message"
|
||||
require "slack_service/pipeline_message"
|
||||
require "slack_service/wiki_page_message"
|
|
@ -1,5 +1,5 @@
|
|||
# Base class for Chat services
|
||||
# This class is not meant to be used directly, but only to inherrit from.
|
||||
# This class is not meant to be used directly, but only to inherit from.
|
||||
class ChatService < Service
|
||||
default_value_for :category, 'chat'
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
class MattermostNotificationService < ChatNotificationService
|
||||
def title
|
||||
'Mattermost notifications'
|
||||
end
|
||||
|
||||
def description
|
||||
'Receive event notifications in Mattermost'
|
||||
end
|
||||
|
||||
def to_param
|
||||
'mattermost_notification'
|
||||
end
|
||||
|
||||
def help
|
||||
'This service sends notifications about projects events to Mattermost channels.<br />
|
||||
To set up this service:
|
||||
<ol>
|
||||
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li>
|
||||
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li>
|
||||
<li>Paste the webhook <strong>URL</strong> into the field bellow. </li>
|
||||
<li>Select events below to enable notifications. The channel and username are optional. </li>
|
||||
</ol>'
|
||||
end
|
||||
|
||||
def fields
|
||||
default_fields + build_event_channels
|
||||
end
|
||||
|
||||
def default_fields
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
|
||||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
]
|
||||
end
|
||||
|
||||
def default_channel
|
||||
"#town-square"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
class SlackNotificationService < ChatNotificationService
|
||||
def title
|
||||
'Slack notifications'
|
||||
end
|
||||
|
||||
def description
|
||||
'Receive event notifications in Slack'
|
||||
end
|
||||
|
||||
def to_param
|
||||
'slack_notification'
|
||||
end
|
||||
|
||||
def help
|
||||
'This service sends notifications about projects events to Slack channels.<br />
|
||||
To setup this service:
|
||||
<ol>
|
||||
<li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li>
|
||||
<li>Paste the <strong>Webhook URL</strong> into the field below. </li>
|
||||
<li>Select events below to enable notifications. The channel and username are optional. </li>
|
||||
</ol>'
|
||||
end
|
||||
|
||||
def fields
|
||||
default_fields + build_event_channels
|
||||
end
|
||||
|
||||
def default_fields
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
|
||||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
]
|
||||
end
|
||||
|
||||
def default_channel
|
||||
"#general"
|
||||
end
|
||||
end
|
|
@ -220,7 +220,8 @@ class Service < ActiveRecord::Base
|
|||
pivotaltracker
|
||||
pushover
|
||||
redmine
|
||||
slack
|
||||
mattermost_notification
|
||||
slack_notification
|
||||
teamcity
|
||||
]
|
||||
end
|
||||
|
|
|
@ -184,7 +184,8 @@ class IssuableBaseService < BaseService
|
|||
old_labels = issuable.labels.to_a
|
||||
old_mentioned_users = issuable.mentioned_users.to_a
|
||||
|
||||
params[:label_ids] = process_label_ids(params, existing_label_ids: issuable.label_ids)
|
||||
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
|
||||
params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids)
|
||||
|
||||
if params.present? && update_issuable(issuable, params)
|
||||
# We do not touch as it will affect a update on updated_at field
|
||||
|
@ -201,6 +202,10 @@ class IssuableBaseService < BaseService
|
|||
issuable
|
||||
end
|
||||
|
||||
def labels_changing?(old_label_ids, new_label_ids)
|
||||
old_label_ids.sort != new_label_ids.sort
|
||||
end
|
||||
|
||||
def change_state(issuable)
|
||||
case params.delete(:state_event)
|
||||
when 'reopen'
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
|
||||
.title
|
||||
= link_to [:admin, group], class: 'group-name' do
|
||||
= group.name
|
||||
= group.full_name
|
||||
|
||||
- if group.description.present?
|
||||
.description
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- page_title @group.name, "Groups"
|
||||
%h3.page-title
|
||||
Group: #{@group.name}
|
||||
Group: #{@group.full_name}
|
||||
|
||||
= link_to admin_group_edit_path(@group), class: "btn pull-right" do
|
||||
%i.fa.fa-pencil-square-o
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
- subject = local_assigns.fetch(:subject)
|
||||
- status = subject.detailed_status(current_user)
|
||||
- klass = "ci-status-icon ci-status-icon-#{status}"
|
||||
- tooltip_title = "#{subject.name} - #{status.label}"
|
||||
|
||||
- if status.has_details?
|
||||
= link_to status.details_path, data: { toggle: 'tooltip', title: tooltip_title } do
|
||||
= link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do
|
||||
%span{ class: klass }= custom_icon(status.icon)
|
||||
.ci-status-text= subject.name
|
||||
- else
|
||||
|
@ -15,6 +14,6 @@
|
|||
|
||||
- if status.has_action?
|
||||
= link_to status.action_path, method: status.action_method,
|
||||
title: tooltip_title, class: 'ci-action-icon-container' do
|
||||
title: status.action_title, class: 'ci-action-icon-container' do
|
||||
%i.ci-action-icon-wrapper
|
||||
= icon(status.action_icon, class: status.action_class)
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
To add the entry manually, provide the following details to the application on your phone.
|
||||
%p.prepend-top-0.append-bottom-0
|
||||
Account:
|
||||
= current_user.email
|
||||
= @account_string
|
||||
%p.prepend-top-0.append-bottom-0
|
||||
Key:
|
||||
= current_user.otp_secret.scan(/.{4}/).join(' ')
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
- is_playable = subject.playable? && can?(current_user, :update_build, @project)
|
||||
- if is_playable
|
||||
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.js-pipeline-graph', placement: 'bottom' } do
|
||||
= ci_icon_for_status('play')
|
||||
.ci-status-text= subject.name
|
||||
- elsif can?(current_user, :read_build, @project)
|
||||
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } do
|
||||
%span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
|
||||
= ci_icon_for_status(subject.status)
|
||||
.ci-status-text= subject.name
|
||||
- else
|
||||
%span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
|
||||
= ci_icon_for_status(subject.status)
|
|
@ -1,10 +0,0 @@
|
|||
%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } }
|
||||
- if subject.target_url
|
||||
= link_to subject.target_url do
|
||||
%span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
|
||||
= ci_icon_for_status(subject.status)
|
||||
%span.ci-status-text= subject.name
|
||||
- else
|
||||
%span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
|
||||
= ci_icon_for_status(subject.status)
|
||||
%span.ci-status-text= subject.name
|
|
@ -1,3 +1,4 @@
|
|||
- @content_class = "limit-container-width"
|
||||
- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
|
||||
- page_description @issue.description
|
||||
- page_card_attributes @issue.card_attributes
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
- @content_class = "limit-container-width"
|
||||
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
|
||||
- page_description @merge_request.description
|
||||
- page_card_attributes @merge_request.card_attributes
|
||||
|
@ -41,7 +42,7 @@
|
|||
= render "projects/merge_requests/widget/show.html.haml"
|
||||
|
||||
- if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
|
||||
.light.prepend-top-default.append-bottom-default
|
||||
.merge-manually.light.prepend-top-default.append-bottom-default
|
||||
You can also accept this merge request manually using the
|
||||
= succeed '.' do
|
||||
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
|
||||
.title
|
||||
= link_to group, class: 'group-name' do
|
||||
= group.name
|
||||
= group.full_name
|
||||
|
||||
- if group_member
|
||||
as
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Ensure issuable state changes only fire webhooks once
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Replace static fixture for abuse_reports_spec
|
||||
merge_request: 7644
|
||||
author: winniehell
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add GitLab host to 2FA QR code and manual info
|
||||
merge_request: 6941
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Ci::Builds have same ref as Ci::Pipeline in dev fixtures
|
||||
merge_request:
|
||||
author: twonegatives
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 'Gem update: Update grape to 0.18.0'
|
||||
merge_request:
|
||||
author: Robert Schilling
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Create mattermost service
|
||||
merge_request:
|
||||
author:
|
|
@ -115,7 +115,7 @@ class Gitlab::Seeder::Pipelines
|
|||
|
||||
def job_attributes(pipeline, opts)
|
||||
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
|
||||
ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
|
||||
ref: pipeline.ref, tag: false, user: build_user, project: @project, pipeline: pipeline,
|
||||
created_at: Time.now, updated_at: Time.now
|
||||
}.merge(opts)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# rubocop:disable all
|
||||
class MoveSlackServiceToWebhook < ActiveRecord::Migration
|
||||
|
||||
DOWNTIME = true
|
||||
DOWNTIME_REASON = 'Move old fields "token" and "subdomain" to one single field "webhook"'
|
||||
|
||||
def change
|
||||
SlackService.all.each do |slack_service|
|
||||
SlackNotificationService.all.each do |slack_service|
|
||||
if ["token", "subdomain"].all? { |property| slack_service.properties.key? property }
|
||||
token = slack_service.properties['token']
|
||||
subdomain = slack_service.properties['subdomain']
|
||||
|
|
|
@ -16,6 +16,6 @@ class FillRoutesTable < ActiveRecord::Migration
|
|||
end
|
||||
|
||||
def down
|
||||
Route.delete_all(source_type: 'Namespace')
|
||||
execute("DELETE FROM routes WHERE source_type = 'Namespace'")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,15 +8,23 @@ class FillProjectsRoutesTable < ActiveRecord::Migration
|
|||
DOWNTIME_REASON = 'No new projects should be created during data copy'
|
||||
|
||||
def up
|
||||
execute <<-EOF
|
||||
INSERT INTO routes
|
||||
(source_id, source_type, path)
|
||||
(SELECT projects.id, 'Project', concat(namespaces.path, '/', projects.path) FROM projects
|
||||
INNER JOIN namespaces ON projects.namespace_id = namespaces.id)
|
||||
EOF
|
||||
if Gitlab::Database.postgresql?
|
||||
execute <<-EOF
|
||||
INSERT INTO routes (source_id, source_type, path)
|
||||
(SELECT DISTINCT ON (namespaces.path, projects.path) projects.id, 'Project', concat(namespaces.path, '/', projects.path)
|
||||
FROM projects INNER JOIN namespaces ON projects.namespace_id = namespaces.id
|
||||
ORDER BY namespaces.path, projects.path, projects.id DESC)
|
||||
EOF
|
||||
else
|
||||
execute <<-EOF
|
||||
INSERT INTO routes (source_id, source_type, path)
|
||||
(SELECT projects.id, 'Project', concat(namespaces.path, '/', projects.path)
|
||||
FROM projects INNER JOIN namespaces ON projects.namespace_id = namespaces.id)
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
Route.delete_all(source_type: 'Project')
|
||||
execute("DELETE FROM routes WHERE source_type = 'Project'")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,20 +7,21 @@ class RemoveDuplicatesFromRoutes < ActiveRecord::Migration
|
|||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
select_all("SELECT path FROM #{quote_table_name(:routes)} GROUP BY path HAVING COUNT(*) > 1").each do |row|
|
||||
path = connection.quote(row['path'])
|
||||
execute(%Q{
|
||||
DELETE FROM #{quote_table_name(:routes)}
|
||||
WHERE path = #{path}
|
||||
AND id != (
|
||||
SELECT id FROM (
|
||||
SELECT max(id) AS id
|
||||
FROM #{quote_table_name(:routes)}
|
||||
WHERE path = #{path}
|
||||
) max_ids
|
||||
)
|
||||
})
|
||||
end
|
||||
# We can skip this migration when running a PostgreSQL database because
|
||||
# we use an optimized query in the "FillProjectsRoutesTable" migration
|
||||
# to fill these values that avoid duplicate entries in the routes table.
|
||||
return unless Gitlab::Database.mysql?
|
||||
|
||||
execute <<-EOF
|
||||
DELETE duplicated_rows.*
|
||||
FROM routes AS duplicated_rows
|
||||
INNER JOIN (
|
||||
SELECT path, MAX(id) as max_id
|
||||
FROM routes
|
||||
GROUP BY path
|
||||
HAVING COUNT(*) > 1
|
||||
) AS good_rows ON good_rows.path = duplicated_rows.path AND good_rows.max_id <> duplicated_rows.id;
|
||||
EOF
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
@ -13,7 +13,7 @@ class AddNameIndexToNamespace < ActiveRecord::Migration
|
|||
end
|
||||
|
||||
def down
|
||||
if index_exists?(:namespaces, :name)
|
||||
if index_exists?(:namespaces, [:name, :parent_id])
|
||||
remove_index :namespaces, [:name, :parent_id]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
class ChangeSlackServiceToSlackNotificationService < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = true
|
||||
DOWNTIME_REASON = 'Rename SlackService to SlackNotificationService'
|
||||
|
||||
def up
|
||||
execute("UPDATE services SET type = 'SlackNotificationService' WHERE type = 'SlackService'")
|
||||
end
|
||||
|
||||
def down
|
||||
execute("UPDATE services SET type = 'SlackService' WHERE type = 'SlackNotificationService'")
|
||||
end
|
||||
end
|
13
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161212142807) do
|
||||
ActiveRecord::Schema.define(version: 20161213172958) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -98,14 +98,14 @@ ActiveRecord::Schema.define(version: 20161212142807) do
|
|||
t.text "help_page_text_html"
|
||||
t.text "shared_runners_text_html"
|
||||
t.text "after_sign_up_text_html"
|
||||
t.boolean "sidekiq_throttling_enabled", default: false
|
||||
t.string "sidekiq_throttling_queues"
|
||||
t.decimal "sidekiq_throttling_factor"
|
||||
t.boolean "housekeeping_enabled", default: true, null: false
|
||||
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
|
||||
t.integer "housekeeping_incremental_repack_period", default: 10, null: false
|
||||
t.integer "housekeeping_full_repack_period", default: 50, null: false
|
||||
t.integer "housekeeping_gc_period", default: 200, null: false
|
||||
t.boolean "sidekiq_throttling_enabled", default: false
|
||||
t.string "sidekiq_throttling_queues"
|
||||
t.decimal "sidekiq_throttling_factor"
|
||||
t.boolean "html_emails_enabled", default: true
|
||||
end
|
||||
|
||||
|
@ -527,6 +527,7 @@ ActiveRecord::Schema.define(version: 20161212142807) do
|
|||
t.string "type"
|
||||
t.string "fingerprint"
|
||||
t.boolean "public", default: false, null: false
|
||||
t.boolean "can_push", default: false, null: false
|
||||
end
|
||||
|
||||
add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
|
||||
|
@ -739,8 +740,8 @@ ActiveRecord::Schema.define(version: 20161212142807) do
|
|||
t.integer "visibility_level", default: 20, null: false
|
||||
t.boolean "request_access_enabled", default: false, null: false
|
||||
t.datetime "deleted_at"
|
||||
t.boolean "lfs_enabled"
|
||||
t.text "description_html"
|
||||
t.boolean "lfs_enabled"
|
||||
t.integer "parent_id"
|
||||
end
|
||||
|
||||
|
@ -1221,8 +1222,8 @@ ActiveRecord::Schema.define(version: 20161212142807) do
|
|||
t.datetime "otp_grace_period_started_at"
|
||||
t.boolean "ldap_email", default: false, null: false
|
||||
t.boolean "external", default: false
|
||||
t.string "organization"
|
||||
t.string "incoming_email_token"
|
||||
t.string "organization"
|
||||
t.boolean "authorized_projects_populated"
|
||||
end
|
||||
|
||||
|
|
|
@ -44,22 +44,30 @@ as appropriate.
|
|||
|
||||
## Chained hooks support
|
||||
|
||||
> [Introduced][93] in GitLab Shell 4.1.0.
|
||||
> [Introduced][93] in GitLab Shell 4.1.0 and GitLab 8.15.
|
||||
|
||||
The hooks could be also placed in `hooks/<hook_name>.d` (global) or `custom_hooks/<hook_name>.d` (per project)
|
||||
directories supporting chained execution of the hooks.
|
||||
Hooks can be also placed in `hooks/<hook_name>.d` (global) or
|
||||
`custom_hooks/<hook_name>.d` (per project) directories supporting chained
|
||||
execution of the hooks.
|
||||
|
||||
To look in a different directory for the global custom hooks (those in
|
||||
`hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For
|
||||
Omnibus installations, this can be set in `gitlab.rb`; and in source
|
||||
installations, this can be set in `gitlab-shell/config.yml`.
|
||||
|
||||
The hooks are searched and executed in this order:
|
||||
|
||||
1. `<project>.git/hooks/` - symlink to `gitlab-shell/hooks` global dir
|
||||
1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is `gitlab-shell/hooks/<hook_name>`
|
||||
1. `<project>.git/custom_hooks/<hook_name>` - per project hook (this is already existing behavior)
|
||||
1. `<project>.git/custom_hooks/<hook_name>.d/*` - per project hooks
|
||||
1. `<project>.git/hooks/<hook_name>.d/*` - global hooks: all executable files (minus editor backup files)
|
||||
1. `<project>.git/hooks/<hook_name>.d/*` OR `<custom_hooks_dir>/<hook_name.d>/*` - global hooks: all executable files (minus editor backup files)
|
||||
|
||||
Files in `.d` directories need to be executable and not match the backup file pattern (`*~`).
|
||||
Files in `.d` directories need to be executable and not match the backup file
|
||||
pattern (`*~`).
|
||||
|
||||
The hooks of the same type are executed in order and execution stops on the first
|
||||
script exiting with non-zero value.
|
||||
The hooks of the same type are executed in order and execution stops on the
|
||||
first script exiting with a non-zero value.
|
||||
|
||||
## Custom error messages
|
||||
|
||||
|
|
|
@ -703,9 +703,9 @@ Get Redmine service settings for a project.
|
|||
GET /projects/:id/services/redmine
|
||||
```
|
||||
|
||||
## Slack
|
||||
## Slack notifications
|
||||
|
||||
A team communication tool for the 21st century
|
||||
Receive event notifications in Slack
|
||||
|
||||
### Create/Edit Slack service
|
||||
|
||||
|
@ -737,6 +737,40 @@ Get Slack service settings for a project.
|
|||
GET /projects/:id/services/slack
|
||||
```
|
||||
|
||||
## Mattermost notifications
|
||||
|
||||
Receive event notifications in Mattermost
|
||||
|
||||
### Create/Edit Mattermost notifications service
|
||||
|
||||
Set Mattermost service for a project.
|
||||
|
||||
```
|
||||
PUT /projects/:id/services/mattermost
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `webhook` (**required**) - https://mattermost.example/hooks/1298aff...
|
||||
- `username` (optional) - username
|
||||
- `channel` (optional) - #channel
|
||||
|
||||
### Delete Mattermost notifications service
|
||||
|
||||
Delete Mattermost Notifications service for a project.
|
||||
|
||||
```
|
||||
DELETE /projects/:id/services/mattermost
|
||||
```
|
||||
|
||||
### Get Mattermost notifications service settings
|
||||
|
||||
Get Mattermost notifications service settings for a project.
|
||||
|
||||
```
|
||||
GET /projects/:id/services/mattermost
|
||||
```
|
||||
|
||||
## JetBrains TeamCity CI
|
||||
|
||||
A continuous integration and build server
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* [Typography](#typography)
|
||||
* [Icons](#icons)
|
||||
* [Color](#color)
|
||||
* [Cursors](#cursors)
|
||||
|
||||
---
|
||||
|
||||
|
@ -59,3 +60,18 @@ GitLab uses Font Awesome icons throughout our interface.
|
|||
|
||||
> TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage.
|
||||
|
||||
---
|
||||
|
||||
## Cursors
|
||||
The mouse cursor is key in helping users understand how to interact with elements on the screen.
|
||||
|
||||
| | |
|
||||
| :------: | :------- |
|
||||
| ![Default cursor](img/cursors-default.png) | Default cursor |
|
||||
| ![Pointer cursor](img/cursors-pointer.png) | Pointer cursor: used to indicate that you can click on an element to invoke a command or navigate, such as links and buttons |
|
||||
| ![Move cursor](img/cursors-move.png) | Move cursor: used to indicate that you can move an element around on the screen |
|
||||
| ![Pan opened cursor](img/cursors-panopened.png) | Pan cursor (opened): indicates that you can grab and move the entire canvas, affecting what is seen in the view port. |
|
||||
| ![Pan closed cursor](img/cursors-panclosed.png) | Pan cursor (closed): indicates that you are actively panning the canvas. |
|
||||
| ![I-beam cursor](img/cursors-ibeam.png) | I-beam cursor: indicates that this is either text that you can select and copy, or a text field that you can click into to enter text. |
|
||||
|
||||
|
||||
|
|
After Width: | Height: | Size: 567 B |
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 276 B |
After Width: | Height: | Size: 483 B |
After Width: | Height: | Size: 622 B |
After Width: | Height: | Size: 574 B |
After Width: | Height: | Size: 72 KiB |
|
@ -0,0 +1,45 @@
|
|||
# Mattermost Notifications Service
|
||||
|
||||
## On Mattermost
|
||||
|
||||
To enable Mattermost integration you must create an incoming webhook integration:
|
||||
|
||||
1. Sign in to your Mattermost instance
|
||||
1. Visit incoming webhooks, that will be something like: https://mattermost.example/your_team_name/integrations/incoming_webhooks/add
|
||||
1. Choose a display name, description and channel, those can be overridden on GitLab
|
||||
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
|
||||
|
||||
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
|
||||
it on https://mattermost.example/admin_console/integrations/custom.
|
||||
|
||||
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
|
||||
|
||||
## On GitLab
|
||||
|
||||
After you set up Mattermost, it's time to set up GitLab.
|
||||
|
||||
Go to your project's **Settings > Services > Mattermost Notifications** and you will see a
|
||||
checkbox with the following events that can be triggered:
|
||||
|
||||
- Push
|
||||
- Issue
|
||||
- Merge request
|
||||
- Note
|
||||
- Tag push
|
||||
- Build
|
||||
- Wiki page
|
||||
|
||||
Bellow each of these event checkboxes, you will have an input field to insert
|
||||
which Mattermost channel you want to send that event message, with `#town-square`
|
||||
being the default. The hash sign is optional.
|
||||
|
||||
At the end, fill in your Mattermost details:
|
||||
|
||||
| Field | Description |
|
||||
| ----- | ----------- |
|
||||
| **Webhook** | The incoming webhooks which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo... |
|
||||
| **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. |
|
||||
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
|
||||
|
||||
|
||||
![Mattermost configuration](img/mattermost_configuration.png)
|
|
@ -44,10 +44,11 @@ further configuration instructions and details. Contributions are welcome.
|
|||
| JetBrains TeamCity CI | A continuous integration and build server |
|
||||
| [Kubernetes](kubernetes.md) | A containerized deployment service |
|
||||
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
|
||||
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
|
||||
| [Slack Notifications](slack.md) | Receive event notifications in Slack |
|
||||
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
|
||||
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
|
||||
| [Redmine](redmine.md) | Redmine issue tracker |
|
||||
| [Slack](slack.md) | A team communication tool for the 21st century |
|
||||
|
||||
## Services Templates
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Slack Service
|
||||
# Slack Notifications Service
|
||||
|
||||
## On Slack
|
||||
|
||||
|
@ -15,7 +15,7 @@ Slack:
|
|||
|
||||
After you set up Slack, it's time to set up GitLab.
|
||||
|
||||
Go to your project's **Settings > Services > Slack** and you will see a
|
||||
Go to your project's **Settings > Services > Slack Notifications** and you will see a
|
||||
checkbox with the following events that can be triggered:
|
||||
|
||||
- Push
|
||||
|
|
|
@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-15-stable-ee
|
|||
```bash
|
||||
cd /home/git/gitlab-shell
|
||||
sudo -u git -H git fetch --all --tags
|
||||
sudo -u git -H git checkout v4.0.3
|
||||
sudo -u git -H git checkout v4.1.0
|
||||
```
|
||||
|
||||
### 6. Update gitlab-workhorse
|
||||
|
|
|
@ -473,7 +473,7 @@ module API
|
|||
desc: 'The description of the tracker'
|
||||
}
|
||||
],
|
||||
'slack' => [
|
||||
'slack-notification' => [
|
||||
{
|
||||
required: true,
|
||||
name: :webhook,
|
||||
|
@ -493,6 +493,14 @@ module API
|
|||
desc: 'The channel name'
|
||||
}
|
||||
],
|
||||
'mattermost-notification' => [
|
||||
{
|
||||
required: true,
|
||||
name: :webhook,
|
||||
type: String,
|
||||
desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...'
|
||||
}
|
||||
],
|
||||
'teamcity' => [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
@ -107,7 +107,7 @@ describe 'Commits' do
|
|||
describe 'Cancel build' do
|
||||
it 'cancels build' do
|
||||
visit ci_status_path(pipeline)
|
||||
click_on 'Cancel'
|
||||
find('a.btn[title="Cancel"]').click
|
||||
expect(page).to have_content 'canceled'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,8 +38,8 @@ describe "Pipelines", feature: true, js: true do
|
|||
expect(page).to have_css('#js-tab-pipeline.active')
|
||||
end
|
||||
|
||||
context 'pipeline graph' do
|
||||
context 'running build' do
|
||||
describe 'pipeline graph' do
|
||||
context 'when pipeline has running builds' do
|
||||
it 'shows a running icon and a cancel action for the running build' do
|
||||
page.within('a[data-title="deploy - running"]') do
|
||||
expect(page).to have_selector('.ci-status-icon-running')
|
||||
|
@ -58,7 +58,7 @@ describe "Pipelines", feature: true, js: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'success build' do
|
||||
context 'when pipeline has successful builds' do
|
||||
it 'shows the success icon and a retry action for the successfull build' do
|
||||
page.within('a[data-title="build - passed"]') do
|
||||
expect(page).to have_selector('.ci-status-icon-success')
|
||||
|
@ -77,7 +77,7 @@ describe "Pipelines", feature: true, js: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'failed build' do
|
||||
context 'when pipeline has failed builds' do
|
||||
it 'shows the failed icon and a retry action for the failed build' do
|
||||
page.within('a[data-title="test - failed"]') do
|
||||
expect(page).to have_selector('.ci-status-icon-failed')
|
||||
|
@ -96,7 +96,7 @@ describe "Pipelines", feature: true, js: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'manual build' do
|
||||
context 'when pipeline has manual builds' do
|
||||
it 'shows the skipped icon and a play action for the manual build' do
|
||||
page.within('a[data-title="manual build - manual play action"]') do
|
||||
expect(page).to have_selector('.ci-status-icon-skipped')
|
||||
|
@ -115,7 +115,7 @@ describe "Pipelines", feature: true, js: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'external build' do
|
||||
context 'when pipeline has external build' do
|
||||
it 'shows the success icon and the generic comit status build' do
|
||||
expect(page).to have_selector('.ci-status-icon-success')
|
||||
expect(page).to have_content('jenkins')
|
||||
|
|
|
@ -2,8 +2,8 @@ require 'spec_helper'
|
|||
|
||||
feature 'Projects > Slack service > Setup events', feature: true do
|
||||
let(:user) { create(:user) }
|
||||
let(:service) { SlackService.new }
|
||||
let(:project) { create(:project, slack_service: service) }
|
||||
let(:service) { SlackNotificationService.new }
|
||||
let(:project) { create(:project, slack_notification_service: service) }
|
||||
|
||||
background do
|
||||
service.fields
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
/* eslint-disable space-before-function-paren, no-new, padded-blocks */
|
||||
|
||||
/*= require lib/utils/text_utility */
|
||||
/*= require abuse_reports */
|
||||
/*= require jquery */
|
||||
|
||||
((global) => {
|
||||
const FIXTURE = 'abuse_reports.html';
|
||||
const MAX_MESSAGE_LENGTH = 500;
|
||||
describe('Abuse Reports', () => {
|
||||
const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
|
||||
const MAX_MESSAGE_LENGTH = 500;
|
||||
|
||||
function assertMaxLength($message) {
|
||||
expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
|
||||
}
|
||||
let messages;
|
||||
|
||||
const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
|
||||
const findMessage = searchText => messages.filter(
|
||||
(index, element) => element.innerText.indexOf(searchText) > -1,
|
||||
).first();
|
||||
|
||||
describe('Abuse Reports', function() {
|
||||
fixture.preload(FIXTURE);
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
fixture.load(FIXTURE);
|
||||
new global.AbuseReports();
|
||||
this.abuseReports = new global.AbuseReports();
|
||||
messages = $('.abuse-reports .message');
|
||||
});
|
||||
|
||||
it('should truncate long messages', function() {
|
||||
const $longMessage = $('#long');
|
||||
|
||||
it('should truncate long messages', () => {
|
||||
const $longMessage = findMessage('LONG MESSAGE');
|
||||
expect($longMessage.data('original-message')).toEqual(jasmine.anything());
|
||||
assertMaxLength($longMessage);
|
||||
});
|
||||
|
||||
it('should not truncate short messages', function() {
|
||||
const $shortMessage = $('#short');
|
||||
it('should not truncate short messages', () => {
|
||||
const $shortMessage = findMessage('SHORT MESSAGE');
|
||||
expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything());
|
||||
});
|
||||
|
||||
it('should allow clicking a truncated message to expand and collapse the full message', function() {
|
||||
const $longMessage = $('#long');
|
||||
it('should allow clicking a truncated message to expand and collapse the full message', () => {
|
||||
const $longMessage = findMessage('LONG MESSAGE');
|
||||
$longMessage.click();
|
||||
expect($longMessage.data('original-message').length).toEqual($longMessage.text().length);
|
||||
$longMessage.click();
|
||||
assertMaxLength($longMessage);
|
||||
});
|
||||
});
|
||||
|
||||
})(window.gl);
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
.abuse-reports
|
||||
.message#long
|
||||
Cat ipsum dolor sit amet, hide head under blanket so no one can see.
|
||||
Gate keepers of hell eat and than sleep on your face but hunt by meowing
|
||||
loudly at 5am next to human slave food dispenser cats go for world
|
||||
domination or chase laser, yet poop on grasses chirp at birds. Cat is love,
|
||||
cat is life chase after silly colored fish toys around the house climb a
|
||||
tree, wait for a fireman jump to fireman then scratch his face fall asleep
|
||||
on the washing machine lies down always hungry so caticus cuteicus. Sit on
|
||||
human. Spot something, big eyes, big eyes, crouch, shake butt, prepare to
|
||||
pounce sleep in the bathroom sink hiss at vacuum cleaner hide head under
|
||||
blanket so no one can see throwup on your pillow.
|
||||
.message#short
|
||||
Cat ipsum dolor sit amet, groom yourself 4 hours - checked, have your
|
||||
beauty sleep 18 hours - checked, be fabulous for the rest of the day -
|
||||
checked! for shake treat bag.
|
|
@ -0,0 +1,27 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:abuse_report) { create(:abuse_report) }
|
||||
let!(:abuse_report_with_short_message) { create(:abuse_report, message: 'SHORT MESSAGE') }
|
||||
let!(:abuse_report_with_long_message) { create(:abuse_report, message: "LONG MESSAGE\n" * 50) }
|
||||
|
||||
render_views
|
||||
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('abuse_reports/')
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
it 'abuse_reports/abuse_reports_list.html.raw' do |example|
|
||||
get :index
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
end
|
|
@ -136,7 +136,8 @@ project:
|
|||
- assembla_service
|
||||
- asana_service
|
||||
- gemnasium_service
|
||||
- slack_service
|
||||
- slack_notification_service
|
||||
- mattermost_notification_service
|
||||
- buildkite_service
|
||||
- bamboo_service
|
||||
- teamcity_service
|
||||
|
|
|
@ -7,7 +7,7 @@ describe Gitlab::Middleware::Multipart do
|
|||
let(:middleware) { described_class.new(app) }
|
||||
|
||||
it 'opens top-level files' do
|
||||
Tempfile.open do |tempfile|
|
||||
Tempfile.open('top-level') do |tempfile|
|
||||
env = post_env({ 'file' => tempfile.path }, { 'file.name' => 'filename' }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
|
||||
|
||||
expect(app).to receive(:call) do |env|
|
||||
|
@ -33,7 +33,7 @@ describe Gitlab::Middleware::Multipart do
|
|||
end
|
||||
|
||||
it 'opens files one level deep' do
|
||||
Tempfile.open do |tempfile|
|
||||
Tempfile.open('one-level') do |tempfile|
|
||||
in_params = { 'user' => { 'avatar' => { '.name' => 'filename' } } }
|
||||
env = post_env({ 'user[avatar]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
|
||||
|
||||
|
@ -48,7 +48,7 @@ describe Gitlab::Middleware::Multipart do
|
|||
end
|
||||
|
||||
it 'opens files two levels deep' do
|
||||
Tempfile.open do |tempfile|
|
||||
Tempfile.open('two-levels') do |tempfile|
|
||||
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => 'filename' } } } }
|
||||
env = post_env({ 'project[milestone][themesong]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
|
||||
|
||||
|
|
|
@ -128,4 +128,26 @@ describe Namespace, models: true do
|
|||
it { expect(group.full_path).to eq(group.path) }
|
||||
it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
|
||||
end
|
||||
|
||||
describe '#full_name' do
|
||||
let(:group) { create(:group) }
|
||||
let(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
it { expect(group.full_name).to eq(group.name) }
|
||||
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
|
||||
end
|
||||
|
||||
describe '#parents' do
|
||||
let(:group) { create(:group) }
|
||||
let(:nested_group) { create(:group, parent: group) }
|
||||
let(:deep_nested_group) { create(:group, parent: nested_group) }
|
||||
let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
|
||||
|
||||
it 'returns the correct parents' do
|
||||
expect(very_deep_nested_group.parents).to eq([group, nested_group, deep_nested_group])
|
||||
expect(deep_nested_group.parents).to eq([group, nested_group])
|
||||
expect(nested_group.parents).to eq([group])
|
||||
expect(group.parents).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::BuildMessage do
|
||||
subject { SlackService::BuildMessage.new(args) }
|
||||
describe ChatMessage::BuildMessage do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:args) do
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::IssueMessage, models: true do
|
||||
subject { SlackService::IssueMessage.new(args) }
|
||||
describe ChatMessage::IssueMessage, models: true do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:args) do
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::MergeMessage, models: true do
|
||||
subject { SlackService::MergeMessage.new(args) }
|
||||
describe ChatMessage::MergeMessage, models: true do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:args) do
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::NoteMessage, models: true do
|
||||
describe ChatMessage::NoteMessage, models: true do
|
||||
let(:color) { '#345' }
|
||||
|
||||
before do
|
||||
|
@ -36,7 +36,7 @@ describe SlackService::NoteMessage, models: true do
|
|||
end
|
||||
|
||||
it 'returns a message regarding notes on commits' do
|
||||
message = SlackService::NoteMessage.new(@args)
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"commit 5f163b2b> in <somewhere.com|project_name>: " \
|
||||
"*Added a commit message*")
|
||||
|
@ -62,7 +62,7 @@ describe SlackService::NoteMessage, models: true do
|
|||
end
|
||||
|
||||
it 'returns a message regarding notes on a merge request' do
|
||||
message = SlackService::NoteMessage.new(@args)
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"merge request !30> in <somewhere.com|project_name>: " \
|
||||
"*merge request title*")
|
||||
|
@ -88,7 +88,7 @@ describe SlackService::NoteMessage, models: true do
|
|||
end
|
||||
|
||||
it 'returns a message regarding notes on an issue' do
|
||||
message = SlackService::NoteMessage.new(@args)
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq(
|
||||
"test.user <url|commented on " \
|
||||
"issue #20> in <somewhere.com|project_name>: " \
|
||||
|
@ -114,7 +114,7 @@ describe SlackService::NoteMessage, models: true do
|
|||
end
|
||||
|
||||
it 'returns a message regarding notes on a project snippet' do
|
||||
message = SlackService::NoteMessage.new(@args)
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"snippet #5> in <somewhere.com|project_name>: " \
|
||||
"*snippet title*")
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::PipelineMessage do
|
||||
subject { SlackService::PipelineMessage.new(args) }
|
||||
describe ChatMessage::PipelineMessage do
|
||||
subject { described_class.new(args) }
|
||||
let(:user) { { name: 'hacker' } }
|
||||
|
||||
let(:args) do
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::PushMessage, models: true do
|
||||
subject { SlackService::PushMessage.new(args) }
|
||||
describe ChatMessage::PushMessage, models: true do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:args) do
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::WikiPageMessage, models: true do
|
||||
describe ChatMessage::WikiPageMessage, models: true do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:args) do
|
|
@ -0,0 +1,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ChatNotificationService, models: true do
|
||||
describe "Associations" do
|
||||
before do
|
||||
allow(subject).to receive(:activated?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of :webhook }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MattermostNotificationService, models: true do
|
||||
it_behaves_like "slack or mattermost"
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackNotificationService, models: true do
|
||||
it_behaves_like "slack or mattermost"
|
||||
end
|
|
@ -22,7 +22,8 @@ describe Project, models: true do
|
|||
it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:chat_services) }
|
||||
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:slack_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:slack_notification_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:boards).dependent(:destroy) }
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe GroupPolicy, models: true do
|
||||
let(:guest) { create(:user) }
|
||||
let(:reporter) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
let(:master) { create(:user) }
|
||||
let(:owner) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:group) { create(:group) }
|
||||
|
||||
let(:master_permissions) do
|
||||
[
|
||||
:create_projects,
|
||||
:admin_milestones,
|
||||
:admin_label
|
||||
]
|
||||
end
|
||||
|
||||
let(:owner_permissions) do
|
||||
[
|
||||
:admin_group,
|
||||
:admin_namespace,
|
||||
:admin_group_member,
|
||||
:change_visibility_level
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_guest(guest)
|
||||
group.add_reporter(reporter)
|
||||
group.add_developer(developer)
|
||||
group.add_master(master)
|
||||
group.add_owner(owner)
|
||||
end
|
||||
|
||||
subject { described_class.abilities(current_user, group).to_set }
|
||||
|
||||
context 'with no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.not_to include(*master_permissions)
|
||||
is_expected.not_to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'guests' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.not_to include(*master_permissions)
|
||||
is_expected.not_to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'reporter' do
|
||||
let(:current_user) { reporter }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.not_to include(*master_permissions)
|
||||
is_expected.not_to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'developer' do
|
||||
let(:current_user) { developer }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.not_to include(*master_permissions)
|
||||
is_expected.not_to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'master' do
|
||||
let(:current_user) { master }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.to include(*master_permissions)
|
||||
is_expected.not_to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'owner' do
|
||||
let(:current_user) { owner }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.to include(*master_permissions)
|
||||
is_expected.to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'admin' do
|
||||
let(:current_user) { admin }
|
||||
|
||||
it do
|
||||
is_expected.to include(:read_group)
|
||||
is_expected.to include(*master_permissions)
|
||||
is_expected.to include(*owner_permissions)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -376,5 +376,10 @@ describe Issues::UpdateService, services: true do
|
|||
let(:mentionable) { issue }
|
||||
include_examples 'updating mentions', Issues::UpdateService
|
||||
end
|
||||
|
||||
include_examples 'issuable update service' do
|
||||
let(:open_issuable) { issue }
|
||||
let(:closed_issuable) { create(:closed_issue, project: project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -320,5 +320,10 @@ describe MergeRequests::UpdateService, services: true do
|
|||
expect(issue_ids).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'issuable update service' do
|
||||
let(:open_issuable) { merge_request }
|
||||
let(:closed_issuable) { create(:closed_merge_request, source_project: project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
shared_examples 'issuable update service' do
|
||||
context 'changing state' do
|
||||
before { expect(project).to receive(:execute_hooks).once }
|
||||
|
||||
context 'to reopened' do
|
||||
it 'executes hooks only once' do
|
||||
described_class.new(project, user, state_event: 'reopen').execute(closed_issuable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'to closed' do
|
||||
it 'executes hooks only once' do
|
||||
described_class.new(project, user, state_event: 'close').execute(open_issuable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
|
||||
|
||||
describe SlackService, models: true do
|
||||
let(:slack) { SlackService.new }
|
||||
RSpec.shared_examples 'slack or mattermost' do
|
||||
let(:chat_service) { described_class.new }
|
||||
let(:webhook_url) { 'https://example.gitlab.com/' }
|
||||
|
||||
describe "Associations" do
|
||||
|
@ -24,7 +24,7 @@ describe SlackService, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Execute" do
|
||||
describe "#execute" do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:username) { 'slack_username' }
|
||||
|
@ -35,7 +35,7 @@ describe SlackService, models: true do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
allow(chat_service).to receive_messages(
|
||||
project: project,
|
||||
project_id: project.id,
|
||||
service_hook: true,
|
||||
|
@ -77,54 +77,55 @@ describe SlackService, models: true do
|
|||
@wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
|
||||
end
|
||||
|
||||
it "calls Slack API for push events" do
|
||||
slack.execute(push_sample_data)
|
||||
it "calls Slack/Mattermost API for push events" do
|
||||
chat_service.execute(push_sample_data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
||||
it "calls Slack API for issue events" do
|
||||
slack.execute(@issues_sample_data)
|
||||
it "calls Slack/Mattermost API for issue events" do
|
||||
chat_service.execute(@issues_sample_data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
||||
it "calls Slack API for merge requests events" do
|
||||
slack.execute(@merge_sample_data)
|
||||
it "calls Slack/Mattermost API for merge requests events" do
|
||||
chat_service.execute(@merge_sample_data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
||||
it "calls Slack API for wiki page events" do
|
||||
slack.execute(@wiki_page_sample_data)
|
||||
it "calls Slack/Mattermost API for wiki page events" do
|
||||
chat_service.execute(@wiki_page_sample_data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
||||
it 'uses the username as an option for slack when configured' do
|
||||
allow(slack).to receive(:username).and_return(username)
|
||||
allow(chat_service).to receive(:username).and_return(username)
|
||||
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, username: username).
|
||||
with(webhook_url, username: username, channel: chat_service.default_channel).
|
||||
and_return(
|
||||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(push_sample_data)
|
||||
chat_service.execute(push_sample_data)
|
||||
end
|
||||
|
||||
it 'uses the channel as an option when it is configured' do
|
||||
allow(slack).to receive(:channel).and_return(channel)
|
||||
allow(chat_service).to receive(:channel).and_return(channel)
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, channel: channel).
|
||||
and_return(
|
||||
double(:slack_service).as_null_object
|
||||
)
|
||||
slack.execute(push_sample_data)
|
||||
chat_service.execute(push_sample_data)
|
||||
end
|
||||
|
||||
context "event channels" do
|
||||
it "uses the right channel for push event" do
|
||||
slack.update_attributes(push_channel: "random")
|
||||
chat_service.update_attributes(push_channel: "random")
|
||||
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, channel: "random").
|
||||
|
@ -132,11 +133,11 @@ describe SlackService, models: true do
|
|||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(push_sample_data)
|
||||
chat_service.execute(push_sample_data)
|
||||
end
|
||||
|
||||
it "uses the right channel for merge request event" do
|
||||
slack.update_attributes(merge_request_channel: "random")
|
||||
chat_service.update_attributes(merge_request_channel: "random")
|
||||
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, channel: "random").
|
||||
|
@ -144,11 +145,11 @@ describe SlackService, models: true do
|
|||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(@merge_sample_data)
|
||||
chat_service.execute(@merge_sample_data)
|
||||
end
|
||||
|
||||
it "uses the right channel for issue event" do
|
||||
slack.update_attributes(issue_channel: "random")
|
||||
chat_service.update_attributes(issue_channel: "random")
|
||||
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, channel: "random").
|
||||
|
@ -156,11 +157,11 @@ describe SlackService, models: true do
|
|||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(@issues_sample_data)
|
||||
chat_service.execute(@issues_sample_data)
|
||||
end
|
||||
|
||||
it "uses the right channel for wiki event" do
|
||||
slack.update_attributes(wiki_page_channel: "random")
|
||||
chat_service.update_attributes(wiki_page_channel: "random")
|
||||
|
||||
expect(Slack::Notifier).to receive(:new).
|
||||
with(webhook_url, channel: "random").
|
||||
|
@ -168,7 +169,7 @@ describe SlackService, models: true do
|
|||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(@wiki_page_sample_data)
|
||||
chat_service.execute(@wiki_page_sample_data)
|
||||
end
|
||||
|
||||
context "note event" do
|
||||
|
@ -177,7 +178,7 @@ describe SlackService, models: true do
|
|||
end
|
||||
|
||||
it "uses the right channel" do
|
||||
slack.update_attributes(note_channel: "random")
|
||||
chat_service.update_attributes(note_channel: "random")
|
||||
|
||||
note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
|
||||
|
||||
|
@ -187,7 +188,7 @@ describe SlackService, models: true do
|
|||
double(:slack_service).as_null_object
|
||||
)
|
||||
|
||||
slack.execute(note_data)
|
||||
chat_service.execute(note_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -198,7 +199,7 @@ describe SlackService, models: true do
|
|||
let(:project) { create(:project, creator_id: user.id) }
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
allow(chat_service).to receive_messages(
|
||||
project: project,
|
||||
project_id: project.id,
|
||||
service_hook: true,
|
||||
|
@ -216,9 +217,9 @@ describe SlackService, models: true do
|
|||
note: 'a comment on a commit')
|
||||
end
|
||||
|
||||
it "calls Slack API for commit comment events" do
|
||||
it "calls Slack/Mattermost API for commit comment events" do
|
||||
data = Gitlab::DataBuilder::Note.build(commit_note, user)
|
||||
slack.execute(data)
|
||||
chat_service.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
@ -232,7 +233,7 @@ describe SlackService, models: true do
|
|||
|
||||
it "calls Slack API for merge request comment events" do
|
||||
data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
|
||||
slack.execute(data)
|
||||
chat_service.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
@ -245,7 +246,7 @@ describe SlackService, models: true do
|
|||
|
||||
it "calls Slack API for issue comment events" do
|
||||
data = Gitlab::DataBuilder::Note.build(issue_note, user)
|
||||
slack.execute(data)
|
||||
chat_service.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
@ -259,7 +260,7 @@ describe SlackService, models: true do
|
|||
|
||||
it "calls Slack API for snippet comment events" do
|
||||
data = Gitlab::DataBuilder::Note.build(snippet_note, user)
|
||||
slack.execute(data)
|
||||
chat_service.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
@ -277,21 +278,21 @@ describe SlackService, models: true do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
allow(chat_service).to receive_messages(
|
||||
project: project,
|
||||
service_hook: true,
|
||||
webhook: webhook_url
|
||||
)
|
||||
end
|
||||
|
||||
shared_examples 'call Slack API' do
|
||||
shared_examples 'call Slack/Mattermost API' do
|
||||
before do
|
||||
WebMock.stub_request(:post, webhook_url)
|
||||
end
|
||||
|
||||
it 'calls Slack API for pipeline events' do
|
||||
it 'calls Slack/Mattermost API for pipeline events' do
|
||||
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
|
||||
slack.execute(data)
|
||||
chat_service.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
|
@ -300,16 +301,16 @@ describe SlackService, models: true do
|
|||
context 'with failed pipeline' do
|
||||
let(:status) { 'failed' }
|
||||
|
||||
it_behaves_like 'call Slack API'
|
||||
it_behaves_like 'call Slack/Mattermost API'
|
||||
end
|
||||
|
||||
context 'with succeeded pipeline' do
|
||||
let(:status) { 'success' }
|
||||
|
||||
context 'with default to notify_only_broken_pipelines' do
|
||||
it 'does not call Slack API for pipeline events' do
|
||||
it 'does not call Slack/Mattermost API for pipeline events' do
|
||||
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
|
||||
result = slack.execute(data)
|
||||
result = chat_service.execute(data)
|
||||
|
||||
expect(result).to be_falsy
|
||||
end
|
||||
|
@ -317,10 +318,10 @@ describe SlackService, models: true do
|
|||
|
||||
context 'with setting notify_only_broken_pipelines to false' do
|
||||
before do
|
||||
slack.notify_only_broken_pipelines = false
|
||||
chat_service.notify_only_broken_pipelines = false
|
||||
end
|
||||
|
||||
it_behaves_like 'call Slack API'
|
||||
it_behaves_like 'call Slack/Mattermost API'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,44 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Khan Academy
|
||||
|
||||
This software also uses portions of the underscore.js project, which is
|
||||
MIT licensed with the following copyright:
|
||||
|
||||
Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative
|
||||
Reporters & Editors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Here is how to build a version of KaTeX that works with gitlab.
|
||||
|
||||
The problem is that the standard procedure for changing font location doesn't work for the empty string.
|
||||
|
||||
1. Clone KaTeX. Anything later than 4fb9445a9 (is merged into master) will do.
|
||||
2. make (requires node)
|
||||
3. sed -i 's,fonts/,,' build/katex.css
|
||||
4. Copy build/katex.js, build/katex.css and fonts/* to gitlab.
|
||||
*/
|
||||
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.katex = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
/* eslint no-console:0 */
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,44 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Khan Academy
|
||||
|
||||
This software also uses portions of the underscore.js project, which is
|
||||
MIT licensed with the following copyright:
|
||||
|
||||
Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative
|
||||
Reporters & Editors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Here is how to build a version of KaTeX that works with gitlab.
|
||||
|
||||
The problem is that the standard procedure for changing font location doesn't work for the empty string.
|
||||
|
||||
1. Clone KaTeX. Anything later than 4fb9445a9 (is merged into master) will do.
|
||||
2. make (requires node)
|
||||
3. sed -i 's,fonts/,,' build/katex.css
|
||||
4. Copy build/katex.js, build/katex.css and fonts/* to gitlab.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'KaTeX_AMS';
|
||||
src: url('KaTeX_AMS-Regular.eot');
|
||||
|
|