diff --git a/CHANGELOG.md b/CHANGELOG.md index fb13db4dd1c..786b0128aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.5 (2016-12-14) + +- Moved Leave Project and Leave Group buttons to access_request_buttons from the settings dropdown. !7600 +- fix display hook error message. !7775 (basyura) +- Remove wrong '.builds-feature' class from the MR settings fieldset. !7930 +- Avoid escaping relative links in Markdown twice. !7940 (winniehell) +- API: Memoize the current_user so that sudo can work properly. !8017 +- Displays milestone remaining days only when it's present. +- Allow branch names with dots on API endpoint. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Shows group members in project members list. +- Encode input when migrating ProcessCommitWorker jobs to prevent migration errors. +- Fixed timeago re-rendering every timeago. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.14.4 (2016-12-08) - Fix diff view permalink highlighting. !7090 @@ -264,6 +279,13 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.10 (2016-12-14) + +- API: Memoize the current_user so that sudo can work properly. !8017 +- Filter `authentication_token`, `incoming_email_token` and `runners_token` parameters. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.13.9 (2016-12-08) - Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ee74734aa22..627a3f43a64 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -4.1.0 +4.1.1 diff --git a/Gemfile b/Gemfile index bea31b53b1c..774dceff4f4 100644 --- a/Gemfile +++ b/Gemfile @@ -169,7 +169,7 @@ gem 'gitlab-flowdock-git-hook', '~> 1.0.1' gem 'gemnasium-gitlab-service', '~> 0.2' # Slack integration -gem 'slack-notifier', '~> 1.2.0' +gem 'slack-notifier', '~> 1.5.1' # Asana integration gem 'asana', '~> 0.4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 811adfc5c1d..8bc22479346 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -683,7 +683,7 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - slack-notifier (1.2.1) + slack-notifier (1.5.1) slop (3.6.0) spinach (0.8.10) colorize @@ -952,7 +952,7 @@ DEPENDENCIES sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) - slack-notifier (~> 1.2.0) + slack-notifier (~> 1.5.1) spinach-rails (~> 0.2.1) spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 57dabfe05e4..bb516b3d2df 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -343,16 +343,18 @@ selector = ".dropdown-page-one .dropdown-content a"; } this.dropdown.on("click", selector, function(e) { - var $el, selected; + var $el, selected, selectedObj, isMarking; $el = $(this); selected = self.rowClicked($el); + selectedObj = selected ? selected[0] : null; + isMarking = selected ? selected[1] : null; if (self.options.clicked) { - self.options.clicked(selected[0], $el, e, selected[1]); + self.options.clicked(selectedObj, $el, e, isMarking); } // Update label right after all modifications in dropdown has been done if (self.options.toggleLabel) { - self.updateLabel(selected[0], $el, self); + self.updateLabel(selectedObj, $el, self); } $el.trigger('blur'); diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 324b68a7efc..5d0d594073d 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -19,7 +19,7 @@ }); $(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) { if (data.saved) { - return $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html); + return $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html); } else { return new Flash('Failed to save new settings', 'alert'); } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 940807fc399..8726a69867b 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -96,6 +96,10 @@ label { code { line-height: 1.8; } + + img { + margin-right: $gl-padding; + } } @media(max-width: $screen-xs-max) { diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index 226bd2ead31..8624a25c052 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -49,3 +49,11 @@ fill: $gray-darkest; } } + +.ci-status-icon-manual { + color: $gl-text-color; + + svg { + fill: $gl-text-color; + } +} diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index abfdd7a759d..7eb9962ba33 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -54,7 +54,7 @@ } // Display Star and Fork buttons without counters on mobile. - .project-action-buttons { + .project-repo-buttons { display: block; .count-buttons .btn { diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss index fff7d7f7524..625bea96aaa 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page-header.scss @@ -57,7 +57,6 @@ } .ci-status-link { - svg { position: relative; top: 2px; diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 5ba0486177f..9d8d08dff88 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -18,6 +18,20 @@ margin-top: -2px; margin-left: 5px; } + + &.split { + display: flex; + align-items: center; + } + + .left { + flex: 1 1 auto; + } + + .right { + flex: 0 0 auto; + text-align: right; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index d0c27d64239..460c5d995be 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -24,6 +24,7 @@ $gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; +$gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c4c4c4; diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 80baebd5ea3..9b28df1afc5 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -2,7 +2,6 @@ padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; color: $gl-text-color-dark; - font-size: 16px; line-height: 34px; .author { diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index a9af7af59e2..16bff5f1e03 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -27,12 +27,6 @@ } } -.group-buttons { - .notification-dropdown { - display: inline-block; - } -} - .groups-header { @media (min-width: $screen-sm-min) { .nav-links { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index be22e7bdc79..243c9153ded 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -428,7 +428,7 @@ width: 21px; height: 25px; position: absolute; - top: -32px; + top: -31px; border-top: 2px solid $border-color; } @@ -456,32 +456,31 @@ } .build { - border: 1px solid $border-color; - border-radius: 30px; - background-color: $white-light; position: relative; - padding: 8px 4px 9px 10px; width: 186px; margin-bottom: 10px; white-space: normal; + color: $gl-text-color-light; - &:hover { - background-color: $stage-hover-bg; - border: 1px solid $stage-hover-border; + > .build-content { + display: inline-block; + padding: 8px 10px 9px; + width: 100%; + border: 1px solid $border-color; + border-radius: 30px; + background-color: $white-light; - a, - .dropdown-counter-badge, - .dropdown-menu-toggle { + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $stage-hover-border; color: $gl-text-color; } + } - .grouped-pipeline-dropdown a { - color: $gl-text-color-light; - - &:hover { - color: $gl-text-color; - } - } + > .ci-action-icon-container { + position: absolute; + right: 4px; + top: 5px; } .ci-status-icon { @@ -621,8 +620,8 @@ padding: 0; width: 191px; left: auto; - right: -206px; - top: -11px; + right: -195px; + top: -4px; box-shadow: 0 1px 5px $black-transparent; a { @@ -650,17 +649,25 @@ .dropdown-build { color: $gl-text-color-light; - a.ci-action-icon-container { - padding: 0; + .build-content { + width: 100%; + } + + .ci-action-icon-container { font-size: 11px; - float: right; - margin-top: 4px; - display: inline-block; - position: relative; + position: absolute; + right: 4px; i { + width: 25px; + height: 25px; font-size: 11px; margin-top: 0; + + &::before { + top: 1px; + left: 1px; + } } } @@ -670,18 +677,6 @@ color: $gl-text-color; } - .ci-action-icon-container { - i { - width: 25px; - height: 25px; - - &::before { - top: 1px; - left: 1px; - } - } - } - .stage { max-width: 100px; width: 100px; @@ -704,9 +699,6 @@ // Action Icons .ci-action-icon-container .ci-action-icon-wrapper { - float: right; - margin-top: -4px; - i { color: $border-color; border-radius: 100%; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2e2532580b6..f1493a60b01 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -145,8 +145,6 @@ .project-repo-buttons, .group-buttons { - margin-top: 15px; - .btn { @include btn-gray; padding: 3px 10px; @@ -175,11 +173,9 @@ } } - .download-button, - .dropdown-toggle, - .notification-dropdown, - .project-dropdown { - margin-left: 10px; + .project-action-button { + margin: 15px 5px 0; + vertical-align: top; } .notification-dropdown .dropdown-menu { @@ -195,13 +191,15 @@ .count-buttons { display: inline-block; vertical-align: top; + margin-top: 15px; } .project-clone-holder { display: inline-block; + margin: 15px 5px 0 0; input { - height: 29px; + height: 28px; } } @@ -255,7 +253,7 @@ line-height: 13px; padding: $gl-vert-padding $gl-padding; letter-spacing: .4px; - padding: 7px 14px; + padding: 6px 14px; text-align: center; vertical-align: middle; touch-action: manipulation; @@ -492,6 +490,7 @@ a.deploy-project-label { .project-stats { font-size: 0; + text-align: center; border-bottom: 1px solid $border-color; .nav { diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index f3b0608e545..a810ed32327 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -15,8 +15,7 @@ height: 13px; width: 13px; position: relative; - top: 1px; - margin-right: 3px; + top: 2px; overflow: visible; } @@ -25,7 +24,7 @@ border-color: $gl-danger; &:not(span):hover { - background-color: rgba( $gl-danger, .07); + background-color: rgba($gl-danger, .07); } svg { @@ -39,7 +38,7 @@ border-color: $gl-success; &:not(span):hover { - background-color: rgba( $gl-success, .07); + background-color: rgba($gl-success, .07); } svg { @@ -52,7 +51,7 @@ border-color: $gl-info; &:not(span):hover { - background-color: rgba( $gl-info, .07); + background-color: rgba($gl-info, .07); } svg { @@ -66,7 +65,7 @@ border-color: $gl-gray; &:not(span):hover { - background-color: rgba( $gl-gray, .07); + background-color: rgba($gl-gray, .07); } svg { @@ -79,7 +78,7 @@ border-color: $gl-warning; &:not(span):hover { - background-color: rgba( $gl-warning, .07); + background-color: rgba($gl-warning, .07); } svg { @@ -92,7 +91,7 @@ border-color: $blue-normal; &:not(span):hover { - background-color: rgba( $blue-normal, .07); + background-color: rgba($blue-normal, .07); } svg { @@ -106,13 +105,26 @@ border-color: $gl-gray-light; &:not(span):hover { - background-color: rgba( $gl-gray-light, .07); + background-color: rgba($gl-gray-light, .07); } svg { fill: $gl-gray-light; } } + + &.ci-manual { + color: $gl-text-color; + border-color: $gl-text-color; + + &:not(span):hover { + background-color: rgba($gl-text-color, .07); + } + + svg { + fill: $gl-text-color; + } + } } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index c0341db7289..cad4e149845 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -14,6 +14,7 @@ .add-to-tree { vertical-align: top; + padding: 6px 10px; } .tree-table { @@ -172,7 +173,7 @@ position: relative; z-index: 2; - .download-button { + .project-action-button { margin-left: $btn-side-margin; } } diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index f576d0be1fc..863a766a255 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -8,6 +8,9 @@ class Projects::BlameController < Projects::ApplicationController def show @blob = @repository.blob_at(@commit.id, @path) + + return render_404 unless @blob + @blame_groups = Gitlab::Blame.new(@blob, @commit).groups end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 591aba6bdc9..cb76cdf5981 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -18,7 +18,7 @@ module Ci end serialize :options - serialize :yaml_variables + serialize :yaml_variables, Gitlab::Serialize::Ci::Variables validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b7c775777c7..5ce20cc43d6 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -97,7 +97,7 @@ class MergeRequest < ActiveRecord::Base validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true - validates :merge_user, presence: true, if: :merge_when_build_succeeds? + validates :merge_user, presence: true, if: :merge_when_build_succeeds?, unless: :importing? validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? diff --git a/app/models/project.rb b/app/models/project.rb index 5d5d6737dad..009843d6aed 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -79,7 +79,6 @@ class Project < ActiveRecord::Base has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' has_many :boards, before_add: :validate_board_limit, dependent: :destroy - has_many :chat_services # Project services has_one :campfire_service, dependent: :destroy @@ -96,6 +95,7 @@ class Project < ActiveRecord::Base has_one :gemnasium_service, dependent: :destroy has_one :mattermost_slash_commands_service, dependent: :destroy has_one :mattermost_notification_service, dependent: :destroy + has_one :slack_slash_commands_service, dependent: :destroy has_one :slack_notification_service, dependent: :destroy has_one :buildkite_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy diff --git a/app/models/project_services/chat_service.rb b/app/models/project_services/chat_service.rb deleted file mode 100644 index 574788462de..00000000000 --- a/app/models/project_services/chat_service.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Base class for Chat services -# This class is not meant to be used directly, but only to inherit from. -class ChatService < Service - default_value_for :category, 'chat' - - has_many :chat_names, foreign_key: :service_id - - def valid_token?(token) - self.respond_to?(:token) && - self.token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) - end - - def supported_events - [] - end - - def trigger(params) - raise NotImplementedError - end -end diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb new file mode 100644 index 00000000000..0bc160af604 --- /dev/null +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -0,0 +1,56 @@ +# Base class for Chat services +# This class is not meant to be used directly, but only to inherrit from. +class ChatSlashCommandsService < Service + default_value_for :category, 'chat' + + prop_accessor :token + + has_many :chat_names, foreign_key: :service_id, dependent: :destroy + + def valid_token?(token) + self.respond_to?(:token) && + self.token.present? && + ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + end + + def supported_events + [] + end + + def can_test? + false + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' } + ] + end + + def trigger(params) + return unless valid_token?(params[:token]) + + user = find_chat_user(params) + unless user + url = authorize_chat_name_url(params) + return presenter.authorize_chat_name(url) + end + + Gitlab::ChatCommands::Command.new(project, user, + params).execute + end + + private + + def find_chat_user(params) + ChatNames::FindUserService.new(self, params).execute + end + + def authorize_chat_name_url(params) + ChatNames::AuthorizeUserService.new(self, params).execute + end + + def presenter + Gitlab::ChatCommands::Presenter.new + end +end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index accf59bea18..572a02b01d4 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -1,4 +1,4 @@ -class MattermostSlashCommandsService < ChatService +class MattermostSlashCommandsService < ChatSlashCommandsService include TriggersHelper prop_accessor :token @@ -19,12 +19,6 @@ class MattermostSlashCommandsService < ChatService 'mattermost_slash_commands' end - def fields - [ - { type: 'text', name: 'token', placeholder: '' } - ] - end - def configure!(user, params) token = Mattermost::Command.new(user). create(command(params)) @@ -36,18 +30,6 @@ class MattermostSlashCommandsService < ChatService Mattermost::Team.new(user).all end - def trigger(params) - return nil unless valid_token?(params[:token]) - - user = find_chat_user(params) - unless user - url = authorize_chat_name_url(params) - return Mattermost::Presenter.authorize_chat_name(url) - end - - Gitlab::ChatCommands::Command.new(project, user, params).execute - end - private def command(params) @@ -62,12 +44,4 @@ class MattermostSlashCommandsService < ChatService method: 'P', user_name: 'GitLab') end - - def find_chat_user(params) - ChatNames::FindUserService.new(self, params).execute - end - - def authorize_chat_name_url(params) - ChatNames::AuthorizeUserService.new(self, params).execute - end end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb new file mode 100644 index 00000000000..cb19ebf4cad --- /dev/null +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -0,0 +1,28 @@ +class SlackSlashCommandsService < ChatSlashCommandsService + include TriggersHelper + + def title + 'Slack Command' + end + + def description + "Perform common operations on GitLab in Slack" + end + + def to_param + 'slack_slash_commands' + end + + def trigger(params) + # Format messages to be Slack-compatible + super.tap do |result| + result[:text] = format(result[:text]) + end + end + + private + + def format(text) + Slack::Notifier::LinkFormatter.format(text) if text + end +end diff --git a/app/models/route.rb b/app/models/route.rb index d40214b9da6..caf596efa79 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -13,7 +13,7 @@ class Route < ActiveRecord::Base def rename_children # We update each row separately because MySQL does not have regexp_replace. # rubocop:disable Rails/FindEach - Route.where('path LIKE ?', "#{path_was}%").each do |route| + Route.where('path LIKE ?', "#{path_was}/%").each do |route| # Note that update column skips validation and callbacks. # We need this to avoid recursive call of rename_children method route.update_column(:path, route.path.sub(path_was, path)) diff --git a/app/models/service.rb b/app/models/service.rb index 0bbab078cf6..8abd8e73e43 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -216,11 +216,12 @@ class Service < ActiveRecord::Base jira kubernetes mattermost_slash_commands + mattermost_notification pipelines_email pivotaltracker pushover redmine - mattermost_notification + slack_slash_commands slack_notification teamcity ] diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index f2135af2686..601fb7f0f3f 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,10 +1,11 @@ - status = local_assigns.fetch(:status) +- css_classes = "ci-status ci-#{status.group}" - if status.has_details? - = link_to status.details_path, class: "ci-status ci-#{status}" do + = link_to status.details_path, class: css_classes do = custom_icon(status.icon) = status.text - else - %span{ class: "ci-status ci-#{status}" } + %span{ class: css_classes } = custom_icon(status.icon) = status.text diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml index c7d04ab61e9..52b4d77d074 100644 --- a/app/views/ci/status/_graph_badge.html.haml +++ b/app/views/ci/status/_graph_badge.html.haml @@ -2,15 +2,16 @@ - subject = local_assigns.fetch(:subject) - status = subject.detailed_status(current_user) -- klass = "ci-status-icon ci-status-icon-#{status}" +- klass = "ci-status-icon ci-status-icon-#{status.group}" - if status.has_details? - = link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do + = link_to status.details_path, class: 'build-content' do %span{ class: klass }= custom_icon(status.icon) - .ci-status-text= subject.name + .ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{subject.name} - #{status.label}" }= subject.name - else - %span{ class: klass }= custom_icon(status.icon) - .ci-status-text= subject.name + .build-content + %span{ class: klass }= custom_icon(status.icon) + .ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{subject.name} - #{status.label}" }= subject.name - if status.has_action? = link_to status.action_path, method: status.action_method, diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 5a04c3318cf..0d1f2b70018 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -18,11 +18,19 @@ = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - .project-repo-buttons.project-action-buttons + .project-repo-buttons .count-buttons = render 'projects/buttons/star' = render 'projects/buttons/fork' - - if @project.feature_available?(:repository, current_user) - .project-clone-holder - = render "shared/clone_panel" + %span.hidden-xs + - if @project.feature_available?(:repository, current_user) + .project-clone-holder + = render "shared/clone_panel" + + - if current_user && can?(current_user, :download_code, @project) + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' + = render 'shared/notifications/button', notification_setting: @notification_setting + = render 'projects/buttons/koding' + = render 'shared/members/access_request_buttons', source: @project diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 40bfa01a45a..324a7f8cd3f 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - .dropdown.inline.download-button + .project-action-button.dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') = icon("caret-down") diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index d3ccebbe290..f5659be25f0 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,5 +1,5 @@ - if current_user - .dropdown.inline + .project-action-button.dropdown.inline %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('plus') = icon("caret-down") diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml index fdc80d44253..5d9a776da89 100644 --- a/app/views/projects/buttons/_koding.html.haml +++ b/app/views/projects/buttons/_koding.html.haml @@ -1,7 +1,3 @@ -- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch) - - if @repository.koding_yml - = link_to koding_project_url(@project), class: 'btn', target: '_blank' do - Run in IDE (Koding) - - else - = link_to add_koding_stack_path(@project), class: 'btn' do - Set Up Koding +- if koding_enabled? && current_user && @repository.koding_yml && can_push_branch?(@project, @project.default_branch) + = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do + Run in IDE (Koding) diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml new file mode 100644 index 00000000000..c45052e3954 --- /dev/null +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -0,0 +1,93 @@ +- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}" + +.well + This service allows GitLab users to perform common operations on this + project by entering slash commands in Slack. + %br + See list of available commands in Slack after setting up this service, + by entering + %code /<command> help + %br + %br + To setup this service: + %ul.list-unstyled + %li + 1. + = link_to 'Add a slash command', 'https://my.slack.com/services/new/slash-commands' + in your Slack team with these options: + + %hr + + .help-form + .form-group + = label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block + %p Fill in the word that works best for your team. + %p + Suggestions: + %code= 'gitlab' + %code= @project.path # Path contains no spaces, but dashes + %code= @project.path_with_namespace + + .form-group + = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#url') + + .form-group + = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block POST + + .form-group + = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#customize_name') + + .form-group + = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block + = image_tag(asset_url('gitlab_logo.png'), width: 36, height: 36) + = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank') + + .form-group + = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block Show this command in the autocomplete list + + .form-group + = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#autocomplete_description') + + .form-group + = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#autocomplete_usage_hint') + + .form-group + = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#descriptive_label') + + %hr + + %ul.list-unstyled + %li + 2. Paste the + %strong Token + into the field below + %li + 3. Select the + %strong Active + checkbox, press + %strong Save changes + and start using GitLab inside Slack! diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index c50093cf47c..8a214e1de58 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -64,20 +64,11 @@ - unless @repository.gitlab_ci_yml %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do - Set Up CI + Set up CI + - if koding_enabled? && @repository.koding_yml.blank? + %li.missing + = link_to 'Set up Koding', add_koding_stack_path(@project) - %li.project-repo-buttons.right - .project-right-buttons - - if current_user - = render 'shared/members/access_request_buttons', source: @project - = render "projects/buttons/koding" - - .btn-group.project-repo-btn-group - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' - - .pull-right - = render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.commit .project-last-commit{ class: container_class } = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index b70b574e687..6919b210a00 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -10,12 +10,11 @@ - status_groups.each do |group_name, grouped_statuses| - if grouped_statuses.one? - status = grouped_statuses.first - %li.build + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve - .build-content - = render 'ci/status/graph_badge', subject: status + = render 'ci/status/graph_badge', subject: status - else - %li.build + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve - .dropdown.inline.build-content - = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses + = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses + diff --git a/app/views/projects/stage/_in_stage_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index b03837d1211..b15f7eaeab2 100644 --- a/app/views/projects/stage/_in_stage_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml @@ -1,8 +1,8 @@ - group_status = CommitStatus.where(id: subject).status -%button.dropdown-menu-toggle.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } +%button.dropdown-menu-toggle.build-content.has-tooltip{ type: 'button', data: { toggle: 'dropdown'} } %span{class: "ci-status-icon ci-status-icon-#{group_status}"} = ci_icon_for_status(group_status) - %span.ci-status-text + %span.ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{name} - #{group_status}" } = name %span.dropdown-counter-badge= subject.size .dropdown-menu.grouped-pipeline-dropdown diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index e166dfab710..fb795ad1c72 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,16 +1,17 @@ - model_name = source.model_name.to_s.downcase -- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) - = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(source) }, - class: 'btn' -- elsif requester = source.requesters.find_by(user_id: current_user.id) - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(requester) }, - class: 'btn' -- elsif source.request_access_enabled && can?(current_user, :request_access, source) - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn' +.project-action-button.inline + - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'btn' + - elsif requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' + - elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'btn' diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index 8619939dde7..15ff5b8a27e 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -3,10 +3,12 @@ - panel_class = primary ? 'panel-primary' : 'panel-default' .panel{ class: panel_class } - .panel-heading - = title + .panel-heading.split + .left + = title - if show_counter - .pull-right= issuables.size + .right + = issuables.size - class_prefix = dom_class(issuables).pluralize %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index fbad0d05de3..1d072c16b32 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,5 +1,5 @@ - if notification_setting - .dropdown.notification-dropdown + .js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" diff --git a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml b/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml deleted file mode 100644 index 99dbe4a32a0..00000000000 --- a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moved Leave Project and Leave Group buttons to access_request_buttons from - the settings dropdown -merge_request: 7600 -author: diff --git a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml b/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml deleted file mode 100644 index a7576e2cbdb..00000000000 --- a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove wrong '.builds-feature' class from the MR settings fieldset -merge_request: 7930 -author: diff --git a/changelogs/unreleased/25740-fix-new-branch-button-padding.yml b/changelogs/unreleased/25740-fix-new-branch-button-padding.yml new file mode 100644 index 00000000000..7da8f9357a7 --- /dev/null +++ b/changelogs/unreleased/25740-fix-new-branch-button-padding.yml @@ -0,0 +1,4 @@ +--- +title: Made the padding on the plus button in the breadcrumb menu even +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/dz-fix-route-rename.yml b/changelogs/unreleased/dz-fix-route-rename.yml new file mode 100644 index 00000000000..a649fb169a5 --- /dev/null +++ b/changelogs/unreleased/dz-fix-route-rename.yml @@ -0,0 +1,4 @@ +--- +title: Fix Route#rename_children behavior +merge_request: +author: diff --git a/changelogs/unreleased/fix-blame-500.yml b/changelogs/unreleased/fix-blame-500.yml new file mode 100644 index 00000000000..379d81aaa44 --- /dev/null +++ b/changelogs/unreleased/fix-blame-500.yml @@ -0,0 +1,4 @@ +--- +title: Fix blame 500 error on invalid path. +merge_request: 25761 +author: Jeff Stubler diff --git a/changelogs/unreleased/fix-import-export-build-token.yml b/changelogs/unreleased/fix-import-export-build-token.yml new file mode 100644 index 00000000000..622487e6829 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-build-token.yml @@ -0,0 +1,4 @@ +--- +title: Fix Import/Export duplicated builds error +merge_request: +author: diff --git a/changelogs/unreleased/fix-import-export-ee-services.yml b/changelogs/unreleased/fix-import-export-ee-services.yml new file mode 100644 index 00000000000..c0aacbc96f8 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-ee-services.yml @@ -0,0 +1,4 @@ +--- +title: Fix missing service error importing from EE to CE +merge_request: 8144 +author: diff --git a/changelogs/unreleased/fix-import-export-mr-error.yml b/changelogs/unreleased/fix-import-export-mr-error.yml new file mode 100644 index 00000000000..e1137bca131 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-mr-error.yml @@ -0,0 +1,4 @@ +--- +title: Fix Import/Export merge requests error while importing +merge_request: +author: diff --git a/changelogs/unreleased/fix-milestone-summary.yml b/changelogs/unreleased/fix-milestone-summary.yml deleted file mode 100644 index 3045a15054c..00000000000 --- a/changelogs/unreleased/fix-milestone-summary.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Displays milestone remaining days only when it's present -merge_request: -author: diff --git a/changelogs/unreleased/fix-yaml-variables.yml b/changelogs/unreleased/fix-yaml-variables.yml new file mode 100644 index 00000000000..3abff1e3b08 --- /dev/null +++ b/changelogs/unreleased/fix-yaml-variables.yml @@ -0,0 +1,4 @@ +--- +title: Convert CI YAML variables keys into strings +merge_request: 8088 +author: diff --git a/changelogs/unreleased/group-members-in-project-members-view.yml b/changelogs/unreleased/group-members-in-project-members-view.yml deleted file mode 100644 index 415e2b6b1e2..00000000000 --- a/changelogs/unreleased/group-members-in-project-members-view.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Shows group members in project members list -merge_request: -author: diff --git a/changelogs/unreleased/issue_24020.yml b/changelogs/unreleased/issue_24020.yml deleted file mode 100644 index 87310b75296..00000000000 --- a/changelogs/unreleased/issue_24020.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "fix display hook error message" -merge_request: 7775 -author: basyura diff --git a/changelogs/unreleased/issue_25030.yml b/changelogs/unreleased/issue_25030.yml deleted file mode 100644 index e18b8d6a79b..00000000000 --- a/changelogs/unreleased/issue_25030.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow branch names with dots on API endpoint -merge_request: -author: diff --git a/changelogs/unreleased/ldap_maint_task.yml b/changelogs/unreleased/ldap_maint_task.yml new file mode 100644 index 00000000000..8acffba0ce5 --- /dev/null +++ b/changelogs/unreleased/ldap_maint_task.yml @@ -0,0 +1,4 @@ +--- +title: Add LDAP Rake task to rename a provider +merge_request: 2181 +author: diff --git a/changelogs/unreleased/leave-project-btn.yml b/changelogs/unreleased/leave-project-btn.yml new file mode 100644 index 00000000000..2aa553d7b97 --- /dev/null +++ b/changelogs/unreleased/leave-project-btn.yml @@ -0,0 +1,4 @@ +--- +title: Move all action buttons to project header +merge_request: +author: diff --git a/changelogs/unreleased/pipeline-build-hitbox.yml b/changelogs/unreleased/pipeline-build-hitbox.yml new file mode 100644 index 00000000000..051b538a9a3 --- /dev/null +++ b/changelogs/unreleased/pipeline-build-hitbox.yml @@ -0,0 +1,4 @@ +--- +title: Make CI badge hitboxes match parent +merge_request: +author: diff --git a/changelogs/unreleased/timeago-perf-fix.yml b/changelogs/unreleased/timeago-perf-fix.yml deleted file mode 100644 index 265e7db29a9..00000000000 --- a/changelogs/unreleased/timeago-perf-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed timeago re-rendering every timeago -merge_request: -author: diff --git a/changelogs/unreleased/unescape-relative-path.yml b/changelogs/unreleased/unescape-relative-path.yml deleted file mode 100644 index 755b0379a16..00000000000 --- a/changelogs/unreleased/unescape-relative-path.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Avoid escaping relative links in Markdown twice -merge_request: 7940 -author: winniehell diff --git a/changelogs/unreleased/zj-slack-slash-commands.yml b/changelogs/unreleased/zj-slack-slash-commands.yml new file mode 100644 index 00000000000..9f4c8681ad0 --- /dev/null +++ b/changelogs/unreleased/zj-slack-slash-commands.yml @@ -0,0 +1,4 @@ +--- +title: Refactor presenters ChatCommands +merge_request: 7846 +author: diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 19e001854d2..be95d788850 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -1,26 +1,50 @@ class Gitlab::Seeder::Pipelines STAGES = %w[build test deploy notify] BUILDS = [ - { name: 'build:linux', stage: 'build', status: :success }, - { name: 'build:osx', stage: 'build', status: :success }, - { name: 'rspec:linux 0 3', stage: 'test', status: :success }, - { name: 'rspec:linux 1 3', stage: 'test', status: :success }, - { name: 'rspec:linux 2 3', stage: 'test', status: :success }, - { name: 'rspec:windows 0 3', stage: 'test', status: :success }, - { name: 'rspec:windows 1 3', stage: 'test', status: :success }, - { name: 'rspec:windows 2 3', stage: 'test', status: :success }, - { name: 'rspec:windows 2 3', stage: 'test', status: :success }, - { name: 'rspec:osx', stage: 'test', status_event: :success }, - { name: 'spinach:linux', stage: 'test', status: :success }, - { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, - { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending }, - { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, - { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, - { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { on_stop: 'stop staging' } } }, - { name: 'stop staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped }, - { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, + # build stage + { name: 'build:linux', stage: 'build', status: :success, + queued_at: 10.hour.ago, started_at: 9.hour.ago, finished_at: 8.hour.ago }, + { name: 'build:osx', stage: 'build', status: :success, + queued_at: 10.hour.ago, started_at: 10.hour.ago, finished_at: 9.hour.ago }, + + # test stage + { name: 'rspec:linux 0 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:linux 1 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:linux 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 0 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 1 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:osx', stage: 'test', status_event: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:linux', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + + # deploy stage + { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, + options: { environment: { action: 'start', on_stop: 'stop staging' } }, + queued_at: 7.hour.ago, started_at: 6.hour.ago, finished_at: 4.hour.ago }, + { name: 'stop staging', stage: 'deploy', environment: 'staging', + when: 'manual', status: :skipped }, + { name: 'production', stage: 'deploy', environment: 'production', + when: 'manual', status: :skipped }, + + # notify stage { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] + EXTERNAL_JOBS = [ + { name: 'jenkins', stage: 'test', status: :success, + queued_at: 7.hour.ago, started_at: 6.hour.ago, finished_at: 4.hour.ago }, + ] def initialize(project) @project = project @@ -30,11 +54,12 @@ class Gitlab::Seeder::Pipelines pipelines.each do |pipeline| begin BUILDS.each { |opts| build_create!(pipeline, opts) } - commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success) + EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) } print '.' rescue ActiveRecord::RecordInvalid print 'F' ensure + pipeline.update_duration pipeline.update_status end end diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index d1d2fed4861..c8b5434c068 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -74,24 +74,5 @@ Example output: The LDAP check Rake task will test the bind_dn and password credentials (if configured) and will list a sample of LDAP users. This task is also -executed as part of the `gitlab:check` task, but can run independently -using the command below. - -**Omnibus Installation** - -``` -sudo gitlab-rake gitlab:ldap:check -``` - -**Source Installation** - -```bash -sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production -``` - -By default, the task will return a sample of 100 LDAP users. Change this -limit by passing a number to the check task: - -```bash -rake gitlab:ldap:check[50] -``` +executed as part of the `gitlab:check` task, but can run independently. +See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details. diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md new file mode 100644 index 00000000000..91fc0133d56 --- /dev/null +++ b/doc/administration/raketasks/ldap.md @@ -0,0 +1,120 @@ +# LDAP Rake Tasks + +## Check + +The LDAP check Rake task will test the `bind_dn` and `password` credentials +(if configured) and will list a sample of LDAP users. This task is also +executed as part of the `gitlab:check` task, but can run independently +using the command below. + +**Omnibus Installation** + +``` +sudo gitlab-rake gitlab:ldap:check +``` + +**Source Installation** + +```bash +sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production +``` + +------ + +By default, the task will return a sample of 100 LDAP users. Change this +limit by passing a number to the check task: + +```bash +rake gitlab:ldap:check[50] +``` + +## Rename a provider + +If you change the LDAP server ID in `gitlab.yml` or `gitlab.rb` you will need +to update all user identities or users will be unable to sign in. Input the +old and new provider and this task will update all matching identities in the +database. + +`old_provider` and `new_provider` are derived from the prefix `ldap` plus the +LDAP server ID from the configuration file. For example, in `gitlab.yml` or +`gitlab.rb` you may see LDAP configuration like this: + +```yaml +main: + label: 'LDAP' + host: '_your_ldap_server' + port: 389 + uid: 'sAMAccountName' + ... +``` + +`main` is the LDAP server ID. Together, the unique provider is `ldapmain`. + +> **Warning**: If you input an incorrect new provider users will be unable +to sign in. If this happens, run the task again with the incorrect provider +as the `old_provider` and the correct provider as the `new_provider`. + +**Omnibus Installation** + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[old_provider,new_provider] +``` + +**Source Installation** + +```bash +bundle exec rake gitlab:ldap:rename_provider[old_provider,new_provider] RAILS_ENV=production +``` + +### Example + +Consider beginning with the default server ID `main` (full provider `ldapmain`). +If we change `main` to `mycompany`, the `new_provider` is `ldapmycompany`. +To rename all user identities run the following command: + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[ldapmain,ldapmycompany] +``` + +Example output: + +``` +100 users with provider 'ldapmain' will be updated to 'ldapmycompany'. +If the new provider is incorrect, users will be unable to sign in. +Do you want to continue (yes/no)? yes + +User identities were successfully updated +``` + +### Other options + +If you do not specify an `old_provider` and `new_provider` you will be prompted +for them: + +**Omnibus Installation** + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider +``` + +**Source Installation** + +```bash +bundle exec rake gitlab:ldap:rename_provider RAILS_ENV=production +``` + +**Example output:** + +``` +What is the old provider? Ex. 'ldapmain': ldapmain +What is the new provider? Ex. 'ldapcustom': ldapmycompany +``` + +------ + +This tasks also accepts the `force` environment variable which will skip the +confirmation dialog: + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[old_provider,new_provider] force=yes +``` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index dd8e1078c60..7158b2e7895 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1056,7 +1056,7 @@ variables: GET_SOURCES_ATTEMPTS: "3" ``` -You can set the them in the global [`variables`](#variables) section or the [`variables`](#job-variables) +You can set them in the global [`variables`](#variables) section or the [`variables`](#job-variables) section for individual jobs. ## Shallow cloning diff --git a/doc/development/performance.md b/doc/development/performance.md index 5c43ae7b79a..f936a49a2aa 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -37,7 +37,7 @@ graphs/dashboards. GitLab provides built-in tools to aid the process of improving performance: * [Sherlock](profiling.md#sherlock) -* [GitLab Performance Monitoring](../administration/monitoring/performance/monitoring.md) +* [GitLab Performance Monitoring](../administration/monitoring/performance/introduction.md) * [Request Profiling](../administration/monitoring/performance/request_profiling.md) GitLab employees can use GitLab.com's performance monitoring systems located at diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 5df6e103f42..1dfc985eaea 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -5,7 +5,7 @@ Bitbucket.org account. ## Overview -You can set up Bitbucket.org as an OAuth provider so that you can use your +You can set up Bitbucket.org as an OAuth2 provider so that you can use your credentials to authenticate into GitLab or import your projects from Bitbucket.org. @@ -50,6 +50,7 @@ you to use. Repositories: Read Pull Requests: Read Issues: Read + Wiki: Read and Write ``` ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png) diff --git a/doc/integration/img/bitbucket_oauth_settings_page.png b/doc/integration/img/bitbucket_oauth_settings_page.png index 21ce82a6074..3e6dea6cfe9 100644 Binary files a/doc/integration/img/bitbucket_oauth_settings_page.png and b/doc/integration/img/bitbucket_oauth_settings_page.png differ diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md index a49c43b8ef2..2b81ebc9c59 100644 --- a/doc/raketasks/README.md +++ b/doc/raketasks/README.md @@ -4,7 +4,8 @@ - [Check](check.md) - [Cleanup](cleanup.md) - [Features](features.md) -- [Maintenance](maintenance.md) and self-checks +- [LDAP Maintenance](../administration/raketasks/ldap.md) +- [General Maintenance](maintenance.md) and self-checks - [User management](user_management.md) - [Webhooks](web_hooks.md) - [Import](import.md) of git repositories in bulk diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md index 4eacab0c890..8d4bfd913bd 100644 --- a/doc/update/8.14-to-8.15.md +++ b/doc/update/8.14-to-8.15.md @@ -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.1.0 +sudo -u git -H git checkout v4.1.1 ``` ### 6. Update gitlab-workhorse diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md index b6d47e5afa2..97380bce172 100644 --- a/doc/workflow/importing/import_projects_from_bitbucket.md +++ b/doc/workflow/importing/import_projects_from_bitbucket.md @@ -17,6 +17,7 @@ to enable this if not already. - the pull requests (GitLab 8.4+) - the pull request comments (GitLab 8.15+) - the milestones (GitLab 8.15+) + - the wiki (GitLab 8.15+) - References to pull requests and issues are preserved (GitLab 8.7+) - Repository public access is retained. If a repository is private in Bitbucket it will be created as private in GitLab as well. diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature deleted file mode 100644 index 5c1dd7531c1..00000000000 --- a/features/admin/appearance.feature +++ /dev/null @@ -1,37 +0,0 @@ -Feature: Admin Appearance - Scenario: Create new appearance - Given I sign in as an admin - And I visit admin appearance page - When submit form with new appearance - Then I should be redirected to admin appearance page - And I should see newly created appearance - - Scenario: Preview appearance - Given application has custom appearance - And I sign in as an admin - When I visit admin appearance page - And I click preview button - Then I should see a customized appearance - - Scenario: Custom sign-in page - Given application has custom appearance - When I visit login page - Then I should see a customized appearance - - Scenario: Appearance logo - Given application has custom appearance - And I sign in as an admin - And I visit admin appearance page - When I attach a logo - Then I should see a logo - And I remove the logo - Then I should see logo removed - - Scenario: Header logos - Given application has custom appearance - And I sign in as an admin - And I visit admin appearance page - When I attach header logos - Then I should see header logos - And I remove the header logos - Then I should see header logos removed diff --git a/features/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature deleted file mode 100644 index 4f9c651561e..00000000000 --- a/features/admin/broadcast_messages.feature +++ /dev/null @@ -1,33 +0,0 @@ -@admin -Feature: Admin Broadcast Messages - Background: - Given I sign in as an admin - And application already has a broadcast message - And I visit admin messages page - - Scenario: See broadcast messages list - Then I should see all broadcast messages - - Scenario: Create a customized broadcast message - When submit form with new customized broadcast message - Then I should be redirected to admin messages page - And I should see newly created broadcast message - Then I visit dashboard page - And I should see a customized broadcast message - - Scenario: Edit an existing broadcast message - When I edit an existing broadcast message - And I change the broadcast message text - Then I should be redirected to admin messages page - And I should see the updated broadcast message - - Scenario: Remove an existing broadcast message - When I remove an existing broadcast message - Then I should be redirected to admin messages page - And I should not see the removed broadcast message - - @javascript - Scenario: Live preview a customized broadcast message - When I visit admin messages page - And I enter a broadcast message with Markdown - Then I should see a live preview of the rendered broadcast message diff --git a/features/admin/labels.feature b/features/admin/labels.feature deleted file mode 100644 index 1af0e700bd4..00000000000 --- a/features/admin/labels.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Admin Issues Labels - Background: - Given I sign in as an admin - And I have labels: "bug", "feature", "enhancement" - Given I visit admin labels page - - Scenario: I should see labels list - Then I should see label 'bug' - And I should see label 'feature' - - Scenario: I create new label - Given I submit new label 'support' - Then I should see label 'support' - - Scenario: I edit label - Given I visit 'bug' label edit page - When I change label 'bug' to 'fix' - Then I should not see label 'bug' - Then I should see label 'fix' - - Scenario: I remove label - When I remove label 'bug' - Then I should not see label 'bug' - - @javascript - Scenario: I delete all labels - When I delete all labels - Then I should see labels help message - - Scenario: I create a label with invalid color - Given I visit admin new label page - When I submit new label with invalid color - Then I should see label color error message - - Scenario: I create a label that already exists - Given I visit admin new label page - When I submit new label 'bug' - Then I should see label exist error message diff --git a/features/project/service.feature b/features/project/service.feature index 3a7b8308524..cce5f58adec 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -37,11 +37,11 @@ Feature: Project Services And I fill Assembla settings Then I should see Assembla service settings saved - Scenario: Activate Slack service + Scenario: Activate Slack notifications service When I visit project "Shop" services page - And I click Slack service link - And I fill Slack settings - Then I should see Slack service settings saved + And I click Slack notifications service link + And I fill Slack notifications settings + Then I should see Slack Notifications service settings saved Scenario: Activate Pushover service When I visit project "Shop" services page diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb deleted file mode 100644 index 0d1be46d11d..00000000000 --- a/features/steps/admin/appearance.rb +++ /dev/null @@ -1,72 +0,0 @@ -class Spinach::Features::AdminAppearance < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - - step 'submit form with new appearance' do - fill_in 'appearance_title', with: 'MyCompany' - fill_in 'appearance_description', with: 'dev server' - click_button 'Save' - end - - step 'I should be redirected to admin appearance page' do - expect(current_path).to eq admin_appearances_path - expect(page).to have_content 'Appearance settings' - end - - step 'I should see newly created appearance' do - expect(page).to have_field('appearance_title', with: 'MyCompany') - expect(page).to have_field('appearance_description', with: 'dev server') - expect(page).to have_content 'Last edit' - end - - step 'I click preview button' do - click_link "Preview" - end - - step 'application has custom appearance' do - create(:appearance) - end - - step 'I should see a customized appearance' do - expect(page).to have_content appearance.title - expect(page).to have_content appearance.description - end - - step 'I attach a logo' do - attach_file(:appearance_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) - click_button 'Save' - end - - step 'I attach header logos' do - attach_file(:appearance_header_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) - click_button 'Save' - end - - step 'I should see a logo' do - expect(page).to have_xpath('//img[@src="/uploads/appearance/logo/1/dk.png"]') - end - - step 'I should see header logos' do - expect(page).to have_xpath('//img[@src="/uploads/appearance/header_logo/1/dk.png"]') - end - - step 'I remove the logo' do - click_link 'Remove logo' - end - - step 'I remove the header logos' do - click_link 'Remove header logo' - end - - step 'I should see logo removed' do - expect(page).not_to have_xpath('//img[@src="/uploads/appearance/logo/1/gitlab_logo.png"]') - end - - step 'I should see header logos removed' do - expect(page).not_to have_xpath('//img[@src="/uploads/appearance/header_logo/1/header_logo_light.png"]') - end - - def appearance - Appearance.last - end -end diff --git a/features/steps/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb deleted file mode 100644 index af2b4a29313..00000000000 --- a/features/steps/admin/broadcast_messages.rb +++ /dev/null @@ -1,66 +0,0 @@ -class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - - step 'application already has a broadcast message' do - FactoryGirl.create(:broadcast_message, :expired, message: "Migration to new server") - end - - step 'I should see all broadcast messages' do - expect(page).to have_content "Migration to new server" - end - - step 'I should be redirected to admin messages page' do - expect(current_path).to eq admin_broadcast_messages_path - end - - step 'I should see newly created broadcast message' do - expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' - end - - step 'submit form with new customized broadcast message' do - fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**' - fill_in 'broadcast_message_color', with: '#f2dede' - fill_in 'broadcast_message_font', with: '#b94a48' - select Date.today.next_year.year, from: "broadcast_message_ends_at_1i" - click_button "Add broadcast message" - end - - step 'I should see a customized broadcast message' do - expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' - expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST' - expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) - end - - step 'I edit an existing broadcast message' do - click_link 'Edit' - end - - step 'I change the broadcast message text' do - fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW' - click_button 'Update broadcast message' - end - - step 'I should see the updated broadcast message' do - expect(page).to have_content "Application update RIGHT NOW" - end - - step 'I remove an existing broadcast message' do - click_link 'Remove' - end - - step 'I should not see the removed broadcast message' do - expect(page).not_to have_content 'Migration to new server' - end - - step 'I enter a broadcast message with Markdown' do - fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" - end - - step 'I should see a live preview of the rendered broadcast message' do - page.within('.broadcast-message-preview') do - expect(page).to have_selector('strong', text: 'Markdown') - expect(page).to have_selector('img.emoji') - end - end -end diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb deleted file mode 100644 index 55ddcc25085..00000000000 --- a/features/steps/admin/labels.rb +++ /dev/null @@ -1,117 +0,0 @@ -class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - step 'I visit \'bug\' label edit page' do - visit edit_admin_label_path(bug_label) - end - - step 'I visit admin new label page' do - visit new_admin_label_path - end - - step 'I visit admin labels page' do - visit admin_labels_path - end - - step 'I remove label \'bug\'' do - page.within "#label_#{bug_label.id}" do - click_link 'Delete' - end - end - - step 'I have labels: "bug", "feature", "enhancement"' do - ["bug", "feature", "enhancement"].each do |title| - Label.create(title: title, template: true) - end - end - - step 'I delete all labels' do - page.within '.labels' do - page.all('.btn-remove').each do |remove| - remove.click - sleep 0.05 - end - end - end - - step 'I should see labels help message' do - page.within '.labels' do - expect(page).to have_content 'There are no labels yet' - end - end - - step 'I submit new label \'support\'' do - visit new_admin_label_path - fill_in 'Title', with: 'support' - fill_in 'Background color', with: '#F95610' - click_button 'Save' - end - - step 'I submit new label \'bug\'' do - visit new_admin_label_path - fill_in 'Title', with: 'bug' - fill_in 'Background color', with: '#F95610' - click_button 'Save' - end - - step 'I submit new label with invalid color' do - visit new_admin_label_path - fill_in 'Title', with: 'support' - fill_in 'Background color', with: '#12' - click_button 'Save' - end - - step 'I should see label exist error message' do - page.within '.label-form' do - expect(page).to have_content 'Title has already been taken' - end - end - - step 'I should see label color error message' do - page.within '.label-form' do - expect(page).to have_content 'Color must be a valid color code' - end - end - - step 'I should see label \'feature\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'feature' - end - end - - step 'I should see label \'bug\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'bug' - end - end - - step 'I should not see label \'bug\'' do - page.within '.manage-labels-list' do - expect(page).not_to have_content 'bug' - end - end - - step 'I should see label \'support\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'support' - end - end - - step 'I change label \'bug\' to \'fix\'' do - fill_in 'Title', with: 'fix' - fill_in 'Background color', with: '#F15610' - click_button 'Save' - end - - step 'I should see label \'fix\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'fix' - end - end - - def bug_label - Label.templates.find_or_create_by(title: 'bug') - end -end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index bd6466f3686..a4d29770922 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -137,17 +137,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps expect(find_field('Colorize messages').value).to eq '1' end - step 'I click Slack service link' do - click_link 'Slack' + step 'I click Slack notifications service link' do + click_link 'Slack notifications' end - step 'I fill Slack settings' do + step 'I fill Slack notifications settings' do check 'Active' fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' end - step 'I should see Slack service settings saved' do + step 'I should see Slack Notifications service settings saved' do expect(find_field('Webhook').value).to eq 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index a78d0a775ba..15b81fa529b 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -195,10 +195,6 @@ module SharedPaths visit admin_groups_path end - step 'I visit admin appearance page' do - visit admin_appearances_path - end - step 'I visit admin teams page' do visit admin_teams_path end diff --git a/lib/api/services.rb b/lib/api/services.rb index 59232c84c24..aa97f6af0b2 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -378,7 +378,6 @@ module API desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' }, ], - 'mattermost-slash-commands' => [ { required: true, @@ -387,6 +386,14 @@ module API desc: 'The Mattermost token' } ], + 'slack-slash-commands' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Slack token' + } + ], 'pipelines-email' => [ { required: true, diff --git a/lib/bitbucket/representation/repo.rb b/lib/bitbucket/representation/repo.rb index 8969ecd1c19..423eff8f2a5 100644 --- a/lib/bitbucket/representation/repo.rb +++ b/lib/bitbucket/representation/repo.rb @@ -51,6 +51,10 @@ module Bitbucket raw['scm'] == 'git' end + def has_wiki? + raw['has_wiki'] + end + def visibility_level if raw['is_private'] Gitlab::VisibilityLevel::PRIVATE diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index ed87a2603e8..142bce82286 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -41,7 +41,7 @@ module Ci put ":id" do authenticate_runner! build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id]) - forbidden!('Build has been erased!') if build.erased? + validate_build!(build) update_runner_info @@ -71,9 +71,7 @@ module Ci # PATCH /builds/:id/trace.txt patch ":id/trace.txt" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) - forbidden!('Build has been erased!') if build.erased? + authenticate_build!(build) error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range') content_range = request.headers['Content-Range'] @@ -104,8 +102,7 @@ module Ci Gitlab::Workhorse.verify_api_request!(headers) not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) forbidden!('build is not running') unless build.running? if params[:filesize] @@ -142,10 +139,8 @@ module Ci require_gitlab_workhorse! not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) forbidden!('Build is not running!') unless build.running? - forbidden!('Build has been erased!') if build.erased? artifacts_upload_path = ArtifactUploader.artifacts_upload_path artifacts = uploaded_file(:file, artifacts_upload_path) @@ -176,8 +171,7 @@ module Ci # GET /builds/:id/artifacts get ":id/artifacts" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) artifacts_file = build.artifacts_file unless artifacts_file.file_storage? @@ -202,8 +196,7 @@ module Ci # DELETE /builds/:id/artifacts delete ":id/artifacts" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) build.erase_artifacts! end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index e608f5f6cad..31fbd1da108 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -13,8 +13,19 @@ module Ci forbidden! unless current_runner end - def authenticate_build_token!(build) - forbidden! unless build_token_valid?(build) + def authenticate_build!(build) + validate_build!(build) do + forbidden! unless build_token_valid?(build) + end + end + + def validate_build!(build) + not_found! unless build + + yield if block_given? + + forbidden!('Project has been deleted!') unless build.project + forbidden!('Build has been erased!') if build.erased? end def runner_registration_token_valid? diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index fef652cb975..7463bd719d5 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -118,7 +118,7 @@ module Ci .merge(job_variables(name)) variables.map do |key, value| - { key: key, value: value, public: true } + { key: key.to_s, value: value, public: true } end end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 7d2f92d577a..44323b47dca 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -1,6 +1,8 @@ module Gitlab module BitbucketImport class Importer + include Gitlab::ShellAdapter + LABELS = [{ title: 'bug', color: '#FF0000' }, { title: 'enhancement', color: '#428BCA' }, { title: 'proposal', color: '#69D100' }, @@ -18,6 +20,7 @@ module Gitlab end def execute + import_wiki import_issues import_pull_requests handle_errors @@ -55,6 +58,16 @@ module Gitlab @repo ||= client.repo(project.import_source) end + def import_wiki + return if project.wiki.repository_exists? + + path_with_namespace = "#{project.path_with_namespace}.wiki" + import_url = project.import_url.sub(/\.git\z/, ".git/wiki") + gitlab_shell.import_repository(project.repository_storage_path, path_with_namespace, import_url) + rescue StandardError => e + errors << { type: :wiki, errors: e.message } + end + def import_issues return unless repo.issues_enabled? diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index eb03882ab26..d94f70fd1fb 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -22,9 +22,16 @@ module Gitlab import_type: 'bitbucket', import_source: repo.full_name, import_url: repo.clone_url(session_data[:token]), - import_data: { credentials: session_data } + import_data: { credentials: session_data }, + skip_wiki: skip_wiki ).execute end + + private + + def skip_wiki + repo.has_wiki? + end end end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 25da8474e95..4fe53ce93a9 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -42,6 +42,10 @@ module Gitlab def find_by_iid(iid) collection.find_by(iid: iid) end + + def presenter + Gitlab::ChatCommands::Presenter.new + end end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index b0d3fdbc48a..145086755e4 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -22,8 +22,6 @@ module Gitlab end end - private - def match_command match = nil service = available_commands.find do |klass| @@ -33,6 +31,8 @@ module Gitlab [service, match] end + private + def help_messages available_commands.map(&:help_message) end @@ -48,15 +48,15 @@ module Gitlab end def help(messages) - Mattermost::Presenter.help(messages, params[:command]) + presenter.help(messages, params[:command]) end def access_denied - Mattermost::Presenter.access_denied + presenter.access_denied end def present(resource) - Mattermost::Presenter.present(resource) + presenter.present(resource) end end end diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index 0eed1fce0dc..6bb854dc080 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -4,7 +4,7 @@ module Gitlab include Gitlab::Routing.url_helpers def self.match(text) - /\Adeploy\s+(?.*)\s+to+\s+(?.*)\z/.match(text) + /\Adeploy\s+(?\S+.*)\s+to+\s+(?\S+.*)\z/.match(text) end def self.help_message diff --git a/lib/mattermost/presenter.rb b/lib/gitlab/chat_commands/presenter.rb similarity index 95% rename from lib/mattermost/presenter.rb rename to lib/gitlab/chat_commands/presenter.rb index 67eda983a74..caceaa25391 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -1,7 +1,7 @@ -module Mattermost - class Presenter - class << self - include Gitlab::Routing.url_helpers +module Gitlab + module ChatCommands + class Presenter + include Gitlab::Routing def authorize_chat_name(url) message = if url @@ -64,7 +64,7 @@ module Mattermost def single_resource(resource) return error(resource) if resource.errors.any? || !resource.persisted? - message = "### #{title(resource)}" + message = "#{title(resource)}:" message << "\n\n#{resource.description}" if resource.try(:description) in_channel_response(message) diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 5c506e6d59f..1bf949c96dd 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -17,6 +17,10 @@ module Gitlab 'icon_status_manual' end + def group + 'manual' + end + def has_action? can?(user, :update_build, subject) end diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index f8ffa95cde4..e1dfdb76d41 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -17,6 +17,10 @@ module Gitlab 'icon_status_manual' end + def group + 'manual' + end + def has_action? can?(user, :update_build, subject) end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 46fef8262c1..73b6ab5a635 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -22,15 +22,8 @@ module Gitlab raise NotImplementedError end - # Deprecation warning: this method is here because we need to maintain - # backwards compatibility with legacy statuses. We often do something - # like "ci-status ci-status-#{status}" to set CSS class. - # - # `to_s` method should be renamed to `group` at some point, after - # phasing legacy satuses out. - # - def to_s - self.class.name.demodulize.downcase.underscore + def group + self.class.name.demodulize.underscore end def has_details? diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb index a7c98f9e909..24bf8b869e0 100644 --- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb +++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb @@ -17,7 +17,7 @@ module Gitlab 'icon_status_warning' end - def to_s + def group 'success_with_warnings' end diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 25e9d619697..79dd0cf7df2 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -22,7 +22,7 @@ module Gitlab def valid? environment_variables.all? do |(name, value)| - value.start_with?(project.repository.path_to_repo) + value.to_s.start_with?(project.repository.path_to_repo) end end @@ -35,7 +35,7 @@ module Gitlab end def environment_variables - @environment_variables ||= env.slice(*ALLOWED_VARIABLES) + @environment_variables ||= env.slice(*ALLOWED_VARIABLES).compact end end end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index c551321c18d..cda6ddf0443 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -120,7 +120,7 @@ module Gitlab members_mapper: members_mapper, user: @user, project_id: restored_project.id) - end + end.compact relation_hash_list.is_a?(Array) ? relation_array : relation_array.first end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index a0e80fccad9..65b229ca8ff 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -14,7 +14,7 @@ module Gitlab priorities: :label_priorities, label: :project_label }.freeze - USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id].freeze + USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id].freeze PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze @@ -40,6 +40,8 @@ module Gitlab # the relation_hash, updating references with new object IDs, mapping users using # the "members_mapper" object, also updating notes if required. def create + return nil if unknown_service? + setup_models generate_imported_object @@ -99,6 +101,8 @@ module Gitlab def generate_imported_object if BUILD_MODELS.include?(@relation_name) # call #trace= method after assigning the other attributes trace = @relation_hash.delete('trace') + @relation_hash.delete('token') + imported_object do |object| object.trace = trace object.commit_id = nil @@ -215,6 +219,11 @@ module Gitlab existing_object end end + + def unknown_service? + @relation_name == :services && parsed_relation_hash['type'] && + !Object.const_defined?(parsed_relation_hash['type']) + end end end end diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serialize/ci/variables.rb new file mode 100644 index 00000000000..3a9443bfcd9 --- /dev/null +++ b/lib/gitlab/serialize/ci/variables.rb @@ -0,0 +1,27 @@ +module Gitlab + module Serialize + module Ci + # This serializer could make sure our YAML variables' keys and values + # are always strings. This is more for legacy build data because + # from now on we convert them into strings before saving to database. + module Variables + extend self + + def load(string) + return unless string + + object = YAML.safe_load(string, [Symbol]) + + object.map do |variable| + variable[:key] = variable[:key].to_s + variable + end + end + + def dump(object) + YAML.dump(object) + end + end + end + end +end diff --git a/lib/tasks/gitlab/ldap.rake b/lib/tasks/gitlab/ldap.rake new file mode 100644 index 00000000000..c66a2a263dc --- /dev/null +++ b/lib/tasks/gitlab/ldap.rake @@ -0,0 +1,40 @@ +namespace :gitlab do + namespace :ldap do + desc 'GitLab | LDAP | Rename provider' + task :rename_provider, [:old_provider, :new_provider] => :environment do |_, args| + old_provider = args[:old_provider] || + prompt('What is the old provider? Ex. \'ldapmain\': '.color(:blue)) + new_provider = args[:new_provider] || + prompt('What is the new provider ID? Ex. \'ldapcustom\': '.color(:blue)) + puts '' # Add some separation in the output + + identities = Identity.where(provider: old_provider) + identity_count = identities.count + + if identities.empty? + puts "Found no user identities with '#{old_provider}' provider." + puts 'Please check the provider name and try again.' + exit 1 + end + + plural_id_count = ActionController::Base.helpers.pluralize(identity_count, 'user') + + unless ENV['force'] == 'yes' + puts "#{plural_id_count} with provider '#{old_provider}' will be updated to '#{new_provider}'" + puts 'If the new provider is incorrect, users will be unable to sign in' + ask_to_continue + puts '' + end + + updated_count = identities.update_all(provider: new_provider) + + if updated_count == identity_count + puts 'User identities were successfully updated'.color(:green) + else + plural_updated_count = ActionController::Base.helpers.pluralize(updated_count, 'user') + puts 'Some user identities could not be updated'.color(:red) + puts "Successfully updated #{plural_updated_count} out of #{plural_id_count} total" + end + end + end +end diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index 25f06299a29..4402ca43c65 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -25,5 +25,10 @@ describe Projects::BlameController do let(:id) { 'master/files/ruby/popen.rb' } it { is_expected.to respond_with(:success) } end + + context "invalid file" do + let(:id) { 'master/files/ruby/missing_file.rb'} + it { expect(response).to have_http_status(404) } + end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 62466c06194..0397d5d4001 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -22,7 +22,7 @@ FactoryGirl.define do yaml_variables do [ - { key: :DB_NAME, value: 'postgres', public: true } + { key: 'DB_NAME', value: 'postgres', public: true } ] end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb new file mode 100644 index 00000000000..96d715ef383 --- /dev/null +++ b/spec/features/admin/admin_appearance_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +feature 'Admin Appearance', feature: true do + let!(:appearance) { create(:appearance) } + + scenario 'Create new appearance' do + login_as :admin + visit admin_appearances_path + + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + click_button 'Save' + + expect(current_path).to eq admin_appearances_path + expect(page).to have_content 'Appearance settings' + + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_content 'Last edit' + end + + scenario 'Preview appearance' do + login_as :admin + + visit admin_appearances_path + click_link "Preview" + + expect_page_has_custom_appearance(appearance) + end + + scenario 'Custom sign-in page' do + visit new_user_session_path + expect_page_has_custom_appearance(appearance) + end + + scenario 'Appearance logo' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(logo_selector) + + click_link 'Remove logo' + expect(page).not_to have_css(logo_selector) + end + + scenario 'Header logos' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_header_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(header_logo_selector) + + click_link 'Remove header logo' + expect(page).not_to have_css(header_logo_selector) + end + + def expect_page_has_custom_appearance(appearance) + expect(page).to have_content appearance.title + expect(page).to have_content appearance.description + end + + def logo_selector + '//img[@src^="/uploads/appearance/logo"]' + end + + def header_logo_selector + '//img[@src^="/uploads/appearance/header_logo"]' + end + + def logo_fixture + Rails.root.join('spec', 'fixtures', 'dk.png') + end +end diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb new file mode 100644 index 00000000000..bc957ec72e1 --- /dev/null +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature 'Admin Broadcast Messages', feature: true do + before do + login_as :admin + create(:broadcast_message, :expired, message: 'Migration to new server') + visit admin_broadcast_messages_path + end + + scenario 'See broadcast messages list' do + expect(page).to have_content 'Migration to new server' + end + + scenario 'Create a customized broadcast message' do + fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**' + fill_in 'broadcast_message_color', with: '#f2dede' + fill_in 'broadcast_message_font', with: '#b94a48' + select Date.today.next_year.year, from: 'broadcast_message_ends_at_1i' + click_button 'Add broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' + expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST' + expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) + end + + scenario 'Edit an existing broadcast message' do + click_link 'Edit' + fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW' + click_button 'Update broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update RIGHT NOW' + end + + scenario 'Remove an existing broadcast message' do + click_link 'Remove' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).not_to have_content 'Migration to new server' + end + + scenario 'Live preview a customized broadcast message', js: true do + fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" + + page.within('.broadcast-message-preview') do + expect(page).to have_selector('strong', text: 'Markdown') + expect(page).to have_selector('img.emoji') + end + end +end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb new file mode 100644 index 00000000000..eaa42aad0a7 --- /dev/null +++ b/spec/features/admin/admin_labels_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +RSpec.describe 'admin issues labels' do + include WaitForAjax + + let!(:bug_label) { Label.create(title: 'bug', template: true) } + let!(:feature_label) { Label.create(title: 'feature', template: true) } + + before do + login_as :admin + end + + describe 'list' do + before do + visit admin_labels_path + end + + it 'renders labels list' do + page.within '.manage-labels-list' do + expect(page).to have_content('bug') + expect(page).to have_content('feature') + end + end + + it 'deletes label' do + page.within "#label_#{bug_label.id}" do + click_link 'Delete' + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + end + end + + it 'deletes all labels', js: true do + page.within '.labels' do + page.all('.btn-remove').each do |remove| + wait_for_ajax + remove.click + end + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + expect(page).not_to have_content('feature_label') + end + end + end + + describe 'create' do + before do + visit new_admin_label_path + end + + it 'creates new label' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('support') + end + end + + it 'does not creates label with invalid color' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#12' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content('Color must be a valid color code') + end + end + + it 'does not creates label if label already exists' do + fill_in 'Title', with: 'bug' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content 'Title has already been taken' + end + end + end + + describe 'edit' do + it 'changes bug label' do + visit edit_admin_label_path(bug_label) + + fill_in 'Title', with: 'fix' + fill_in 'Background color', with: '#F15610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('fix') + end + end + end +end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 8cd66f189be..47fa2f14307 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -17,9 +17,9 @@ feature 'Admin updates settings', feature: true do expect(page).to have_content "Application settings saved successfully" end - scenario 'Change Slack Service template settings' do + scenario 'Change Slack Notifications Service template settings' do click_link 'Service Templates' - click_link 'Slack' + click_link 'Slack notifications' fill_in 'Webhook', with: 'http://localhost' fill_in 'Username', with: 'test_user' fill_in 'service_push_channel', with: '#test_channel' @@ -30,7 +30,7 @@ feature 'Admin updates settings', feature: true do expect(page).to have_content 'Application settings saved successfully' - click_link 'Slack' + click_link 'Slack notifications' page.all('input[type=checkbox]').each do |checkbox| expect(checkbox).to be_checked diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 0a77eaa123c..1210e2745db 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -19,7 +19,7 @@ describe "Pipelines", feature: true, js: true do @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') end @@ -41,37 +41,34 @@ describe "Pipelines", feature: true, js: true 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 + page.within('#ci-badge-deploy') do expect(page).to have_selector('.ci-status-icon-running') - expect(page).to have_content('deploy') - end - - page.within('a[data-title="deploy - running"] + .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-ban') + expect(page).to have_content('deploy') end end it 'should be possible to cancel the running build' do - find('a[data-title="deploy - running"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-deploy .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Cancel running') end end 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 + it 'shows the success icon and a retry action for the successful build' do + page.within('#ci-badge-build') do expect(page).to have_selector('.ci-status-icon-success') expect(page).to have_content('build') end - page.within('a[data-title="build - passed"] + .ci-action-icon-container') do + page.within('#ci-badge-build .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-refresh') end end it 'should be possible to retry the success build' do - find('a[data-title="build - passed"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-build .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Retry build') end @@ -79,18 +76,18 @@ describe "Pipelines", feature: true, js: true 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 + page.within('#ci-badge-test') do expect(page).to have_selector('.ci-status-icon-failed') expect(page).to have_content('test') end - page.within('a[data-title="test - failed"] + .ci-action-icon-container') do + page.within('#ci-badge-test .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-refresh') end end it 'should be possible to retry the failed build' do - find('a[data-title="test - failed"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-test .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Retry build') end @@ -98,18 +95,18 @@ describe "Pipelines", feature: true, js: true 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') + page.within('#ci-badge-manual-build') do + expect(page).to have_selector('.ci-status-icon-manual') expect(page).to have_content('manual') end - page.within('a[data-title="manual build - manual play action"] + .ci-action-icon-container') do + page.within('#ci-badge-manual-build .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-play') end end it 'should be possible to play the manual build' do - find('a[data-title="manual build - manual play action"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-manual-build .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Play build') end @@ -167,7 +164,7 @@ describe "Pipelines", feature: true, js: true do @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') end diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb new file mode 100644 index 00000000000..32b32f7ae8e --- /dev/null +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Slack slash commands', feature: true do + include WaitForAjax + + given(:user) { create(:user) } + given(:project) { create(:project) } + given(:service) { project.create_slack_slash_commands_service } + + background do + project.team << [user, :master] + login_as(user) + end + + scenario 'user visits the slack slash command config page and shows a help message', js: true do + visit edit_namespace_project_service_path(project.namespace, project, service) + + wait_for_ajax + + expect(page).to have_content('This service allows GitLab users to perform common') + end + + scenario 'shows the token after saving' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + fill_in 'service_token', with: 'token' + click_on 'Save' + + value = find_field('service_token').value + + expect(value).to eq('token') + end + + scenario 'shows the correct trigger url' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + value = find_field('url').value + expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") + end +end diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml index deca50ceaa7..c0b5ab4411e 100644 --- a/spec/javascripts/fixtures/pipeline_graph.html.haml +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -8,8 +8,7 @@ %ul %li.build .curve - .build-content - %a - %svg - .ci-status-text - stop_review + %a + %svg + .ci-status-text + stop_review diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb new file mode 100644 index 00000000000..adcd978e1b3 --- /dev/null +++ b/spec/lib/bitbucket/representation/repo_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Bitbucket::Representation::Repo do + describe '#has_wiki?' do + it { expect(described_class.new({ 'has_wiki' => false }).has_wiki?).to be_falsey } + it { expect(described_class.new({ 'has_wiki' => true }).has_wiki?).to be_truthy } + end + + describe '#name' do + it { expect(described_class.new({ 'name' => 'test' }).name).to eq('test') } + end + + describe '#valid?' do + it { expect(described_class.new({ 'scm' => 'hg' }).valid?).to be_falsey } + it { expect(described_class.new({ 'scm' => 'git' }).valid?).to be_truthy } + end + + describe '#full_name' do + it { expect(described_class.new({ 'full_name' => 'test_full' }).full_name).to eq('test_full') } + end + + describe '#description' do + it { expect(described_class.new({ 'description' => 'desc' }).description).to eq('desc') } + end + + describe '#issues_enabled?' do + it { expect(described_class.new({ 'has_issues' => false }).issues_enabled?).to be_falsey } + it { expect(described_class.new({ 'has_issues' => true }).issues_enabled?).to be_truthy } + end + + describe '#owner_and_slug' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner_and_slug).to eq(['ben', 'test']) } + end + + describe '#owner' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner).to eq('ben') } + end + + describe '#slug' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).slug).to eq('test') } + end + + describe '#clone_url' do + it 'builds url' do + data = { 'links' => { 'clone' => [ { 'name' => 'https', 'href' => 'https://bibucket.org/test/test.git' }] } } + expect(described_class.new(data).clone_url('abc')).to eq('https://x-token-auth:abc@bibucket.org/test/test.git') + end + end +end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ff5dcc06ab3..62d68721574 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -483,7 +483,7 @@ module Ci context 'when global variables are defined' do let(:variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end let(:config) do { @@ -495,18 +495,18 @@ module Ci it 'returns global variables' do expect(subject).to contain_exactly( - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end context 'when job and global variables are defined' do let(:global_variables) do - { VAR1: 'global1', VAR3: 'global3' } + { 'VAR1' => 'global1', 'VAR3' => 'global3' } end let(:job_variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end let(:config) do { @@ -518,9 +518,9 @@ module Ci it 'returns all unique variables' do expect(subject).to contain_exactly( - { key: :VAR3, value: 'global3', public: true }, - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR3', value: 'global3', public: true }, + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end @@ -535,13 +535,13 @@ module Ci context 'when syntax is correct' do let(:variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end it 'returns job variables' do expect(subject).to contain_exactly( - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end @@ -549,7 +549,7 @@ module Ci context 'when syntax is incorrect' do context 'when variables defined but invalid' do let(:variables) do - [ :VAR1, 'value1', :VAR2, 'value2' ] + [ 'VAR1', 'value1', 'VAR2', 'value2' ] end it 'raises error' do diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 53f3c73ade4..72b1ba36b58 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -69,6 +69,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do context 'issues statuses' do before do + # HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this + Bitbucket::Representation::Issue.new({}) + stub_request( :get, "https://api.bitbucket.org/2.0/repositories/#{project_identifier}" @@ -108,13 +111,16 @@ describe Gitlab::BitbucketImport::Importer, lib: true do body: {}.to_json) end - it 'map statuses to open or closed' do - # HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this - Bitbucket::Representation::Issue.new({}) + it 'maps statuses to open or closed' do importer.execute expect(project.issues.where(state: "closed").size).to eq(5) expect(project.issues.where(state: "opened").size).to eq(2) end + + it 'calls import_wiki' do + expect(importer).to receive(:import_wiki) + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index b6d052a4612..773d0d4d288 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -11,7 +11,8 @@ describe Gitlab::BitbucketImport::ProjectCreator, lib: true do owner: "asd", full_name: 'Vim repo', visibility_level: Gitlab::VisibilityLevel::PRIVATE, - clone_url: 'ssh://git@bitbucket.org/asd/vim.git') + clone_url: 'ssh://git@bitbucket.org/asd/vim.git', + has_wiki?: false) end let(:namespace){ create(:group, owner: user) } diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index bfc6818ac08..a0ec8884635 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -5,7 +5,9 @@ describe Gitlab::ChatCommands::Command, service: true do let(:user) { create(:user) } describe '#execute' do - subject { described_class.new(project, user, params).execute } + subject do + described_class.new(project, user, params).execute + end context 'when no command is available' do let(:params) { { text: 'issue show 1' } } @@ -74,7 +76,7 @@ describe Gitlab::ChatCommands::Command, service: true do end it 'returns action' do - expect(subject[:text]).to include('Deployment from staging to production started') + expect(subject[:text]).to include('Deployment from staging to production started.') expect(subject[:response_type]).to be(:in_channel) end @@ -91,4 +93,26 @@ describe Gitlab::ChatCommands::Command, service: true do end end end + + describe '#match_command' do + subject { described_class.new(project, user, params).match_command.first } + + context 'IssueShow is triggered' do + let(:params) { { text: 'issue show 123' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) } + end + + context 'IssueCreate is triggered' do + let(:params) { { text: 'issue create my title' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) } + end + + context 'IssueSearch is triggered' do + let(:params) { { text: 'issue search my query' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) } + end + end end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 9376bce17a1..b3c07347de1 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -32,6 +32,14 @@ describe Gitlab::Ci::Status::Build::Cancelable do end end + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 4ddf04a8e11..f1b50a59ce6 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -18,6 +18,10 @@ describe Gitlab::Ci::Status::Build::Play do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe '#group' do + it { expect(subject.group).to eq 'manual' } + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index d61e5bbaa6b..62036f8ec5d 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -32,6 +32,14 @@ describe Gitlab::Ci::Status::Build::Retryable do end end + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 59a85b55f90..597e02e86e4 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -20,6 +20,10 @@ describe Gitlab::Ci::Status::Build::Stop do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe '#group' do + it { expect(subject.group).to eq 'manual' } + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 4639278ad45..38412fe2e4f 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Canceled do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_canceled' } end + + describe '#group' do + it { expect(subject.group).to eq 'canceled' } + end end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 2ce176a29d6..6d847484693 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Created do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_created' } end + + describe '#group' do + it { expect(subject.group).to eq 'created' } + end end diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 9d527e6a7ef..990d686d22c 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Failed do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_failed' } end + + describe '#group' do + it { expect(subject.group).to eq 'failed' } + end end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index d03f595d3c7..7bb6579c317 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Pending do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_pending' } end + + describe '#group' do + it { expect(subject.group).to eq 'pending' } + end end diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb index 7e3383c307f..979160eb9c4 100644 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do it { expect(subject.icon).to eq 'icon_status_warning' } end + describe '#group' do + it { expect(subject.group).to eq 'success_with_warnings' } + end + describe '.matches?' do context 'when pipeline is successful' do let(:pipeline) do diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index 9f47090d396..852d6c06baf 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Running do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_running' } end + + describe '#group' do + it { expect(subject.group).to eq 'running' } + end end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 94601648a8d..e00b356a24b 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Skipped do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_skipped' } end + + describe '#group' do + it { expect(subject.group).to eq 'skipped' } + end end diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 90f9f615e0d..4a89e1faf40 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Success do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_success' } end + + describe '#group' do + it { expect(subject.group).to eq 'success' } + end end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 444639acbaa..1f9c987be0b 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -26,6 +26,13 @@ describe Gitlab::Git::RevList, lib: true do expect(rev_list).not_to be_valid end + + it "ignores nil values" do + env = { var => nil } + rev_list = described_class.new('oldrev', 'newrev', project: project, env: env) + + expect(rev_list).to be_valid + end end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 9b49d6837c3..7e618e2fcf5 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -129,6 +129,7 @@ project: - builds_email_service - pipelines_email_service - mattermost_slash_commands_service +- slack_slash_commands_service - irker_service - pivotaltracker_service - hipchat_service diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index ed9df468ced..931d426c87f 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2517,7 +2517,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_build_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -6548,7 +6548,9 @@ "url": null }, "erased_by_id": null, - "erased_at": null + "erased_at": null, + "type": "Ci::Build", + "token": "abcd" }, { "id": 72, @@ -7409,6 +7411,28 @@ "category": "common", "default": false, "wiki_page_events": true + }, + { + "id": 101, + "title": "JenkinsDeprecated", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.031Z", + "updated_at": "2016-06-14T15:01:51.031Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true, + "type": "JenkinsDeprecatedService" } ], "hooks": [ diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 3038ab53ad8..4b07fa53bf5 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -189,6 +189,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do end end end + + context 'when there is an existing build with build token' do + it 'restores project json correctly' do + create(:ci_build, token: 'abcd') + + expect(restored_project_json).to be true + end + end end end end diff --git a/spec/lib/gitlab/serialize/ci/variables_spec.rb b/spec/lib/gitlab/serialize/ci/variables_spec.rb new file mode 100644 index 00000000000..7ea74da5252 --- /dev/null +++ b/spec/lib/gitlab/serialize/ci/variables_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Gitlab::Serialize::Ci::Variables do + subject do + described_class.load(described_class.dump(object)) + end + + let(:object) do + [{ key: :key, value: 'value', public: true }, + { key: 'wee', value: 1, public: false }] + end + + it 'converts keys into strings' do + is_expected.to eq([ + { key: 'key', value: 'value', public: true }, + { key: 'wee', value: 1, public: false }]) + end +end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 6f1c2ae0fd8..cd3b6d51545 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -458,7 +458,7 @@ describe Ci::Build, models: true do }) end let(:variables) do - [{ key: :KEY, value: 'value', public: true }] + [{ key: 'KEY', value: 'value', public: true }] end it { is_expected.to eq(predefined_variables + variables) } @@ -1306,11 +1306,25 @@ describe Ci::Build, models: true do describe '#expanded_environment_name' do subject { build.expanded_environment_name } - context 'when environment uses variables' do - let(:build) { create(:ci_build, ref: 'master', environment: 'review/$CI_BUILD_REF_NAME') } + context 'when environment uses $CI_BUILD_REF_NAME' do + let(:build) do + create(:ci_build, + ref: 'master', + environment: 'review/$CI_BUILD_REF_NAME') + end it { is_expected.to eq('review/master') } end + + context 'when environment uses yaml_variables containing symbol keys' do + let(:build) do + create(:ci_build, + yaml_variables: [{ key: :APP_HOST, value: 'host' }], + environment: 'review/$APP_HOST') + end + + it { is_expected.to eq('review/host') } + end end describe '#detailed_status' do diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb index b71d153f814..50ad5013df9 100644 --- a/spec/models/project_services/chat_message/build_message_spec.rb +++ b/spec/models/project_services/chat_message/build_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do tag: false, project_name: 'project_name', - project_url: 'example.gitlab.com', + project_url: 'http://example.gitlab.com', commit: { status: status, @@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do end def build_message(status_text = status) - ":" \ - " Commit :" \ + " Commit " \ - " of branch" \ + " of branch" \ " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index ebe0ead4408..190ff4c535d 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Issue title', id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', action: 'open', state: 'opened', description: 'issue description' @@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by test.user') + '[] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", - title_link: "url", + title_link: "http://url.com", text: "issue description", color: color, } @@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by test.user') + '[] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 07c414c6ca4..cc154112e90 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: "Issue title\nSecond line", id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', state: 'opened', description: 'issue description', source_branch: 'source_branch', @@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened '\ - 'in : *Issue title*') + 'test.user opened '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end @@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed '\ - 'in : *Issue title*') + 'test.user closed '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 31936da40a2..da700a08e57 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do avatar_url: 'http://fakeavatar' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', repository: { name: 'project_name', - url: 'somewhere.com', + url: 'http://somewhere.com', }, object_attributes: { id: 10, note: 'comment on a commit', - url: 'url', + url: 'http://url.com', noteable_type: 'Commit' } } @@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*Added a commit message*") expected_attachments = [ { @@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*merge request title*") expected_attachments = [ { @@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = described_class.new(@args) expect(message.pretext).to eq( - "test.user in : " \ + "test.user in : " \ "*issue title*") expected_attachments = [ { @@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*snippet title*") expected_attachments = [ { diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index eca71db07b6..bf2a9616455 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -15,7 +15,7 @@ describe ChatMessage::PipelineMessage do duration: duration }, project: { path_with_namespace: 'project_name', - web_url: 'example.gitlab.com' }, + web_url: 'http://example.gitlab.com' }, user: user } end @@ -59,9 +59,9 @@ describe ChatMessage::PipelineMessage do end def build_message(status_text = status, name = user[:name]) - ":" \ - " Pipeline " \ - " of branch" \ + ":" \ + " Pipeline " \ + " of branch" \ " by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index b781c4505db..24928873bad 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/heads/master', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end @@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do context 'push' do before do args[:commits] = [ - { message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } }, - { message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } }, + { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } }, + { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }, ] end it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'test.user pushed to branch of '\ - ' ()' + 'test.user pushed to branch of '\ + ' ()' ) expect(subject.attachments).to eq([ { - text: ": message1 - author1\n"\ - ": message2 - author2", + text: ": message1 - author1\n"\ + ": message2 - author2", color: color, } ]) @@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/tags/new_tag', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end it 'returns a message regarding pushes' do expect(subject.pretext).to eq('test.user pushed new tag ' \ - ' to ' \ - '') + ' to ' \ + '') expect(subject.attachments).to be_empty end end @@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'test.user pushed new branch to '\ - '' + 'test.user pushed new branch to '\ + '' ) expect(subject.attachments).to be_empty end @@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'test.user removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 94c04dc0865..a2ad61e38e7 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Wiki page title', - url: 'url', + url: 'http://url.com', content: 'Wiki page description' } } @@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end diff --git a/spec/models/project_services/chat_service_spec.rb b/spec/models/project_services/chat_service_spec.rb deleted file mode 100644 index c6a45a3e1be..00000000000 --- a/spec/models/project_services/chat_service_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe ChatService, models: true do - describe "Associations" do - it { is_expected.to have_many :chat_names } - end - - describe '#valid_token?' do - subject { described_class.new } - - it 'is false as it has no token' do - expect(subject.valid_token?('wer')).to be_falsey - end - end -end diff --git a/spec/models/project_services/mattermost_notification_service_spec.rb b/spec/models/project_services/mattermost_notification_service_spec.rb index c01e64b4c8e..7832d6f50cf 100644 --- a/spec/models/project_services/mattermost_notification_service_spec.rb +++ b/spec/models/project_services/mattermost_notification_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe MattermostNotificationService, models: true do - it_behaves_like "slack or mattermost" + it_behaves_like "slack or mattermost notifications" end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 00018624d96..1ae1483e2a4 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,135 +1,5 @@ require 'spec_helper' -describe MattermostSlashCommandsService, models: true do - describe "Associations" do - it { is_expected.to respond_to :token } - end - - describe '#valid_token?' do - subject { described_class.new } - - context 'when the token is empty' do - it 'is false' do - expect(subject.valid_token?('wer')).to be_falsey - end - end - - context 'when there is a token' do - before do - subject.token = '123' - end - - it 'accepts equal tokens' do - expect(subject.valid_token?('123')).to be_truthy - end - end - end - - describe '#trigger' do - subject { described_class.new } - - context 'no token is passed' do - let(:params) { Hash.new } - - it 'returns nil' do - expect(subject.trigger(params)).to be_nil - end - end - - context 'with a token passed' do - let(:project) { create(:empty_project) } - let(:params) { { token: 'token' } } - - before do - allow(subject).to receive(:token).and_return('token') - end - - context 'no user can be found' do - context 'when no url can be generated' do - it 'responds with the authorize url' do - response = subject.trigger(params) - - expect(response[:response_type]).to eq :ephemeral - expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you" - end - end - - context 'when an auth url can be generated' do - let(:params) do - { - team_domain: 'http://domain.tld', - team_id: 'T3423423', - user_id: 'U234234', - user_name: 'mepmep', - token: 'token' - } - end - - let(:service) do - project.create_mattermost_slash_commands_service( - properties: { token: 'token' } - ) - end - - it 'generates the url' do - response = service.trigger(params) - - expect(response[:text]).to start_with(':wave: Hi there!') - end - end - end - - context 'when the user is authenticated' do - let!(:chat_name) { create(:chat_name, service: service) } - let(:service) do - project.create_mattermost_slash_commands_service( - properties: { token: 'token' } - ) - end - let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } - - it 'triggers the command' do - expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute) - - service.trigger(params) - end - end - end - end - - describe '#configure' do - let(:project) { create(:empty_project) } - let(:service) { project.build_mattermost_slash_commands_service } - - subject do - service.configure('http://localhost:8065', team_id: 'abc', trigger: 'gitlab', url: 'http://trigger.url', icon_url: 'http://icon.url/icon.png') - end - - context 'the requests succeeds' do - before do - allow_any_instance_of(Mattermost::Session).to receive(:with_session). - and_return('token' => 'mynewtoken') - end - - it 'saves the service' do - expect_any_instance_of(Mattermost::Session).to receive(:with_session) - expect { subject }.to change { project.services.count }.by(1) - end - - it 'saves the token' do - subject - - expect(service.reload.token).to eq('mynewtoken') - end - end - - context 'an error is received' do - it 'shows error messages' do - allow_any_instance_of(Mattermost::Session).to receive(:with_session). - and_return('token' => 'mynewtoken', 'message' => "Error") - - expect(subject).to eq("Error") - end - end - end +describe MattermostSlashCommandsService, :models do + it_behaves_like "chat slash commands service" end diff --git a/spec/models/project_services/slack_notification_service_spec.rb b/spec/models/project_services/slack_notification_service_spec.rb index 59ddddf7454..110b5bf2115 100644 --- a/spec/models/project_services/slack_notification_service_spec.rb +++ b/spec/models/project_services/slack_notification_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe SlackNotificationService, models: true do - it_behaves_like "slack or mattermost" + it_behaves_like "slack or mattermost notifications" end diff --git a/spec/models/project_services/slack_slash_commands_service.rb b/spec/models/project_services/slack_slash_commands_service.rb new file mode 100644 index 00000000000..5775e439906 --- /dev/null +++ b/spec/models/project_services/slack_slash_commands_service.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe SlackSlashCommandsService, :models do + it_behaves_like "chat slash commands service" + + describe '#trigger' do + context 'when an auth url is generated' do + let(:project) { create(:empty_project) } + let(:params) do + { + team_domain: 'http://domain.tld', + team_id: 'T3423423', + user_id: 'U234234', + user_name: 'mepmep', + token: 'token' + } + end + + let(:service) do + project.create_slack_slash_commands_service( + properties: { token: 'token' } + ) + end + + let(:authorize_url) do + 'http://authorize.example.com/' + end + + before do + allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url) + end + + it 'uses slack compatible links' do + response = service.trigger(params) + + expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>") + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ed6b2c6a22b..53b504fdfba 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -20,7 +20,6 @@ describe Project, models: true do it { is_expected.to have_many(:deploy_keys) } it { is_expected.to have_many(:hooks).dependent(:destroy) } 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_notification_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) } @@ -37,6 +36,7 @@ describe Project, models: true do it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } it { is_expected.to have_one(:assembla_service).dependent(:destroy) } + it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) } it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 6f491fdf9a0..8481a9bef16 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Route, models: true do - let!(:group) { create(:group) } + let!(:group) { create(:group, path: 'gitlab') } let!(:route) { group.route } describe 'relationships' do @@ -17,13 +17,15 @@ describe Route, models: true do describe '#rename_children' do let!(:nested_group) { create(:group, path: "test", parent: group) } let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } + let!(:similar_group) { create(:group, path: 'gitlab-org') } + + before { route.update_attributes(path: 'bar') } it "updates children routes with new path" do - route.update_attributes(path: 'bar') - expect(described_class.exists?(path: 'bar')).to be_truthy expect(described_class.exists?(path: 'bar/test')).to be_truthy expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy + expect(described_class.exists?(path: 'gitlab-org')).to be_truthy end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 80652129928..79f12ace999 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -249,7 +249,11 @@ describe Ci::API::Builds do end describe 'PATCH /builds/:id/trace.txt' do - let(:build) { create(:ci_build, :pending, :trace, runner_id: runner.id) } + let(:build) do + attributes = { runner_id: runner.id, pipeline: pipeline } + create(:ci_build, :running, :trace, attributes) + end + let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } } let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } let(:update_interval) { 10.seconds.to_i } @@ -276,7 +280,6 @@ describe Ci::API::Builds do end before do - build.run! initial_patch_the_trace end @@ -329,6 +332,19 @@ describe Ci::API::Builds do end end end + + context 'when project for the build has been deleted' do + let(:build) do + attributes = { runner_id: runner.id, pipeline: pipeline } + create(:ci_build, :running, :trace, attributes) do |build| + build.project.update(pending_delete: true) + end + end + + it 'responds with forbidden' do + expect(response.status).to eq(403) + end + end end context 'when Runner makes a force-patch' do diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb new file mode 100644 index 00000000000..4dfa29849ee --- /dev/null +++ b/spec/support/chat_slash_commands_shared_examples.rb @@ -0,0 +1,97 @@ +RSpec.shared_examples 'chat slash commands service' do + describe "Associations" do + it { is_expected.to respond_to :token } + it { is_expected.to have_many :chat_names } + end + + describe '#valid_token?' do + subject { described_class.new } + + context 'when the token is empty' do + it 'is false' do + expect(subject.valid_token?('wer')).to be_falsey + end + end + + context 'when there is a token' do + before do + subject.token = '123' + end + + it 'accepts equal tokens' do + expect(subject.valid_token?('123')).to be_truthy + end + end + end + + describe '#trigger' do + subject { described_class.new } + + context 'no token is passed' do + let(:params) { Hash.new } + + it 'returns nil' do + expect(subject.trigger(params)).to be_nil + end + end + + context 'with a token passed' do + let(:project) { create(:empty_project) } + let(:params) { { token: 'token' } } + + before do + allow(subject).to receive(:token).and_return('token') + end + + context 'no user can be found' do + context 'when no url can be generated' do + it 'responds with the authorize url' do + response = subject.trigger(params) + + expect(response[:response_type]).to eq :ephemeral + expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you" + end + end + + context 'when an auth url can be generated' do + let(:params) do + { + team_domain: 'http://domain.tld', + team_id: 'T3423423', + user_id: 'U234234', + user_name: 'mepmep', + token: 'token' + } + end + + let(:service) do + project.create_mattermost_slash_commands_service( + properties: { token: 'token' } + ) + end + + it 'generates the url' do + response = service.trigger(params) + + expect(response[:text]).to start_with(':wave: Hi there!') + end + end + end + + context 'when the user is authenticated' do + let!(:chat_name) { create(:chat_name, service: subject) } + let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } + + subject do + described_class.create(project: project, properties: { token: 'token' }) + end + + it 'triggers the command' do + expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute) + + subject.trigger(params) + end + end + end + end +end diff --git a/spec/support/slack_mattermost_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb similarity index 99% rename from spec/support/slack_mattermost_shared_examples.rb rename to spec/support/slack_mattermost_notifications_shared_examples.rb index 56d4965f74d..8582aea5fe5 100644 --- a/spec/support/slack_mattermost_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -1,6 +1,6 @@ Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f } -RSpec.shared_examples 'slack or mattermost' do +RSpec.shared_examples 'slack or mattermost notifications' do let(:chat_service) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com/' } diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb new file mode 100644 index 00000000000..12d442b9820 --- /dev/null +++ b/spec/tasks/gitlab/ldap_rake_spec.rb @@ -0,0 +1,13 @@ +require 'rake_helper' + +describe 'gitlab:ldap:rename_provider rake task' do + it 'completes without error' do + Rake.application.rake_require 'tasks/gitlab/ldap' + stub_warn_user_is_not_gitlab + ENV['force'] = 'yes' + + create(:identity) # Necessary to prevent `exit 1` from the task. + + run_rake_task('gitlab:ldap:rename_provider', 'ldapmain', 'ldapfoo') + end +end