From e35cb5a025e3f47fd7ab237b5cf368aa3b9b319d Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 28 Mar 2016 21:40:49 +0200 Subject: Docs for the new branch button [ci-skip] --- doc/gitlab-basics/basicsimages/new_branch_button.png | Bin 0 -> 120622 bytes doc/gitlab-basics/create-branch.md | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 doc/gitlab-basics/basicsimages/new_branch_button.png diff --git a/doc/gitlab-basics/basicsimages/new_branch_button.png b/doc/gitlab-basics/basicsimages/new_branch_button.png new file mode 100644 index 00000000000..394c139e17e Binary files /dev/null and b/doc/gitlab-basics/basicsimages/new_branch_button.png differ diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 7556b0f663e..9d688b9389b 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -32,6 +32,15 @@ Fill out the information required: ![Branch info](basicsimages/branch_info.png) +## From an issue +When an issue should be resolved one could also create a branch on the issue page. A button is displayed after the description unless there is already a branch or a referenced merge request. + +![New Branch Button](basicsimages/new_branch_button.png) + +The branch created diverges from the default branch of the project, usually `master`. The branch name will be based on the title of the issue and as suffix its ID. Thus the example screenshot above will yield a branch named `et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum-2`. +After the branch is created the user can edit files in the repository to fix the issue. When a merge request is created the +description field will display `Closes #2` to use the issue closing pattern. This will close the issue once the merge request is merged. + ### Note: You will be able to find and select the name of your branch in the white box next to a project's name: -- cgit v1.2.1 From 31b0e53015e38e51d9c02cca85c9279600b1bf85 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 13:41:00 +0200 Subject: Introduce NotificationSetting model It will hold notification setting per group or per project. It will allow get rid of notification level stored in Member model Signed-off-by: Dmitriy Zaporozhets --- app/models/notification_setting.rb | 14 ++++++++++++++ db/migrate/20160328112808_create_notification_settings.rb | 12 ++++++++++++ db/schema.rb | 15 ++++++++++++--- spec/models/notification_setting_spec.rb | 15 +++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 app/models/notification_setting.rb create mode 100644 db/migrate/20160328112808_create_notification_settings.rb create mode 100644 spec/models/notification_setting_spec.rb diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb new file mode 100644 index 00000000000..0dce146b7a9 --- /dev/null +++ b/app/models/notification_setting.rb @@ -0,0 +1,14 @@ +class NotificationSetting < ActiveRecord::Base + belongs_to :user + belongs_to :source, polymorphic: true + + validates :user, presence: true + validates :source, presence: true + validates :level, presence: true + validates :user_id, uniqueness: { scope: [:source_type, :source_id], + message: "already exists in source", + allow_nil: true } + # Notification level + # Note: When adding an option, it MUST go on the end of the array. + enum level: [:disabled, :participating, :watch, :global, :mention] +end diff --git a/db/migrate/20160328112808_create_notification_settings.rb b/db/migrate/20160328112808_create_notification_settings.rb new file mode 100644 index 00000000000..88652821ac3 --- /dev/null +++ b/db/migrate/20160328112808_create_notification_settings.rb @@ -0,0 +1,12 @@ +class CreateNotificationSettings < ActiveRecord::Migration + def change + create_table :notification_settings do |t| + t.integer :user_id + t.integer :level + t.integer :source_id + t.string :source_type + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index dce2bfe62ca..a9a68e723ab 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160320204112) do +ActiveRecord::Schema.define(version: 20160328112808) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -417,9 +417,9 @@ ActiveRecord::Schema.define(version: 20160320204112) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" - t.boolean "confidential", default: false + t.boolean "confidential", default: false t.datetime "deleted_at" + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -638,6 +638,15 @@ ActiveRecord::Schema.define(version: 20160320204112) do add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree + create_table "notification_settings", force: :cascade do |t| + t.integer "user_id" + t.integer "level" + t.integer "source_id" + t.string "source_type" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "oauth_access_grants", force: :cascade do |t| t.integer "resource_owner_id", null: false t.integer "application_id", null: false diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb new file mode 100644 index 00000000000..f9d668ed75b --- /dev/null +++ b/spec/models/notification_setting_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +RSpec.describe NotificationSetting, type: :model do + describe "Associations" do + it { is_expected.to belong_to(:user) } + end + + describe "Validation" do + subject { NotificationSetting.new } + + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:source) } + it { is_expected.to validate_presence_of(:level) } + end +end -- cgit v1.2.1 From 73c5a3410596165244bfa3d2f657c313ec1c558c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 14:27:30 +0200 Subject: Migrate notification setting from members table Signed-off-by: Dmitriy Zaporozhets --- .../20160328115649_migrate_new_notification_setting.rb | 13 +++++++++++++ db/migrate/20160328121138_add_notification_setting_index.rb | 6 ++++++ db/schema.rb | 5 ++++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160328115649_migrate_new_notification_setting.rb create mode 100644 db/migrate/20160328121138_add_notification_setting_index.rb diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb new file mode 100644 index 00000000000..331c35535f2 --- /dev/null +++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb @@ -0,0 +1,13 @@ +# This migration will create one row of NotificationSetting for each Member row +# It can take long time on big instances. Its unclear yet if this migration can be done online. +# This comment should be updated by @dzaporozhets before 8.7 release. If not - please ask him to do so. +class MigrateNewNotificationSetting < ActiveRecord::Migration + def up + timestamp = Time.now + execute "INSERT INTO notification_settings ( user_id, source_id, source_type, level, created_at, updated_at ) SELECT user_id, source_id, source_type, notification_level, '#{timestamp}', '#{timestamp}' FROM members" + end + + def down + NotificationSetting.delete_all + end +end diff --git a/db/migrate/20160328121138_add_notification_setting_index.rb b/db/migrate/20160328121138_add_notification_setting_index.rb new file mode 100644 index 00000000000..8aebce0244d --- /dev/null +++ b/db/migrate/20160328121138_add_notification_setting_index.rb @@ -0,0 +1,6 @@ +class AddNotificationSettingIndex < ActiveRecord::Migration + def change + add_index :notification_settings, :user_id + add_index :notification_settings, [:source_id, :source_type] + end +end diff --git a/db/schema.rb b/db/schema.rb index a9a68e723ab..29639abb6fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160328112808) do +ActiveRecord::Schema.define(version: 20160328121138) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -647,6 +647,9 @@ ActiveRecord::Schema.define(version: 20160328112808) do t.datetime "updated_at", null: false end + add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree + add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree + create_table "oauth_access_grants", force: :cascade do |t| t.integer "resource_owner_id", null: false t.integer "application_id", null: false -- cgit v1.2.1 From 359157c097993a9b917ca590e128e85cf358d95d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 18:17:42 +0200 Subject: Introduce NotificationSetting to user interface * visiting project will create notification setting if missing * change notification setting per project even without membership * use notification settings instead of membership on profile page Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/project.js.coffee | 8 +-- .../profiles/notifications_controller.rb | 14 ++-- app/controllers/projects_controller.rb | 17 +++-- app/helpers/notifications_helper.rb | 74 +++++++++++++--------- app/models/notification.rb | 4 +- app/models/notification_setting.rb | 7 ++ app/models/user.rb | 7 +- .../profiles/notifications/_settings.html.haml | 18 +++--- app/views/profiles/notifications/show.html.haml | 30 ++++----- .../projects/buttons/_notifications.html.haml | 20 ++---- 10 files changed, 106 insertions(+), 93 deletions(-) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 87d313ed67c..f171442d05a 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -37,15 +37,9 @@ class @Project $('.update-notification').on 'click', (e) -> e.preventDefault() notification_level = $(@).data 'notification-level' + label = $(@).data 'notification-title' $('#notification_level').val(notification_level) $('#notification-form').submit() - label = null - switch notification_level - when 0 then label = ' Disabled ' - when 1 then label = ' Participating ' - when 2 then label = ' Watching ' - when 3 then label = ' Global ' - when 4 then label = ' On Mention ' $('#notifications-button').empty().append("" + label + "") $(@).parents('ul').find('li.active').removeClass 'active' $(@).parent().addClass 'active' diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 1fd1d6882df..6ca7537300f 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -2,8 +2,8 @@ class Profiles::NotificationsController < Profiles::ApplicationController def show @user = current_user @notification = current_user.notification - @project_members = current_user.project_members - @group_members = current_user.group_members + @group_notifications = current_user.notification_settings.for_groups + @project_notifications = current_user.notification_settings.for_projects end def update @@ -11,14 +11,10 @@ class Profiles::NotificationsController < Profiles::ApplicationController @saved = if type == 'global' current_user.update_attributes(user_params) - elsif type == 'group' - group_member = current_user.group_members.find(params[:notification_id]) - group_member.notification_level = params[:notification_level] - group_member.save else - project_member = current_user.project_members.find(params[:notification_id]) - project_member.notification_level = params[:notification_level] - project_member.save + notification_setting = current_user.notification_settings.find(params[:notification_id]) + notification_setting.level = params[:notification_level] + notification_setting.save end respond_to do |format| diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 928817ba811..77122f59128 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -98,14 +98,23 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| format.html do + if current_user + @membership = @project.team.find_member(current_user.id) + + if @membership + @notification_setting = current_user.notification_settings.find_or_initialize_by(source: @project) + + unless @notification_setting.persisted? + @notification_setting.set_defaults + @notification_setting.save + end + end + end + if @project.repository_exists? if @project.empty_repo? render 'projects/empty' else - if current_user - @membership = @project.team.find_member(current_user.id) - end - render :show end else diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 499c655d2bf..a0e91a63d2d 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,48 +1,60 @@ module NotificationsHelper include IconsHelper - def notification_icon(notification) - if notification.disabled? - icon('volume-off', class: 'ns-mute') - elsif notification.participating? - icon('volume-down', class: 'ns-part') - elsif notification.watch? - icon('volume-up', class: 'ns-watch') - else - icon('circle-o', class: 'ns-default') + def notification_icon_class(level) + case level.to_sym + when :disabled + 'microphone-slash' + when :participating + 'volume-up' + when :watch + 'eye' + when :mention + 'at' + when :global + 'globe' end end - def notification_list_item(notification_level, user_membership) - case notification_level - when Notification::N_DISABLED - update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash') - when Notification::N_PARTICIPATING - update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up') - when Notification::N_WATCH - update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye') - when Notification::N_MENTION - update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at') - when Notification::N_GLOBAL - update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe') - else - # do nothing + def notification_icon(level) + icon("#{notification_icon_class(level)} fw") + end + + def notification_title(level) + case level.to_sym + when :disabled + 'Disabled' + when :participating + 'Participate' + when :watch + 'Watch' + when :mention + 'On mention' + when :global + 'Global' end end - def update_notification_link(notification_level, user_membership, title, icon) - content_tag(:li, class: active_level_for(user_membership, notification_level)) do - link_to '#', class: 'update-notification', data: { notification_level: notification_level } do - icon("#{icon} fw", text: title) + def notification_list_item(level, setting) + title = notification_title(level) + + data = { + notification_level: level, + notification_title: title + } + + content_tag(:li, class: active_level_for(setting, level)) do + link_to '#', class: 'update-notification', data: data do + icon("#{notification_icon_class(level)} fw", text: title) end end end - def notification_label(user_membership) - Notification.new(user_membership).to_s + def notification_label(setting) + notification_title(setting.level) end - def active_level_for(user_membership, level) - 'active' if user_membership.notification_level == level + def active_level_for(setting, level) + 'active' if setting.level == level end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 171b8df45c2..379f041969b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -57,7 +57,7 @@ class Notification def level target.notification_level end - + def to_s case level when N_DISABLED @@ -71,7 +71,7 @@ class Notification when N_GLOBAL 'Global' else - # do nothing + # do nothing end end end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 0dce146b7a9..287862a01bc 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -11,4 +11,11 @@ class NotificationSetting < ActiveRecord::Base # Notification level # Note: When adding an option, it MUST go on the end of the array. enum level: [:disabled, :participating, :watch, :global, :mention] + + scope :for_groups, -> { where(source_type: 'Namespace') } + scope :for_projects, -> { where(source_type: 'Project') } + + def set_defaults + self.level = :global + end end diff --git a/app/models/user.rb b/app/models/user.rb index 128ddc2a694..59493e6f90c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -143,6 +143,7 @@ class User < ActiveRecord::Base has_many :spam_logs, dependent: :destroy has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :todos, dependent: :destroy + has_many :notification_settings, dependent: :destroy # # Validations @@ -157,7 +158,7 @@ class User < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } - validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true + validates :notification_level, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } @@ -190,6 +191,10 @@ class User < ActiveRecord::Base # Note: When adding an option, it MUST go on the end of the array. enum project_view: [:readme, :activity, :files] + # Notification level + # Note: When adding an option, it MUST go on the end of the array. + enum notification_level: [:disabled, :participating, :watch, :global, :mention] + alias_attribute :private_token, :authentication_token delegate :path, to: :namespace, allow_nil: true, prefix: true diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index d0d044136f6..c32de0b9925 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -1,17 +1,17 @@ %li.notification-list-item %span.notification.fa.fa-holder.append-right-5 - - if notification.global? - = notification_icon(@notification) + - if setting.global? + = notification_icon(current_user.notification_level) - else - = notification_icon(notification) + = notification_icon(setting.level) %span.str-truncated - - if membership.kind_of? GroupMember - = link_to membership.group.name, membership.group + - if setting.source.kind_of? Project + = link_to_project(setting.source) - else - = link_to_project(membership.project) + = link_to setting.source.name, group_path(setting.source) .pull-right = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do - = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type') - = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id') - = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'form-control trigger-submit' + = hidden_field_tag :notification_id, setting.id + = hidden_field_tag :notification_level, setting.level + = select_tag :notification_level, options_for_select(User.notification_levels.keys, setting.level), class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index de80abd7f4d..f6900f61b2d 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -26,29 +26,29 @@ .form-group = f.label :notification_level, class: 'label-light' .radio - = f.label :notification_level, value: Notification::N_DISABLED do - = f.radio_button :notification_level, Notification::N_DISABLED + = f.label :notification_level, value: :disabled do + = f.radio_button :notification_level, :disabled .level-title Disabled %p You will not get any notifications via email .radio - = f.label :notification_level, value: Notification::N_MENTION do - = f.radio_button :notification_level, Notification::N_MENTION + = f.label :notification_level, value: :mention do + = f.radio_button :notification_level, :mention .level-title On Mention %p You will receive notifications only for comments in which you were @mentioned .radio - = f.label :notification_level, value: Notification::N_PARTICIPATING do - = f.radio_button :notification_level, Notification::N_PARTICIPATING + = f.label :notification_level, value: :participating do + = f.radio_button :notification_level, :participating .level-title Participating %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) .radio - = f.label :notification_level, value: Notification::N_WATCH do - = f.radio_button :notification_level, Notification::N_WATCH + = f.label :notification_level, value: :watch do + = f.radio_button :notification_level, :watch .level-title Watch %p You will receive notifications for any activity @@ -57,18 +57,16 @@ = f.submit 'Update settings', class: "btn btn-create" %hr %h5 - Groups (#{@group_members.count}) + Groups (#{@group_notifications.count}) %div %ul.bordered-list - - @group_members.each do |group_member| - - notification = Notification.new(group_member) - = render 'settings', type: 'group', membership: group_member, notification: notification + - @group_notifications.each do |setting| + = render 'settings', setting: setting %h5 - Projects (#{@project_members.count}) + Projects (#{@project_notifications.count}) %p.account-well To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group. .append-bottom-default %ul.bordered-list - - @project_members.each do |project_member| - - notification = Notification.new(project_member) - = render 'settings', type: 'project', membership: project_member, notification: notification + - @project_notifications.each do |setting| + = render 'settings', setting: setting diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index a3786c35a1f..4b8a10f0819 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,20 +1,12 @@ -- case @membership -- when ProjectMember +- if @notification_setting = form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do - = hidden_field_tag :notification_type, 'project' - = hidden_field_tag :notification_id, @membership.id - = hidden_field_tag :notification_level + = hidden_field_tag :notification_id, @notification_setting.id + = hidden_field_tag :notification_level, @notification_setting.level %span.dropdown %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} = icon('bell') - = notification_label(@membership) + = notification_title(@notification_setting.level) = icon('angle-down') %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - - Notification.project_notification_levels.each do |level| - = notification_list_item(level, @membership) - -- when GroupMember - .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."} - = icon('bell') - = notification_label(@membership) - = icon('angle-down') + - NotificationSetting.levels.each do |level| + = notification_list_item(level.first, @notification_setting) -- cgit v1.2.1 From 7ea1bcab45697556d4ffd79ab680872ed823d4a3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 18:25:57 +0200 Subject: Create notification setting when membership created Signed-off-by: Dmitriy Zaporozhets --- app/models/member.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/member.rb b/app/models/member.rb index ca08007b7eb..177f37c3bbd 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -56,6 +56,7 @@ class Member < ActiveRecord::Base before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } after_create :send_invite, if: :invite? + after_create :create_notification_setting, unless: :invite? after_create :post_create_hook, unless: :invite? after_update :post_update_hook, unless: :invite? after_destroy :post_destroy_hook, unless: :invite? @@ -160,6 +161,15 @@ class Member < ActiveRecord::Base send_invite end + def create_notification_setting + notification_setting = user.notification_settings.find_or_initialize_by(source: source) + + unless notification_setting.persisted? + notification_setting.set_defaults + notification_setting.save + end + end + private def send_invite -- cgit v1.2.1 From b8f38437900cdddac9d19d5c48a2a8e5bb037f41 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 20:31:36 +0200 Subject: Update NotificationService to use NotificationSettings instead of membership Signed-off-by: Dmitriy Zaporozhets --- app/models/concerns/notifiable.rb | 15 ------------- app/models/group.rb | 1 + app/models/member.rb | 5 ++++- app/models/notification.rb | 22 ++----------------- app/models/project.rb | 1 + app/models/user.rb | 3 +++ app/services/notification_service.rb | 34 ++++++++++++++---------------- spec/services/notification_service_spec.rb | 26 +++++++++++------------ 8 files changed, 40 insertions(+), 67 deletions(-) delete mode 100644 app/models/concerns/notifiable.rb diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb deleted file mode 100644 index d7dcd97911d..00000000000 --- a/app/models/concerns/notifiable.rb +++ /dev/null @@ -1,15 +0,0 @@ -# == Notifiable concern -# -# Contains notification functionality -# -module Notifiable - extend ActiveSupport::Concern - - included do - validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true - end - - def notification - @notification ||= Notification.new(self) - end -end diff --git a/app/models/group.rb b/app/models/group.rb index b332601c59b..9a04ac70d35 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -27,6 +27,7 @@ class Group < Namespace has_many :users, through: :group_members has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project + has_many :notification_settings, dependent: :destroy, as: :source validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :visibility_level_allowed_by_projects diff --git a/app/models/member.rb b/app/models/member.rb index 177f37c3bbd..e665ba6fb75 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -19,7 +19,6 @@ class Member < ActiveRecord::Base include Sortable - include Notifiable include Gitlab::Access attr_accessor :raw_invite_token @@ -170,6 +169,10 @@ class Member < ActiveRecord::Base end end + def notification + @notification ||= user.notification_settings.find_by(source: source) + end + private def send_invite diff --git a/app/models/notification.rb b/app/models/notification.rb index 379f041969b..8a90b456cc2 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -30,30 +30,12 @@ class Notification end end + delegate :disabled?, :participating?, :watch?, :global?, :mention?, to: :target + def initialize(target) @target = target end - def disabled? - target.notification_level == N_DISABLED - end - - def participating? - target.notification_level == N_PARTICIPATING - end - - def watch? - target.notification_level == N_WATCH - end - - def global? - target.notification_level == N_GLOBAL - end - - def mention? - target.notification_level == N_MENTION - end - def level target.notification_level end diff --git a/app/models/project.rb b/app/models/project.rb index 2285063ab50..2f9621809b6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -154,6 +154,7 @@ class Project < ActiveRecord::Base has_many :project_group_links, dependent: :destroy has_many :invited_groups, through: :project_group_links, source: :group has_many :todos, dependent: :destroy + has_many :notification_settings, dependent: :destroy, as: :source has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" diff --git a/app/models/user.rb b/app/models/user.rb index 59493e6f90c..f556dc5903d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -193,6 +193,9 @@ class User < ActiveRecord::Base # Notification level # Note: When adding an option, it MUST go on the end of the array. + # + # TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813 + # Because user.notification_disabled? is much better than user.disabled? enum notification_level: [:disabled, :participating, :watch, :global, :mention] alias_attribute :private_token, :authentication_token diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index eff0d96f93d..9628843c230 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -253,8 +253,8 @@ class NotificationService def project_watchers(project) project_members = project_member_notification(project) - users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL) - users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL) + users_with_project_level_global = project_member_notification(project, :global) + users_with_group_level_global = group_member_notification(project, :global) users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) @@ -264,18 +264,16 @@ class NotificationService end def project_member_notification(project, notification_level=nil) - project_members = project.project_members - if notification_level - project_members.where(notification_level: notification_level).pluck(:user_id) + project.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id) else - project_members.pluck(:user_id) + project.notification_settings.pluck(:user_id) end end def group_member_notification(project, notification_level) if project.group - project.group.group_members.where(notification_level: notification_level).pluck(:user_id) + project.group.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id) else [] end @@ -284,13 +282,13 @@ class NotificationService def users_with_global_level_watch(ids) User.where( id: ids, - notification_level: Notification::N_WATCH + notification_level: NotificationSetting.levels[:watch] ).pluck(:id) end # Build a list of users based on project notifcation settings def select_project_member_setting(project, global_setting, users_global_level_watch) - users = project_member_notification(project, Notification::N_WATCH) + users = project_member_notification(project, :watch) # If project setting is global, add to watch list if global setting is watch global_setting.each do |user_id| @@ -304,7 +302,7 @@ class NotificationService # Build a list of users based on group notification settings def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) - uids = group_member_notification(project, Notification::N_WATCH) + uids = group_member_notification(project, :watch) # Group setting is watch, add to users list if user is not project member users = [] @@ -351,20 +349,20 @@ class NotificationService users.reject do |user| next user.notification.send(method_name) unless project - member = project.project_members.find_by(user_id: user.id) + setting = user.notification_settings.find_by(source: project) - if !member && project.group - member = project.group.group_members.find_by(user_id: user.id) + if !setting && project.group + setting = user.notification_settings.find_by(source: group) end - # reject users who globally set mention notification and has no membership - next user.notification.send(method_name) unless member + # reject users who globally set mention notification and has no setting per project/group + next user.notification.send(method_name) unless setting # reject users who set mention notification in project - next true if member.notification.send(method_name) + next true if setting.send(method_name) - # reject users who have N_MENTION in project and disabled in global settings - member.notification.global? && user.notification.send(method_name) + # reject users who have mention level in project and disabled in global settings + setting.global? && user.notification.send(method_name) end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 0f2aa3ae73c..c01851a8a24 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -89,11 +89,11 @@ describe NotificationService, services: true do note.project.group.add_user(@u_watcher, GroupMember::MASTER) note.project.save user_project = note.project.project_members.find_by_user_id(@u_watcher.id) - user_project.notification_level = Notification::N_PARTICIPATING + user_project.notification.level = :participating user_project.save group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id) - group_member.notification_level = Notification::N_GLOBAL - group_member.save + group_member.notification.level = :global + group_member.notification.save ActionMailer::Base.deliveries.clear end @@ -215,7 +215,7 @@ describe NotificationService, services: true do end it do - @u_committer.update_attributes(notification_level: Notification::N_MENTION) + @u_committer.update_attributes(notification_level: :mention) notification.new_note(note) should_not_email(@u_committer) end @@ -246,7 +246,7 @@ describe NotificationService, services: true do end it do - issue.assignee.update_attributes(notification_level: Notification::N_MENTION) + issue.assignee.update_attributes(notification_level: :mention) notification.new_issue(issue, @u_disabled) should_not_email(issue.assignee) @@ -596,13 +596,13 @@ describe NotificationService, services: true do end def build_team(project) - @u_watcher = create(:user, notification_level: Notification::N_WATCH) - @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) - @u_participant_mentioned = create(:user, username: 'participant', notification_level: Notification::N_PARTICIPATING) - @u_disabled = create(:user, notification_level: Notification::N_DISABLED) - @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION) + @u_watcher = create(:user, notification_level: :watch) + @u_participating = create(:user, notification_level: :participating) + @u_participant_mentioned = create(:user, username: 'participant', notification_level: :participating) + @u_disabled = create(:user, notification_level: :disabled) + @u_mentioned = create(:user, username: 'mention', notification_level: :mention) @u_committer = create(:user, username: 'committer') - @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING) + @u_not_mentioned = create(:user, username: 'regular', notification_level: :participating) @u_outsider_mentioned = create(:user, username: 'outsider') project.team << [@u_watcher, :master] @@ -617,8 +617,8 @@ describe NotificationService, services: true do def add_users_with_subscription(project, issuable) @subscriber = create :user @unsubscriber = create :user - @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING) - @watcher_and_subscriber = create(:user, notification_level: Notification::N_WATCH) + @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: :participating) + @watcher_and_subscriber = create(:user, notification_level: :watch) project.team << [@subscribed_participant, :master] project.team << [@subscriber, :master] -- cgit v1.2.1 From 4ca73f56cb59b86f25b55ff02800571fb82c742f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 23:22:28 +0200 Subject: Small refactoring and cleanup of notification logic Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 12 ++------ app/models/member.rb | 2 ++ app/models/members/group_member.rb | 1 - app/models/members/project_member.rb | 1 - app/models/notification.rb | 46 ------------------------------ spec/models/notification_setting_spec.rb | 1 + spec/services/notification_service_spec.rb | 9 ++---- 7 files changed, 8 insertions(+), 64 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index a0e91a63d2d..8816cc5d164 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -22,16 +22,12 @@ module NotificationsHelper def notification_title(level) case level.to_sym - when :disabled - 'Disabled' when :participating 'Participate' - when :watch - 'Watch' when :mention 'On mention' - when :global - 'Global' + else + level.to_s.titlecase end end @@ -50,10 +46,6 @@ module NotificationsHelper end end - def notification_label(setting) - notification_title(setting.level) - end - def active_level_for(setting, level) 'active' if setting.level == level end diff --git a/app/models/member.rb b/app/models/member.rb index e665ba6fb75..799f28c3fdf 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -62,6 +62,8 @@ class Member < ActiveRecord::Base delegate :name, :username, :email, to: :user, prefix: true + default_value_for :notification_level, NotificationSetting.levels[:global] + class << self def find_by_invite_token(invite_token) invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 65d2ea00570..9fb474a1a93 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -24,7 +24,6 @@ class GroupMember < Member # Make sure group member points only to group as it source default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL validates_format_of :source_type, with: /\ANamespace\z/ default_scope { where(source_type: SOURCE_TYPE) } diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 560d1690e14..07ddb02ae9d 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -27,7 +27,6 @@ class ProjectMember < Member # Make sure project member points only to project as it source default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL validates_format_of :source_type, with: /\AProject\z/ default_scope { where(source_type: SOURCE_TYPE) } diff --git a/app/models/notification.rb b/app/models/notification.rb index 8a90b456cc2..3805bde88b0 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,35 +1,6 @@ class Notification - # - # Notification levels - # - N_DISABLED = 0 - N_PARTICIPATING = 1 - N_WATCH = 2 - N_GLOBAL = 3 - N_MENTION = 4 - attr_accessor :target - class << self - def notification_levels - [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH] - end - - def options_with_labels - { - disabled: N_DISABLED, - participating: N_PARTICIPATING, - watch: N_WATCH, - mention: N_MENTION, - global: N_GLOBAL - } - end - - def project_notification_levels - [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL] - end - end - delegate :disabled?, :participating?, :watch?, :global?, :mention?, to: :target def initialize(target) @@ -39,21 +10,4 @@ class Notification def level target.notification_level end - - def to_s - case level - when N_DISABLED - 'Disabled' - when N_PARTICIPATING - 'Participating' - when N_WATCH - 'Watching' - when N_MENTION - 'On mention' - when N_GLOBAL - 'Global' - else - # do nothing - end - end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index f9d668ed75b..f31b2a3cd6f 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' RSpec.describe NotificationSetting, type: :model do describe "Associations" do it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:source) } end describe "Validation" do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index c01851a8a24..c4d52584a4b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -88,12 +88,9 @@ describe NotificationService, services: true do note.project.namespace_id = group.id note.project.group.add_user(@u_watcher, GroupMember::MASTER) note.project.save - user_project = note.project.project_members.find_by_user_id(@u_watcher.id) - user_project.notification.level = :participating - user_project.save - group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id) - group_member.notification.level = :global - group_member.notification.save + + @u_watcher.notification_settings.find_by(source: note.project).participating! + @u_watcher.notification_settings.find_by(source: note.project.group).global! ActionMailer::Base.deliveries.clear end -- cgit v1.2.1 From 855b2820c16c6e569d5c38b7def8ead18c86cecd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 28 Mar 2016 23:39:49 +0200 Subject: Improve db migrations for notification settings Signed-off-by: Dmitriy Zaporozhets --- .../20160328112808_create_notification_settings.rb | 7 +++---- .../20160328115649_migrate_new_notification_setting.rb | 2 +- db/schema.rb | 18 +++++++++--------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/db/migrate/20160328112808_create_notification_settings.rb b/db/migrate/20160328112808_create_notification_settings.rb index 88652821ac3..4755da8b806 100644 --- a/db/migrate/20160328112808_create_notification_settings.rb +++ b/db/migrate/20160328112808_create_notification_settings.rb @@ -1,10 +1,9 @@ class CreateNotificationSettings < ActiveRecord::Migration def change create_table :notification_settings do |t| - t.integer :user_id - t.integer :level - t.integer :source_id - t.string :source_type + t.references :user, null: false + t.references :source, polymorphic: true, null: false + t.integer :level, default: 0, null: false t.timestamps null: false end diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb index 331c35535f2..aff866b5f46 100644 --- a/db/migrate/20160328115649_migrate_new_notification_setting.rb +++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb @@ -4,7 +4,7 @@ class MigrateNewNotificationSetting < ActiveRecord::Migration def up timestamp = Time.now - execute "INSERT INTO notification_settings ( user_id, source_id, source_type, level, created_at, updated_at ) SELECT user_id, source_id, source_type, notification_level, '#{timestamp}', '#{timestamp}' FROM members" + execute "INSERT INTO notification_settings ( user_id, source_id, source_type, level, created_at, updated_at ) SELECT user_id, source_id, source_type, notification_level, '#{timestamp}', '#{timestamp}' FROM members WHERE user_id IS NOT NULL" end def down diff --git a/db/schema.rb b/db/schema.rb index 29639abb6fc..e946ecd3f2b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -639,12 +639,12 @@ ActiveRecord::Schema.define(version: 20160328121138) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "notification_settings", force: :cascade do |t| - t.integer "user_id" - t.integer "level" - t.integer "source_id" - t.string "source_type" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", null: false + t.integer "source_id", null: false + t.string "source_type", null: false + t.integer "level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree @@ -798,9 +798,9 @@ ActiveRecord::Schema.define(version: 20160328121138) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true -- cgit v1.2.1 From 08b3d7f6ef23b7a8a83c7e71e2d04f6416e73406 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 13:08:30 +0200 Subject: Refactor notification helper and fix notification service Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 12 ++++------ app/services/notification_service.rb | 2 +- spec/helpers/notifications_helper_spec.rb | 37 ++++++++----------------------- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 8816cc5d164..54ab9179efc 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -16,8 +16,8 @@ module NotificationsHelper end end - def notification_icon(level) - icon("#{notification_icon_class(level)} fw") + def notification_icon(level, text = nil) + icon("#{notification_icon_class(level)} fw", text: text) end def notification_title(level) @@ -39,14 +39,10 @@ module NotificationsHelper notification_title: title } - content_tag(:li, class: active_level_for(setting, level)) do + content_tag(:li, class: ('active' if setting.level == level)) do link_to '#', class: 'update-notification', data: data do - icon("#{notification_icon_class(level)} fw", text: title) + notification_icon(level, title) end end end - - def active_level_for(setting, level) - 'active' if setting.level == level - end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 9628843c230..23f211dfcd2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -352,7 +352,7 @@ class NotificationService setting = user.notification_settings.find_by(source: project) if !setting && project.group - setting = user.notification_settings.find_by(source: group) + setting = user.notification_settings.find_by(source: project.group) end # reject users who globally set mention notification and has no setting per project/group diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index f1aba4cfdf3..9d5f009ebe1 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -2,34 +2,15 @@ require 'spec_helper' describe NotificationsHelper do describe 'notification_icon' do - let(:notification) { double(disabled?: false, participating?: false, watch?: false) } - - context "disabled notification" do - before { allow(notification).to receive(:disabled?).and_return(true) } - - it "has a red icon" do - expect(notification_icon(notification)).to match('class="fa fa-volume-off ns-mute"') - end - end - - context "participating notification" do - before { allow(notification).to receive(:participating?).and_return(true) } - - it "has a blue icon" do - expect(notification_icon(notification)).to match('class="fa fa-volume-down ns-part"') - end - end - - context "watched notification" do - before { allow(notification).to receive(:watch?).and_return(true) } - - it "has a green icon" do - expect(notification_icon(notification)).to match('class="fa fa-volume-up ns-watch"') - end - end + it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') } + it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') } + it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') } + it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') } + it { expect(notification_icon(:watch)).to match('class="fa fa-eye fa-fw"') } + end - it "has a blue icon" do - expect(notification_icon(notification)).to match('class="fa fa-circle-o ns-default"') - end + describe 'notification_title' do + it { expect(notification_title(:watch)).to match('Watch') } + it { expect(notification_title(:mention)).to match('On mention') } end end -- cgit v1.2.1 From 86418c475be1b2a37e89682bc87055b7372bbcfb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 13:37:43 +0200 Subject: Remove useless Notification model Signed-off-by: Dmitriy Zaporozhets --- app/models/member.rb | 4 ++-- app/models/notification.rb | 13 ------------- app/models/user.rb | 4 ---- app/services/notification_service.rb | 24 +++++++++++++++--------- 4 files changed, 17 insertions(+), 28 deletions(-) delete mode 100644 app/models/notification.rb diff --git a/app/models/member.rb b/app/models/member.rb index 799f28c3fdf..cbcc54c0250 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -171,8 +171,8 @@ class Member < ActiveRecord::Base end end - def notification - @notification ||= user.notification_settings.find_by(source: source) + def notification_setting + @notification_setting ||= user.notification_settings.find_by(source: source) end private diff --git a/app/models/notification.rb b/app/models/notification.rb deleted file mode 100644 index 3805bde88b0..00000000000 --- a/app/models/notification.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Notification - attr_accessor :target - - delegate :disabled?, :participating?, :watch?, :global?, :mention?, to: :target - - def initialize(target) - @target = target - end - - def level - target.notification_level - end -end diff --git a/app/models/user.rb b/app/models/user.rb index f556dc5903d..af6b86bfa70 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -357,10 +357,6 @@ class User < ActiveRecord::Base "#{self.class.reference_prefix}#{username}" end - def notification - @notification ||= Notification.new(self) - end - def generate_password if self.force_random_password self.password = self.password_confirmation = Devise.friendly_token.first(8) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 23f211dfcd2..0928dda349e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -329,25 +329,31 @@ class NotificationService # Remove users with disabled notifications from array # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) - reject_users(users, :disabled?, project) + reject_users(users, :disabled, project) end # Remove users with notification level 'Mentioned' def reject_mention_users(users, project = nil) - reject_users(users, :mention?, project) + reject_users(users, :mention, project) end - # Reject users which method_name from notification object returns true. + # Reject users which has certain notification level # # Example: - # reject_users(users, :watch?, project) + # reject_users(users, :watch, project) # - def reject_users(users, method_name, project = nil) + def reject_users(users, level, project = nil) + level = level.to_s + + unless NotificationSetting.levels.keys.include?(level) + raise 'Invalid notification level' + end + users = users.to_a.compact.uniq users = users.reject(&:blocked?) users.reject do |user| - next user.notification.send(method_name) unless project + next user.notification_level == level unless project setting = user.notification_settings.find_by(source: project) @@ -356,13 +362,13 @@ class NotificationService end # reject users who globally set mention notification and has no setting per project/group - next user.notification.send(method_name) unless setting + next user.notification_level == level unless setting # reject users who set mention notification in project - next true if setting.send(method_name) + next true if setting.level == level # reject users who have mention level in project and disabled in global settings - setting.global? && user.notification.send(method_name) + setting.global? && user.notification_level == level end end -- cgit v1.2.1 From 630c86a7a36cee36ed6b9c93644a6cb51e2b3b23 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 13:42:48 +0200 Subject: Add spec for user_id uniq in NotificationSetting model Signed-off-by: Dmitriy Zaporozhets --- spec/models/notification_setting_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index f31b2a3cd6f..295081e9da1 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -7,10 +7,11 @@ RSpec.describe NotificationSetting, type: :model do end describe "Validation" do - subject { NotificationSetting.new } + subject { NotificationSetting.new(source_id: 1, source_type: 'Project') } it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:level) } + it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) } end end -- cgit v1.2.1 From 71e7b398431506c8bac2e8e6014b0f3891a41f95 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 13:52:42 +0200 Subject: Refactor creating notification setting with defaults Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects_controller.rb | 7 +------ app/models/member.rb | 7 +------ app/models/notification_setting.rb | 11 +++++++++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 77122f59128..e2dc6309d26 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -102,12 +102,7 @@ class ProjectsController < Projects::ApplicationController @membership = @project.team.find_member(current_user.id) if @membership - @notification_setting = current_user.notification_settings.find_or_initialize_by(source: @project) - - unless @notification_setting.persisted? - @notification_setting.set_defaults - @notification_setting.save - end + @notification_setting = current_user.notification_settings.find_or_create_for(@project) end end diff --git a/app/models/member.rb b/app/models/member.rb index cbcc54c0250..747d0f16d8d 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -163,12 +163,7 @@ class Member < ActiveRecord::Base end def create_notification_setting - notification_setting = user.notification_settings.find_or_initialize_by(source: source) - - unless notification_setting.persisted? - notification_setting.set_defaults - notification_setting.save - end + user.notification_setting.find_or_create_for(source) end def notification_setting diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 287862a01bc..13a8995b036 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -15,6 +15,17 @@ class NotificationSetting < ActiveRecord::Base scope :for_groups, -> { where(source_type: 'Namespace') } scope :for_projects, -> { where(source_type: 'Project') } + def self.find_or_create_for(source) + setting = find_or_initialize_by(source: source) + + unless setting.persisted? + setting.set_defaults + setting.save + end + + setting + end + def set_defaults self.level = :global end -- cgit v1.2.1 From f8f68d6b8c5b5d85b4798257ae3ae4bf4ec8fadc Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 14:03:23 +0200 Subject: Fix few bugs related to recent notifications refactoring Signed-off-by: Dmitriy Zaporozhets --- app/controllers/profiles/notifications_controller.rb | 1 - app/models/member.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 6ca7537300f..0cca5d1e330 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -1,7 +1,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController def show @user = current_user - @notification = current_user.notification @group_notifications = current_user.notification_settings.for_groups @project_notifications = current_user.notification_settings.for_projects end diff --git a/app/models/member.rb b/app/models/member.rb index 747d0f16d8d..7d5af1d5c8a 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -163,7 +163,7 @@ class Member < ActiveRecord::Base end def create_notification_setting - user.notification_setting.find_or_create_for(source) + user.notification_settings.find_or_create_for(source) end def notification_setting -- cgit v1.2.1 From 5583197e091e8f75ad9c99a1bbc46e6a0b7279d4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 17:42:38 +0200 Subject: Create NotificationSettings object only when user change value in dropdown Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/project.js.coffee | 9 ++++++++- .../projects/notification_settings_controller.rb | 22 ++++++++++++++++++++++ app/controllers/projects_controller.rb | 3 ++- .../projects/buttons/_notifications.html.haml | 5 ++--- config/routes.rb | 1 + 5 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 app/controllers/projects/notification_settings_controller.rb diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index f171442d05a..07be85a32a5 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -38,12 +38,19 @@ class @Project e.preventDefault() notification_level = $(@).data 'notification-level' label = $(@).data 'notification-title' - $('#notification_level').val(notification_level) + $('#notification_setting_level').val(notification_level) $('#notification-form').submit() $('#notifications-button').empty().append("" + label + "") $(@).parents('ul').find('li.active').removeClass 'active' $(@).parent().addClass 'active' + $('#notification-form').on 'ajax:success', (e, data) -> + if data.saved + new Flash("Notification settings saved", "notice") + else + new Flash("Failed to save new settings", "alert") + + @projectSelectDropdown() projectSelectDropdown: -> diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb new file mode 100644 index 00000000000..3ecf63d107f --- /dev/null +++ b/app/controllers/projects/notification_settings_controller.rb @@ -0,0 +1,22 @@ +class Projects::NotificationSettingsController < Projects::ApplicationController + def create + notification_setting = project.notification_settings.new(notification_setting_params) + notification_setting.user = current_user + saved = notification_setting.save + + render json: { saved: saved } + end + + def update + notification_setting = project.notification_settings.where(user_id: current_user).find(params[:id]) + saved = notification_setting.update_attributes(notification_setting_params) + + render json: { saved: saved } + end + + private + + def notification_setting_params + params.require(:notification_setting).permit(:level) + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e2dc6309d26..ec5318f2d2c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -102,7 +102,8 @@ class ProjectsController < Projects::ApplicationController @membership = @project.team.find_member(current_user.id) if @membership - @notification_setting = current_user.notification_settings.find_or_create_for(@project) + @notification_setting = current_user.notification_settings.find_or_initialize_by(source: @project) + @notification_setting.set_defaults unless @notification_setting.persisted? end end diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index 4b8a10f0819..2b9d8f2ac81 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,7 +1,6 @@ - if @notification_setting - = form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do - = hidden_field_tag :notification_id, @notification_setting.id - = hidden_field_tag :notification_level, @notification_setting.level + = form_for [@project.namespace.becomes(Namespace), @project, @notification_setting], remote: true, html: { class: 'inline', id: 'notification-form' } do |f| + = f.hidden_field :level %span.dropdown %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} = icon('bell') diff --git a/config/routes.rb b/config/routes.rb index 6bf22fb4456..c665cefbb57 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -606,6 +606,7 @@ Rails.application.routes.draw do resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] + resources :notification_settings, only: [:create, :update] resources :refs, only: [] do collection do -- cgit v1.2.1 From 729fe42bff474535c9eebb0b73974a79756372b8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 17:56:12 +0200 Subject: Improve project notification settings explanation Signed-off-by: Dmitriy Zaporozhets --- app/views/profiles/notifications/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index f6900f61b2d..e9c8ae28544 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -65,7 +65,7 @@ %h5 Projects (#{@project_notifications.count}) %p.account-well - To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group. + To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there. .append-bottom-default %ul.bordered-list - @project_notifications.each do |setting| -- cgit v1.2.1 From 26631f9981a826ebe4aeba726e9be2b813d2c5c5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 18:59:03 +0200 Subject: Change how notification settings in profile are rendered and updated Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/profile.js.coffee | 7 ++- .../groups/notification_settings_controller.rb | 14 +++++ .../profiles/notifications_controller.rb | 28 ++------- .../notifications/_group_settings.html.haml | 13 ++++ .../notifications/_project_settings.html.haml | 13 ++++ .../profiles/notifications/_settings.html.haml | 17 ----- app/views/profiles/notifications/show.html.haml | 72 +++++++++++----------- app/views/profiles/notifications/update.js.haml | 6 -- config/routes.rb | 1 + 9 files changed, 89 insertions(+), 82 deletions(-) create mode 100644 app/controllers/groups/notification_settings_controller.rb create mode 100644 app/views/profiles/notifications/_group_settings.html.haml create mode 100644 app/views/profiles/notifications/_project_settings.html.haml delete mode 100644 app/views/profiles/notifications/_settings.html.haml delete mode 100644 app/views/profiles/notifications/update.js.haml diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index ae87c6c4e40..f4a2562885d 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -18,8 +18,11 @@ class @Profile $(this).find('.btn-save').enable() $(this).find('.loading-gif').hide() - $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enable() + $('.update-notifications').on 'ajax:success', (e, data) -> + if data.saved + new Flash("Notification settings saved", "notice") + else + new Flash("Failed to save new settings", "alert") @bindEvents() diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb new file mode 100644 index 00000000000..78e43c83aba --- /dev/null +++ b/app/controllers/groups/notification_settings_controller.rb @@ -0,0 +1,14 @@ +class Groups::NotificationSettingsController < Groups::ApplicationController + def update + notification_setting = group.notification_settings.where(user_id: current_user).find(params[:id]) + saved = notification_setting.update_attributes(notification_setting_params) + + render json: { saved: saved } + end + + private + + def notification_setting_params + params.require(:notification_setting).permit(:level) + end +end diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 0cca5d1e330..18ee55c839a 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -6,29 +6,13 @@ class Profiles::NotificationsController < Profiles::ApplicationController end def update - type = params[:notification_type] - - @saved = if type == 'global' - current_user.update_attributes(user_params) - else - notification_setting = current_user.notification_settings.find(params[:notification_id]) - notification_setting.level = params[:notification_level] - notification_setting.save - end - - respond_to do |format| - format.html do - if @saved - flash[:notice] = "Notification settings saved" - else - flash[:alert] = "Failed to save new settings" - end - - redirect_back_or_default(default: profile_notifications_path) - end - - format.js + if current_user.update_attributes(user_params) + flash[:notice] = "Notification settings saved" + else + flash[:alert] = "Failed to save new settings" end + + redirect_back_or_default(default: profile_notifications_path) end def user_params diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml new file mode 100644 index 00000000000..89ae7ffda2b --- /dev/null +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -0,0 +1,13 @@ +%li.notification-list-item + %span.notification.fa.fa-holder.append-right-5 + - if setting.global? + = notification_icon(current_user.notification_level) + - else + = notification_icon(setting.level) + + %span.str-truncated + = link_to group.name, group_path(group) + + .pull-right + = form_for [group, setting], remote: true, html: { class: 'update-notifications' } do |f| + = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml new file mode 100644 index 00000000000..17c097154da --- /dev/null +++ b/app/views/profiles/notifications/_project_settings.html.haml @@ -0,0 +1,13 @@ +%li.notification-list-item + %span.notification.fa.fa-holder.append-right-5 + - if setting.global? + = notification_icon(current_user.notification_level) + - else + = notification_icon(setting.level) + + %span.str-truncated + = link_to_project(project) + + .pull-right + = form_for [project.namespace.becomes(Namespace), project, setting], remote: true, html: { class: 'update-notifications' } do |f| + = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml deleted file mode 100644 index c32de0b9925..00000000000 --- a/app/views/profiles/notifications/_settings.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%li.notification-list-item - %span.notification.fa.fa-holder.append-right-5 - - if setting.global? - = notification_icon(current_user.notification_level) - - else - = notification_icon(setting.level) - - %span.str-truncated - - if setting.source.kind_of? Project - = link_to_project(setting.source) - - else - = link_to setting.source.name, group_path(setting.source) - .pull-right - = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do - = hidden_field_tag :notification_id, setting.id - = hidden_field_tag :notification_level, setting.level - = select_tag :notification_level, options_for_select(User.notification_levels.keys, setting.level), class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index e9c8ae28544..a2a505c082b 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,8 +1,8 @@ - page_title "Notifications" - header_title page_title, profile_notifications_path -= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| - -if @user.errors.any? +%div + - if @user.errors.any? %div.alert.alert-danger %ul - @user.errors.full_messages.each do |msg| @@ -20,48 +20,50 @@ .col-lg-9 %h5 Global notification settings - .form-group - = f.label :notification_email, class: "label-light" - = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" - .form-group - = f.label :notification_level, class: 'label-light' - .radio - = f.label :notification_level, value: :disabled do - = f.radio_button :notification_level, :disabled - .level-title - Disabled - %p You will not get any notifications via email - .radio - = f.label :notification_level, value: :mention do - = f.radio_button :notification_level, :mention - .level-title - On Mention - %p You will receive notifications only for comments in which you were @mentioned + = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| + .form-group + = f.label :notification_email, class: "label-light" + = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" + .form-group + = f.label :notification_level, class: 'label-light' + .radio + = f.label :notification_level, value: :disabled do + = f.radio_button :notification_level, :disabled + .level-title + Disabled + %p You will not get any notifications via email - .radio - = f.label :notification_level, value: :participating do - = f.radio_button :notification_level, :participating - .level-title - Participating - %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + .radio + = f.label :notification_level, value: :mention do + = f.radio_button :notification_level, :mention + .level-title + On Mention + %p You will receive notifications only for comments in which you were @mentioned - .radio - = f.label :notification_level, value: :watch do - = f.radio_button :notification_level, :watch - .level-title - Watch - %p You will receive notifications for any activity + .radio + = f.label :notification_level, value: :participating do + = f.radio_button :notification_level, :participating + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - .prepend-top-default - = f.submit 'Update settings', class: "btn btn-create" + .radio + = f.label :notification_level, value: :watch do + = f.radio_button :notification_level, :watch + .level-title + Watch + %p You will receive notifications for any activity + + .prepend-top-default + = f.submit 'Update settings', class: "btn btn-create" %hr %h5 Groups (#{@group_notifications.count}) %div %ul.bordered-list - @group_notifications.each do |setting| - = render 'settings', setting: setting + = render 'group_settings', setting: setting, group: setting.source %h5 Projects (#{@project_notifications.count}) %p.account-well @@ -69,4 +71,4 @@ .append-bottom-default %ul.bordered-list - @project_notifications.each do |setting| - = render 'settings', setting: setting + = render 'project_settings', setting: setting, project: setting.source diff --git a/app/views/profiles/notifications/update.js.haml b/app/views/profiles/notifications/update.js.haml deleted file mode 100644 index 84c6ab25599..00000000000 --- a/app/views/profiles/notifications/update.js.haml +++ /dev/null @@ -1,6 +0,0 @@ -- if @saved - :plain - new Flash("Notification settings saved", "notice") -- else - :plain - new Flash("Failed to save new settings", "alert") diff --git a/config/routes.rb b/config/routes.rb index c665cefbb57..7f03fbf6af9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -406,6 +406,7 @@ Rails.application.routes.draw do resource :avatar, only: [:destroy] resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] + resources :notification_settings, only: [:update] end end -- cgit v1.2.1 From 949431fa02da18257c8b7c7a24c03faa04c02c5e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 22:19:00 +0200 Subject: Update API to use notification_level from notification_setting Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f686c568bee..f414c1f9885 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -255,14 +255,19 @@ module API expose :id, :path, :kind end - class ProjectAccess < Grape::Entity + class Member < Grape::Entity expose :access_level - expose :notification_level + expose :notification_level do |member, options| + if member.notification_setting + NotificationSetting.levels[member.notification_setting.level] + end + end end - class GroupAccess < Grape::Entity - expose :access_level - expose :notification_level + class ProjectAccess < Member + end + + class GroupAccess < Member end class ProjectService < Grape::Entity -- cgit v1.2.1 From 49f9873ce2a88fb38f23f7eb09471e8b58aebe1d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 29 Mar 2016 22:20:59 +0200 Subject: Add changelog item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 2cd06d90257..88edf1e8484 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.7.0 (unreleased) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) + - Decouple membership and notifications v 8.6.2 (unreleased) - Comments on confidential issues don't show up in activity feed to non-members -- cgit v1.2.1 From 065faac3a390f29b57db5261e9eab4efa076554c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 30 Mar 2016 10:32:19 +0200 Subject: Test changing notification settings per project fron notificaitons page Signed-off-by: Dmitriy Zaporozhets --- features/profile/notifications.feature | 6 ++++++ features/steps/profile/notifications.rb | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/features/profile/notifications.feature b/features/profile/notifications.feature index 55997d44dec..ef8743932f5 100644 --- a/features/profile/notifications.feature +++ b/features/profile/notifications.feature @@ -7,3 +7,9 @@ Feature: Profile Notifications Scenario: I visit notifications tab When I visit profile notifications page Then I should see global notifications settings + + @javascript + Scenario: I edit Project Notifications + Given I visit profile notifications page + When I select Mention setting from dropdown + Then I should see Notification saved message diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index 447ea6d9d10..a96f35ada51 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -9,4 +9,14 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps step 'I should see global notifications settings' do expect(page).to have_content "Notifications" end + + step 'I select Mention setting from dropdown' do + select 'mention', from: 'notification_setting_level' + end + + step 'I should see Notification saved message' do + page.within '.flash-container' do + expect(page).to have_content 'Notification settings saved' + end + end end -- cgit v1.2.1 From 1a293a43847b30d4357116f830a1b22d370c4a6f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 30 Mar 2016 10:38:03 +0200 Subject: Update migration comment Signed-off-by: Dmitriy Zaporozhets --- db/migrate/20160328115649_migrate_new_notification_setting.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb index aff866b5f46..6a68890f5b1 100644 --- a/db/migrate/20160328115649_migrate_new_notification_setting.rb +++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb @@ -1,6 +1,10 @@ # This migration will create one row of NotificationSetting for each Member row -# It can take long time on big instances. Its unclear yet if this migration can be done online. -# This comment should be updated by @dzaporozhets before 8.7 release. If not - please ask him to do so. +# It can take long time on big instances. +# +# This migration can be done online but with following effects: +# - during migration some users will receive notifications based on their global settings (project/group settings will be ignored) +# - its possible to get duplicate records for notification settings since we don't create uniq index yet +# class MigrateNewNotificationSetting < ActiveRecord::Migration def up timestamp = Time.now -- cgit v1.2.1 From b69f8a62b2078fbd43413c670dea76871b74d0d5 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 30 Mar 2016 15:04:58 -0300 Subject: Add specific markdown_preview route for Wikis --- app/controllers/projects/wikis_controller.rb | 14 ++++++++++++++ app/views/layouts/project.html.haml | 6 +++++- config/routes.rb | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 02ceb8f4334..9f3a4a69721 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController ) end + def markdown_preview + text = params[:text] + + ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user) + ext.analyze(text) + + render json: { + body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki), + references: { + users: ext.users.map(&:username) + } + } + end + def git_access end diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index ab527e8e438..a7ef31acd3d 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -5,10 +5,14 @@ - content_for :scripts_body_top do - project = @target_project || @project + - if @project_wiki + - markdown_preview_path = namespace_project_wikis_markdown_preview_path(project.namespace, project) + - else + - markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project) - if current_user :javascript window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}"; - window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}"; + window.markdown_preview_path = "#{markdown_preview_path}"; - content_for :scripts_body do = render "layouts/init_auto_complete" if current_user diff --git a/config/routes.rb b/config/routes.rb index 6bf22fb4456..e57c04595f6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -575,6 +575,7 @@ Rails.application.routes.draw do # Order matters to give priority to these matches get '/wikis/git_access', to: 'wikis#git_access' get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis/markdown_preview', to:'wikis#markdown_preview' post '/wikis', to: 'wikis#create' get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID -- cgit v1.2.1 From 7d56d592cd7d1df718d59981af1ced9d0be8eaac Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 30 Mar 2016 15:30:36 -0300 Subject: Fixed Gollum pages link url expansion to render correctly in preview --- lib/banzai/filter/gollum_tags_filter.rb | 12 +++++++++--- spec/lib/banzai/filter/gollum_tags_filter_spec.rb | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index 7ce26db1b90..cfd3e28ed09 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -144,12 +144,18 @@ module Banzai # if it is not. def process_page_link_tag(parts) if parts.size == 1 - url = parts[0].strip + reference = parts[0].strip else - name, url = *parts.compact.map(&:strip) + name, reference = *parts.compact.map(&:strip) end - content_tag(:a, name || url, href: url) + if url?(reference) + href = reference + else + href = ::File.join(project_wiki_base_path, reference) + end + + content_tag(:a, name || reference, href: href) end def project_wiki diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 5e23c5c319a..ad777fe4b9c 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -70,20 +70,22 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do end context 'linking internal resources' do - it "the created link's text will be equal to the resource's text" do + it "the created link's text will include the resource's text and project_wiki_base_path" do tag = '[[wiki-slug]]' doc = filter("See #{tag}", project_wiki: project_wiki) + expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug') expect(doc.at_css('a').text).to eq 'wiki-slug' - expect(doc.at_css('a')['href']).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path end it "the created link's text will be link-text" do tag = '[[link-text|wiki-slug]]' doc = filter("See #{tag}", project_wiki: project_wiki) + expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug') expect(doc.at_css('a').text).to eq 'link-text' - expect(doc.at_css('a')['href']).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path end end -- cgit v1.2.1 From ccc929d75d1f34accc4c7a6604c26cac6113a18c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 31 Mar 2016 11:50:00 -0500 Subject: CSS tweaks - Use colors according to design - Fix search input' width --- app/assets/stylesheets/framework/variables.scss | 8 ++++---- app/assets/stylesheets/pages/search.scss | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index bb49ae396c7..b86dbcb04b6 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -199,11 +199,11 @@ $award-emoji-new-btn-icon-color: #dcdcdc; /* * Search Box */ -$search-input-border-color: $dropdown-input-focus-border; +$search-input-border-color: rgba(#4688f1, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; -$search-input-width: $dropdown-width; +$search-input-width: 244px; $location-badge-color: #aaa; $location-badge-bg: $gray-normal; +$location-badge-active-bg: #4f91f8; $location-icon-color: #e7e9ed; -$location-active-color: $gl-text-color; -$location-active-bg: $search-input-border-color; +$location-icon-active-color: #807e7e; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 3c74d25beb0..9274796233b 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -135,13 +135,13 @@ .location-badge { @include transition(all .15s); - background-color: $location-active-bg; + background-color: $location-badge-active-bg; color: $white-light; } .search-input-wrap { i { - color: $location-active-color; + color: $location-icon-active-color; } } -- cgit v1.2.1 From e3a983050714aa54a3c7911666cdd0abbc36e57a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 31 Mar 2016 15:28:45 -0500 Subject: Skip default behaviour if we are clicking a result for the same location --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- app/assets/javascripts/search_autocomplete.js.coffee | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 0fea2a69cb7..e4df7d46d51 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -174,7 +174,7 @@ class GitLabDropdown selected = self.rowClicked $(@) if self.options.clicked - self.options.clicked(selected) + self.options.clicked(selected, e) # Finds an element inside wrapper element getElement: (selector) -> diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 030655491bf..2656c6e30a2 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -62,6 +62,8 @@ class @SearchAutocomplete search: fields: ['text'] data: @getData.bind(@) + selectable: true + clicked: @onClick.bind(@) getData: (term, callback) -> _this = @ @@ -268,3 +270,9 @@ class @SearchAutocomplete
  • Loading...
  • " @dropdownContent.html(html) + + onClick: (item, e) -> + if location.pathname.indexOf(item.url) isnt -1 + e.preventDefault() + @disableAutocomplete() + @searchInput.val('') -- cgit v1.2.1 From ff676333d0d257869a917a5e48e9717d219b8311 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 31 Mar 2016 19:28:17 -0500 Subject: Add current element and event as params to clicked callback --- app/assets/javascripts/gl_dropdown.js.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index e4df7d46d51..d447013f637 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -171,10 +171,11 @@ class GitLabDropdown selector = ".dropdown-page-one .dropdown-content a" @dropdown.on "click", selector, (e) -> - selected = self.rowClicked $(@) + $el = $(@) + selected = self.rowClicked $el if self.options.clicked - self.options.clicked(selected, e) + self.options.clicked(selected, $el, e) # Finds an element inside wrapper element getElement: (selector) -> -- cgit v1.2.1 From ab42560ea6c2fade09b8436a893bf4a269e67c39 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 31 Mar 2016 19:28:41 -0500 Subject: Return selected object if toggleLabel option is not defined --- app/assets/javascripts/gl_dropdown.js.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index d447013f637..ca8d9a16940 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -352,6 +352,8 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel $(@el).find(".dropdown-toggle-text").text @options.toggleLabel + else + selectedObject else if !value? field.remove() -- cgit v1.2.1 From 824fecb728ae7534b8ef56aaa6679814f3924cd5 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 31 Mar 2016 19:31:01 -0500 Subject: Bring back search context when chosing the same project/group --- .../javascripts/search_autocomplete.js.coffee | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 2656c6e30a2..564fb265b9d 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -104,6 +104,8 @@ class @SearchAutocomplete lastCategory = suggestion.category data.push + id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}" + category: suggestion.category text: suggestion.label url: suggestion.url @@ -271,8 +273,27 @@ class @SearchAutocomplete " @dropdownContent.html(html) - onClick: (item, e) -> + onClick: (item, $el, e) -> if location.pathname.indexOf(item.url) isnt -1 e.preventDefault() + if not @badgePresent + if item.category is 'Projects' + @projectInputEl.val(item.id) + @addLocationBadge( + value: 'This project' + ) + + if item.category is 'Groups' + @groupInputEl.val(item.id) + @addLocationBadge( + value: 'This group' + ) + + $el.removeClass('is-active') @disableAutocomplete() - @searchInput.val('') + @searchInput.val('').focus() + + # We need to wait because of @skipBlurEvent + setTimeout( => + @onSearchInputFocus() + , 200) -- cgit v1.2.1 From 28266d9d610123d20333c6b05fcf83d1fb3e336a Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Apr 2016 00:17:37 -0300 Subject: Added WikiLinkFilter --- lib/banzai/filter/wiki_link_filter.rb | 52 +++++++++++++++++++++++++++++++++++ lib/banzai/pipeline/wiki_pipeline.rb | 6 ++-- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 lib/banzai/filter/wiki_link_filter.rb diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb new file mode 100644 index 00000000000..7a585eff653 --- /dev/null +++ b/lib/banzai/filter/wiki_link_filter.rb @@ -0,0 +1,52 @@ +require 'uri' + +module Banzai + module Filter + # HTML filter that "fixes" relative links to files in a repository. + # + # Context options: + # :project_wiki + class WikiLinkFilter < HTML::Pipeline::Filter + + def call + return doc unless project_wiki? + + doc.search('a:not(.gfm)').each do |el| + process_link_attr el.attribute('href') + end + + doc + end + + protected + + def project_wiki? + !context[:project_wiki].nil? + end + + def process_link_attr(html_attr) + return if html_attr.blank? + + uri = URI(html_attr.value) + if uri.relative? && uri.path.present? && uri.path + html_attr.value = rebuild_wiki_uri(uri).to_s + end + rescue URI::Error + # noop + end + + def rebuild_wiki_uri(uri) + uri.path = ::File.join(project_wiki_base_path, uri.path) + uri + end + + def project_wiki + context[:project_wiki] + end + + def project_wiki_base_path + project_wiki && project_wiki.wiki_base_path + end + end + end +end diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index 0b5a9e0b2b8..1cdb3808961 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -2,8 +2,10 @@ module Banzai module Pipeline class WikiPipeline < FullPipeline def self.filters - @filters ||= super.insert_after(Filter::TableOfContentsFilter, - Filter::GollumTagsFilter) + @filters ||= begin + super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter) + .insert_after(Filter::GollumTagsFilter, Filter::WikiLinkFilter) + end end end end -- cgit v1.2.1 From fb8b0041d86080f0f8d53f13be1016b0b199a47f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Mar 2016 18:53:30 -0400 Subject: First pass at a Testing styleguide [ci skip] --- CONTRIBUTING.md | 2 +- doc/development/README.md | 1 + doc/development/testing.md | 105 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 doc/development/testing.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 511336f384c..1f26a5d7eaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -448,7 +448,7 @@ merge request: - multi-line method chaining style **Option B**: dot `.` on previous line - string literal quoting style **Option A**: single quoted by default 1. [Rails](https://github.com/bbatsov/rails-style-guide) -1. [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing) +1. [Testing](doc/development/testing.md) 1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript) 1. [SCSS styleguide][scss-styleguide] 1. [Shell commands](doc/development/shell_commands.md) created by GitLab diff --git a/doc/development/README.md b/doc/development/README.md index 1b281809afc..a8bc4fe5abb 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -9,4 +9,5 @@ - [Shell commands](shell_commands.md) in the GitLab codebase - [Sidekiq debugging](sidekiq_debugging.md) - [SQL guidelines](sql.md) for SQL guidelines +- [Testing standards and style guidelines](testing.md) - [UI guide](ui_guide.md) for building GitLab with existing css styles and elements diff --git a/doc/development/testing.md b/doc/development/testing.md new file mode 100644 index 00000000000..d37ef5d8785 --- /dev/null +++ b/doc/development/testing.md @@ -0,0 +1,105 @@ +# Testing Standards and Style Guidelines + +This guide outlines standards and best practices for automated testing of GitLab +CE and EE. + +It is meant to be an _extension_ of the [thoughtbot testing +styleguide](https://github.com/thoughtbot/guides/tree/master/style/testing). If +this guide defines a rule that contradicts the thoughtbot guide, this guide +takes precedence. Some guidelines may be repeated verbatim to stress their +importance. + +## Factories + +GitLab uses [factory_girl] as a test +fixture replacement. + +- Factory definitions live in `spec/factories/`, named using the pluralization + of their corresponding model (`User` factories are defined in `users.rb`). +- There should be only one top-level factory definition per file. +- Make use of [Traits] to clean up definitions and usages. +- When defining a factory, don't define attributes that are not required for the + resulting record to pass validation. +- When instantiating from a factory, don't supply extraneous attributes that + aren't required by the test. + +[factory_girl]: https://github.com/thoughtbot/factory_girl +[Traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits + +## JavaScript + +GitLab uses [Teaspoon] to run its [Jasmine] JavaScript specs. They can be run on +the command line via `bundle exec teaspoon`, or via a web browser at +`http://localhost:3000/teaspoon` when the Rails server is running. + +- JavaScript tests live in `spec/javascripts/`, matching the folder structure of + `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.coffee` has a corresponding + `spec/javascripts/behaviors/autosize_spec.js.coffee` file. +- Haml fixtures required for JavaScript tests live in + `spec/javascripts/fixtures`. They should contain the bare minimum amount of + markup necessary for the test. + + > **Warning:** Keep in mind that a Rails view may change and + invalidate your test, but everything will still pass because your fixture + doesn't reflect the latest view. + +- Keep in mind that in a CI environment, these tests are run in a headless + browser and you will not have access to certain APIs, such as + [`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification), + which will have to be stubbed. + +[Teaspoon]: https://github.com/modeset/teaspoon +[Jasmine]: https://github.com/jasmine/jasmine + +## RSpec + +### General Guidelines + +- Use a single, top-level `describe ClassName` block. +- Use `described_class` instead of repeating the class name being described. +- Use `.method` to describe class methods and `#method` to describe instance + methods. +- Use `context` to test branching logic. +- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). +- Prefer `not_to` to `to_not`. +- Try to match the ordering of tests to the ordering within the class. + +### Test speed + +GitLab has a massive test suite that, without parallelization, can take more +than an hour to run. It's important that we make an effort to write tests that +are accurate and effective _as well as_ fast. + +Here are some things to keep in mind regarding test performance: + +- `double` and `spy` are faster than `FactoryGirl.build(...)` +- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`. +- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, + `spy`, or `double` will do. Database persistence is slow! +- Use `create(:empty_project)` instead of `create(:project)` when you don't need + the underlying repository. Filesystem operations are slow! +- Don't mark a feature as requiring JavaScript (through `@javascript` in + Spinach or `js: true` in RSpec) unless it's _actually_ required for the test + to be valid. Headless browser testing is slow! + +### Features / Integration + +- Feature specs live in `spec/features/` and should be named + `ROLE_ACTION_spec.rb`, such as `user_changes_password_spec.rb`. +- Use only one `feature` block per feature spec file. +- Use scenario titles that describe the success and failure paths. +- Avoid scenario titles that add no information, such as "successfully." +- Avoid scenario titles that repeat the feature title. + +## Spinach (feature) tests + +GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426) +for its feature/integration tests in September 2012. + +As of March 2016, we are [trying to avoid adding new Spinach +tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward, +opting for [RSpec feature](#features-integration) specs. + +Adding new Spinach scenarios is acceptable _only if_ the new scenario requires +no more than one new `step` definition. If more than that is required, the +test should be re-implemented using RSpec instead. -- cgit v1.2.1 From 0b9d9816f8ef1d871a050cea5d8bc3d9203c3d18 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Mar 2016 19:11:02 -0400 Subject: Add a note about Four-Phase Test to Testing guide [ci skip] --- doc/development/testing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/testing.md b/doc/development/testing.md index d37ef5d8785..a7e85ecebed 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -63,6 +63,8 @@ the command line via `bundle exec teaspoon`, or via a web browser at - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Prefer `not_to` to `to_not`. - Try to match the ordering of tests to the ordering within the class. +- Try to follow the [Four-Phase Test](https://robots.thoughtbot.com/four-phase-test) + pattern, using newlines to separate phases. ### Test speed -- cgit v1.2.1 From 60f4081e135bdcd893d60192e652c4b829c656dd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Apr 2016 20:29:48 -0400 Subject: Factories don't have to be limited to `ActiveRecord` objects [ci skip] --- doc/development/testing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/testing.md b/doc/development/testing.md index a7e85ecebed..3d1c4ccab47 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -22,6 +22,8 @@ fixture replacement. resulting record to pass validation. - When instantiating from a factory, don't supply extraneous attributes that aren't required by the test. +- Factories don't have to be limited to `ActiveRecord` objects. + [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). [factory_girl]: https://github.com/thoughtbot/factory_girl [Traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits -- cgit v1.2.1 From 2e83b562030e078ce6d37f81915590426a57c820 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Apr 2016 20:30:24 -0400 Subject: Add a section about `let` to the Testing guide [ci skip] --- doc/development/testing.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/doc/development/testing.md b/doc/development/testing.md index 3d1c4ccab47..187d852e533 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -11,8 +11,7 @@ importance. ## Factories -GitLab uses [factory_girl] as a test -fixture replacement. +GitLab uses [factory_girl] as a test fixture replacement. - Factory definitions live in `spec/factories/`, named using the pluralization of their corresponding model (`User` factories are defined in `users.rb`). @@ -65,8 +64,30 @@ the command line via `bundle exec teaspoon`, or via a web browser at - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Prefer `not_to` to `to_not`. - Try to match the ordering of tests to the ordering within the class. -- Try to follow the [Four-Phase Test](https://robots.thoughtbot.com/four-phase-test) - pattern, using newlines to separate phases. +- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines + to separate phases. + +[four-phase-test]: https://robots.thoughtbot.com/four-phase-test + +### `let` variables + +GitLab's RSpec suite has made extensive use of `let` variables to reduce +duplication. However, this sometimes [comes at the cost of clarity][lets-not], +so we need to set some guidelines for their use going forward: + +- `let` variables are preferable to instance variables. Local variables are + preferable to `let` variables. +- Use `let` to reduce duplication throughout an entire spec file. +- Don't use `let` to define variables used by a single test; define them as + local variables inside the test's `it` block. +- Don't define a `let` variable inside the top-level `describe` block that's + only used in a more deeply-nested `context` or `describe` block. Keep the + definition as close as possible to where it's used. +- Try to avoid overriding the definition of one `let` variable with another. +- Don't define a `let` variable that's only used by the definition of another. + Use a helper method instead. + +[lets-not]: https://robots.thoughtbot.com/lets-not ### Test speed -- cgit v1.2.1 From 6ffda88c973ce9be263d0e9142a3105f2cccf000 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Apr 2016 20:36:44 -0400 Subject: Add a link back to Development documentation to Testing guide [ci skip] --- doc/development/testing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/development/testing.md b/doc/development/testing.md index 187d852e533..23417845f16 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -128,3 +128,7 @@ opting for [RSpec feature](#features-integration) specs. Adding new Spinach scenarios is acceptable _only if_ the new scenario requires no more than one new `step` definition. If more than that is required, the test should be re-implemented using RSpec instead. + +--- + +[Return to Development documentation](README.md) -- cgit v1.2.1 From 0163e27631fb993bd3541c09a95f0ef5e2026455 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 16 Mar 2016 18:10:03 +0100 Subject: Add Gitlab::Redis connection pool --- config/application.rb | 4 ++-- config/initializers/session_store.rb | 2 +- config/initializers/sidekiq.rb | 4 ++-- config/mail_room.yml | 4 ++-- lib/gitlab/exclusive_lease.rb | 4 +++- lib/gitlab/redis.rb | 35 +++++++++++++++++++++++++++++++++++ lib/gitlab/redis_config.rb | 30 ------------------------------ lib/tasks/cache.rake | 25 +++++++++++++------------ 8 files changed, 58 insertions(+), 50 deletions(-) create mode 100644 lib/gitlab/redis.rb delete mode 100644 lib/gitlab/redis_config.rb diff --git a/config/application.rb b/config/application.rb index 5a0ac70aa2a..9633084d603 100644 --- a/config/application.rb +++ b/config/application.rb @@ -4,7 +4,7 @@ require 'rails/all' require 'devise' I18n.config.enforce_available_locales = false Bundler.require(:default, Rails.env) -require_relative '../lib/gitlab/redis_config' +require_relative '../lib/gitlab/redis' module Gitlab REDIS_CACHE_NAMESPACE = 'cache:gitlab' @@ -69,7 +69,7 @@ module Gitlab end end - redis_config_hash = Gitlab::RedisConfig.redis_store_options + redis_config_hash = Gitlab::Redis.redis_store_options redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever config.cache_store = :redis_store, redis_config_hash diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 3da5d46be92..70285255877 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -13,7 +13,7 @@ end if Rails.env.test? Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" else - redis_config = Gitlab::RedisConfig.redis_store_options + redis_config = Gitlab::Redis.redis_store_options redis_config[:namespace] = 'session:gitlab' Gitlab::Application.config.session_store( diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index cc83137745a..9182d929809 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -2,7 +2,7 @@ SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab' Sidekiq.configure_server do |config| config.redis = { - url: Gitlab::RedisConfig.url, + url: Gitlab::Redis.url, namespace: SIDEKIQ_REDIS_NAMESPACE } @@ -29,7 +29,7 @@ end Sidekiq.configure_client do |config| config.redis = { - url: Gitlab::RedisConfig.url, + url: Gitlab::Redis.url, namespace: SIDEKIQ_REDIS_NAMESPACE } end diff --git a/config/mail_room.yml b/config/mail_room.yml index 60257329f3e..761a32adb9e 100644 --- a/config/mail_room.yml +++ b/config/mail_room.yml @@ -2,7 +2,7 @@ <% require "yaml" require "json" -require_relative "lib/gitlab/redis_config" +require_relative "lib/gitlab/redis" rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" @@ -18,7 +18,7 @@ if File.exists?(config_file) config['mailbox'] = "inbox" if config['mailbox'].nil? if config['enabled'] && config['address'] - redis_url = Gitlab::RedisConfig.new(rails_env).url + redis_url = Gitlab::Redis.new(rails_env).url %> - :host: <%= config['host'].to_json %> diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index c73eca832d7..c2260a5f7ac 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -43,7 +43,9 @@ module Gitlab # false if the lease is already taken. def try_obtain # Performing a single SET is atomic - !!redis.set(redis_key, '1', nx: true, ex: @timeout) + Gitlab::Redis.with do |redis| + !!redis.set(redis_key, '1', nx: true, ex: @timeout) + end end # No #cancel method. See comments above! diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb new file mode 100644 index 00000000000..d03e6e4cd92 --- /dev/null +++ b/lib/gitlab/redis.rb @@ -0,0 +1,35 @@ +module Gitlab + class Redis + attr_reader :url + + def self.url + @url ||= new.url + end + + def self.with + @pool ||= ConnectionPool.new { ::Redis.new(url: url) } + @pool.with { |redis| yield redis } + end + + def self.redis_store_options + url = new.url + redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url) + # Redis::Store does not handle Unix sockets well, so let's do it for them + redis_uri = URI.parse(url) + if redis_uri.scheme == 'unix' + redis_config_hash[:path] = redis_uri.path + end + redis_config_hash + end + + def initialize(rails_env=nil) + rails_env ||= Rails.env + config_file = File.expand_path('../../../config/resque.yml', __FILE__) + + @url = "redis://localhost:6379" + if File.exists?(config_file) + @url =YAML.load_file(config_file)[rails_env] + end + end + end +end diff --git a/lib/gitlab/redis_config.rb b/lib/gitlab/redis_config.rb deleted file mode 100644 index 4949c6db539..00000000000 --- a/lib/gitlab/redis_config.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Gitlab - class RedisConfig - attr_reader :url - - def self.url - new.url - end - - def self.redis_store_options - url = new.url - redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(url) - # Redis::Store does not handle Unix sockets well, so let's do it for them - redis_uri = URI.parse(url) - if redis_uri.scheme == 'unix' - redis_config_hash[:path] = redis_uri.path - end - redis_config_hash - end - - def initialize(rails_env=nil) - rails_env ||= Rails.env - config_file = File.expand_path('../../../config/resque.yml', __FILE__) - - @url = "redis://localhost:6379" - if File.exists?(config_file) - @url =YAML.load_file(config_file)[rails_env] - end - end - end -end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 51e746ef923..6c2e2e91494 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -4,18 +4,19 @@ namespace :cache do desc "GitLab | Clear redis cache" task :clear => :environment do - redis = Redis.new(url: Gitlab::RedisConfig.url) - cursor = REDIS_SCAN_START_STOP - loop do - cursor, keys = redis.scan( - cursor, - match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", - count: CLEAR_BATCH_SIZE - ) - - redis.del(*keys) if keys.any? - - break if cursor == REDIS_SCAN_START_STOP + Gitlab::Redis.with do |redis| + cursor = REDIS_SCAN_START_STOP + loop do + cursor, keys = redis.scan( + cursor, + match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", + count: CLEAR_BATCH_SIZE + ) + + redis.del(*keys) if keys.any? + + break if cursor == REDIS_SCAN_START_STOP + end end end end -- cgit v1.2.1 From 213ee62469c6518af8423f00fb902b7665d61204 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 18 Mar 2016 15:31:53 +0100 Subject: Be careful when setting class instance vars --- lib/gitlab/redis.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index d03e6e4cd92..8c3aea2627c 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -2,12 +2,23 @@ module Gitlab class Redis attr_reader :url + # To be thread-safe we must be careful when writing the class instance + # variables @url and @pool. Because @pool depends on @url we need two + # mutexes to prevent deadlock. + URL_MUTEX = Mutex.new + POOL_MUTEX = Mutex.new + private_constant :URL_MUTEX, :POOL_MUTEX + def self.url - @url ||= new.url + @url || URL_MUTEX.synchronize { @url = new.url } end def self.with - @pool ||= ConnectionPool.new { ::Redis.new(url: url) } + if @pool.nil? + POOL_MUTEX.synchronize do + @pool = ConnectionPool.new { ::Redis.new(url: url) } + end + end @pool.with { |redis| yield redis } end -- cgit v1.2.1 From 1a168279fa3eb87c2061917707397af21e7b26ea Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 4 Apr 2016 19:09:12 -0500 Subject: Prepare SAML for group retrieval --- lib/gitlab/saml/auth_hash.rb | 17 +++++++++++++++++ lib/gitlab/saml/config.rb | 22 ++++++++++++++++++++++ lib/gitlab/saml/user.rb | 43 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/saml/auth_hash.rb create mode 100644 lib/gitlab/saml/config.rb diff --git a/lib/gitlab/saml/auth_hash.rb b/lib/gitlab/saml/auth_hash.rb new file mode 100644 index 00000000000..5ffccc0e100 --- /dev/null +++ b/lib/gitlab/saml/auth_hash.rb @@ -0,0 +1,17 @@ +module Gitlab + module Saml + class AuthHash < Gitlab::OAuth::AuthHash + + def groups + get_raw(Gitlab::Saml::Config.groups) + end + + private + + def get_raw(key) + auth_hash.extra[:raw_info][key] + end + + end + end +end diff --git a/lib/gitlab/saml/config.rb b/lib/gitlab/saml/config.rb new file mode 100644 index 00000000000..dade4c0fa6a --- /dev/null +++ b/lib/gitlab/saml/config.rb @@ -0,0 +1,22 @@ +# Load a specific server configuration +module Gitlab + module Saml + class Config + + class << self + def options + Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' } + end + + def groups + options['groups_attribute'] + end + + def external_groups + options['external_groups'] + end + end + + end + end +end diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index b1e30110ef5..14eda337d9a 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -7,6 +7,11 @@ module Gitlab module Saml class User < Gitlab::OAuth::User + def initialize(auth_hash) + super + update_user_attributes + end + def save super('SAML') end @@ -18,7 +23,7 @@ module Gitlab @user ||= find_or_create_ldap_user end - if auto_link_saml_enabled? + if auto_link_saml_user? @user ||= find_by_email end @@ -37,11 +42,45 @@ module Gitlab end end + def changed? + gl_user.changed? || gl_user.identities.any?(&:changed?) + end + protected - def auto_link_saml_enabled? + def build_new_user + user = super + if external_users_enabled? + unless (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? + user.external = true + end + end + user + end + + def auto_link_saml_user? Gitlab.config.omniauth.auto_link_saml_user end + + def external_users_enabled? + !Gitlab::Saml::Config.external_groups.nil? + end + + def auth_hash=(auth_hash) + @auth_hash = Gitlab::Saml::AuthHash.new(auth_hash) + end + + def update_user_attributes + if persisted? + if external_users_enabled? + if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? + gl_user.external = false + else + gl_user.external = true + end + end + end + end end end end -- cgit v1.2.1 From d14f080c0a7e4cfb2893a9fb2462a650f02a6ef9 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 4 Apr 2016 19:09:46 -0500 Subject: Remove unnecessary LDAP tests from SAML tests --- spec/lib/gitlab/saml/user_spec.rb | 66 +++++---------------------------------- 1 file changed, 7 insertions(+), 59 deletions(-) diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index de7cd99d49d..6f5cf3a1cf5 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -23,6 +23,12 @@ describe Gitlab::Saml::User, lib: true do allow(Gitlab::LDAP::Config).to receive_messages(messages) end + def stub_saml_config(messages) + allow(Gitlab::Saml::Config).to receive_messages(messages) + end + + before { stub_saml_config({ options: { name: 'saml', args: {} } }) } + describe 'account exists on server' do before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } context 'and should bind with SAML' do @@ -138,7 +144,7 @@ describe Gitlab::Saml::User, lib: true do end describe 'blocking' do - before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) } + before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } context 'signup with SAML only' do context 'dont block on create' do @@ -162,64 +168,6 @@ describe Gitlab::Saml::User, lib: true do end end - context 'signup with linked omniauth and LDAP account' do - before do - stub_omniauth_config(auto_link_ldap_user: true) - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } - allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } - allow(saml_user).to receive(:ldap_person).and_return(ldap_user) - end - - context "and no account for the LDAP user" do - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).to be_blocked - end - end - end - - context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } - - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - end - end - - context 'sign-in' do before do saml_user.save -- cgit v1.2.1 From 943d8d4b90fbdc4f68d548c0566343903f895138 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 4 Apr 2016 19:10:17 -0500 Subject: Config examples --- config/gitlab.yml.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index fb1c3476f65..c607e32a05d 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -345,6 +345,8 @@ production: &base # # - { name: 'saml', # label: 'Our SAML Provider', + # groups_attribute: 'Groups', + # external_groups: ['Contractors', 'Freelancers'], # args: { # assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', # idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', -- cgit v1.2.1 From e99855bfe4b4741d33d5575fdf1f0bc2edd85844 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 4 Apr 2016 19:10:59 -0500 Subject: Avoid saving again if the user attributes haven't changed --- app/controllers/omniauth_callbacks_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 21135f7d607..d28e96c3f18 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -55,7 +55,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end else saml_user = Gitlab::Saml::User.new(oauth) - saml_user.save + saml_user.save if saml_user.changed? @user = saml_user.gl_user continue_login_process -- cgit v1.2.1 From 7a2370f74060b2f065e3602700fe1b33fda4685c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Apr 2016 21:25:38 -0400 Subject: Standardize the way we check for and display form errors - Some views had a "Close" button. We've removed this, because we don't want users accidentally hiding the validation errors and not knowing what needs to be fixed. - Some views used `li`, some used `p`, some used `span`. We've standardized on `li`. - Some views only showed the first error. We've standardized on showing all of them. - Some views added an `#error_explanation` div, which we've made standard. --- app/helpers/form_helper.rb | 18 +++++++++ app/views/abuse_reports/new.html.haml | 6 +-- app/views/admin/appearances/_form.html.haml | 5 +-- .../admin/application_settings/_form.html.haml | 6 +-- app/views/admin/applications/_form.html.haml | 7 +--- app/views/admin/broadcast_messages/_form.html.haml | 6 +-- app/views/admin/deploy_keys/new.html.haml | 6 +-- app/views/admin/groups/_form.html.haml | 5 +-- app/views/admin/hooks/index.html.haml | 6 +-- app/views/admin/identities/_form.html.haml | 6 +-- app/views/admin/labels/_form.html.haml | 8 +--- app/views/admin/users/_form.html.haml | 6 +-- app/views/doorkeeper/applications/_form.html.haml | 6 +-- app/views/groups/edit.html.haml | 4 +- app/views/groups/new.html.haml | 5 +-- app/views/profiles/keys/_form.html.haml | 6 +-- app/views/profiles/notifications/show.html.haml | 6 +-- app/views/profiles/passwords/edit.html.haml | 7 +--- app/views/profiles/passwords/new.html.haml | 7 +--- app/views/profiles/show.html.haml | 7 +--- app/views/projects/_errors.html.haml | 5 +-- app/views/projects/deploy_keys/_form.html.haml | 6 +-- app/views/projects/hooks/index.html.haml | 6 +-- app/views/projects/labels/_form.html.haml | 8 +--- .../projects/merge_requests/_new_compare.html.haml | 5 +-- app/views/projects/milestones/_form.html.haml | 7 +--- .../projects/protected_branches/index.html.haml | 6 +-- app/views/projects/variables/show.html.haml | 8 +--- app/views/projects/wikis/_form.html.haml | 6 +-- app/views/shared/_service_settings.html.haml | 7 +--- app/views/shared/issuable/_form.html.haml | 9 +---- app/views/shared/snippets/_form.html.haml | 6 +-- spec/helpers/form_helper_spec.rb | 46 ++++++++++++++++++++++ 33 files changed, 105 insertions(+), 153 deletions(-) create mode 100644 app/helpers/form_helper.rb create mode 100644 spec/helpers/form_helper_spec.rb diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb new file mode 100644 index 00000000000..6a43be2cf3e --- /dev/null +++ b/app/helpers/form_helper.rb @@ -0,0 +1,18 @@ +module FormHelper + def form_errors(model) + return unless model.errors.any? + + pluralized = 'error'.pluralize(model.errors.count) + headline = "The form contains the following #{pluralized}:" + + content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do + content_tag(:h4, headline) << + content_tag(:ul) do + model.errors.full_messages. + map { |msg| content_tag(:li, msg) }. + join. + html_safe + end + end + end +end diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index 3bc1b24b5e2..06be1a53318 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -3,11 +3,9 @@ %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr = form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f| + = form_errors(@abuse_report) + = f.hidden_field :user_id - - if @abuse_report.errors.any? - .alert.alert-danger - - @abuse_report.errors.full_messages.each do |msg| - %p= msg .form-group = f.label :user_id, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 6f325914d14..d88f3ad314d 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -1,8 +1,5 @@ = form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| - - if @appearance.errors.any? - .alert.alert-danger - - @appearance.errors.full_messages.each do |msg| - %p= msg + = form_errors(@appearance) %fieldset.sign-in %legend diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index de86dacbb12..a8cca1a81cb 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -1,9 +1,5 @@ = form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| - - if @application_setting.errors.any? - #error_explanation - .alert.alert-danger - - @application_setting.errors.full_messages.each do |msg| - %p= msg + = form_errors(@application_setting) %fieldset %legend Visibility and Access Controls diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index e18f7b499dd..4aacbb8cd77 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -1,9 +1,6 @@ = form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - - application.errors.full_messages.each do |msg| - %p= msg + = form_errors(application) + = content_tag :div, class: 'form-group' do = f.label :name, class: 'col-sm-2 control-label' .col-sm-10 diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index b748460a9f7..6b157abf842 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -4,10 +4,8 @@ = render_broadcast_message(@broadcast_message.message.presence || "Your message here") = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| - -if @broadcast_message.errors.any? - .alert.alert-danger - - @broadcast_message.errors.full_messages.each do |msg| - %p= msg + = form_errors(@broadcast_message) + .form-group = f.label :message, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 5b46b3222a9..15aa059c93d 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -4,11 +4,7 @@ %div = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| - -if @deploy_key.errors.any? - .alert.alert-danger - %ul - - @deploy_key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@deploy_key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 7f2b1cd235d..0cc405401cf 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -1,8 +1,5 @@ = form_for [:admin, @group], html: { class: "form-horizontal" } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f .form-group.group-description-holder diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 53b3cd04c68..ad952052f25 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -10,10 +10,8 @@ = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL:", class: 'control-label' .col-sm-10 diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml index 3a788558226..112a201fafa 100644 --- a/app/views/admin/identities/_form.html.haml +++ b/app/views/admin/identities/_form.html.haml @@ -1,9 +1,5 @@ = form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| - - if @identity.errors.any? - #error_explanation - .alert.alert-danger - - @identity.errors.full_messages.each do |msg| - %p= msg + = form_errors(@identity) .form-group = f.label :provider, class: 'control-label' diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index 8c6b389bf15..448aa953548 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -1,11 +1,5 @@ = form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| - -if @label.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index d2527ede995..b05fdbd5552 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -1,10 +1,6 @@ .user_new = form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f| - -if @user.errors.any? - #error_explanation - .alert.alert-danger - - @user.errors.full_messages.each do |msg| - %p= msg + = form_errors(@user) %fieldset %legend Account diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index 906b0676150..5c98265727a 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -1,9 +1,5 @@ = form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %ul - - application.errors.full_messages.each do |msg| - %li= msg + = form_errors(application) .form-group = f.label :name, class: 'label-light' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index ea5a0358392..a698cbbe9db 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -5,9 +5,7 @@ Group settings .panel-body = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first + = form_errors(@group) = render 'shared/group_form', f: f .form-group diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 30ab8aeba13..2b8bc269e64 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -6,10 +6,7 @@ %hr = form_for @group, html: { class: 'group-form form-horizontal' } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 4d78215ed3c..b3ed59a1a4a 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -1,10 +1,6 @@ %div = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f| - - if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :key, class: 'label-light' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 3d15c0d932b..6609295a2a5 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -2,11 +2,7 @@ - header_title page_title, profile_notifications_path = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| - -if @user.errors.any? - %div.alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) = hidden_field_tag :notification_type, 'global' .row diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 44d758dceb3..5ac8a8b9d09 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -13,11 +13,8 @@ - unless @user.password_automatically_set? or recover your current one = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + - unless @user.password_automatically_set? .form-group = f.label :current_password, class: 'label-light' diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index d165f758c81..2eb9fac57c3 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -7,11 +7,8 @@ Please set a new password before proceeding. %br After a successful password update you will be redirected to login screen. - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + + = form_errors(@user) - unless @user.password_automatically_set? .form-group diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index dcb3be9585d..f59d27f7ed0 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,9 +1,6 @@ = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| - -if @user.errors.any? - %div.alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + .row .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml index 7c8bb33ed7e..2dba22d3be6 100644 --- a/app/views/projects/_errors.html.haml +++ b/app/views/projects/_errors.html.haml @@ -1,4 +1 @@ -- if @project.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - = @project.errors.full_messages.first += form_errors(@project) diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 5e182af2669..f6565f85836 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,10 +1,6 @@ %div = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f| - -if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 67d016bd871..e39224d86c6 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -9,10 +9,8 @@ %hr.clearfix = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL", class: 'control-label' .col-sm-10 diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index be7a0bb5628..aa143e54ffe 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,11 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - -if @label.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 01dc7519bee..0931f743a35 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -28,10 +28,7 @@ .mr_target_commit - if @merge_request.errors.any? - .alert.alert-danger - - @merge_request.errors.full_messages.each do |msg| - %div= msg - + = form_errors(@merge_request) - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? .light-well.append-bottom-default .center diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 23f2bca7baf..b2dae1c70ee 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,9 +1,6 @@ = form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f| - -if @milestone.errors.any? - .alert.alert-danger - %ul - - @milestone.errors.full_messages.each do |msg| - %li= msg + = form_errors(@milestone) + .row .col-md-6 .form-group diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index cfd7e1534ca..653b02da4db 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -13,11 +13,7 @@ - if can? current_user, :admin_project, @project = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| - -if @protected_branch.errors.any? - .alert.alert-danger - %ul - - @protected_branch.errors.full_messages.each do |msg| - %li= msg + = form_errors(@protected_branch) .form-group = f.label :name, "Branch", class: 'control-label' diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml index efe1e6f24c2..ca284b84d39 100644 --- a/app/views/projects/variables/show.html.haml +++ b/app/views/projects/variables/show.html.haml @@ -13,13 +13,7 @@ = nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| - - if @project.errors.any? - #error_explanation - %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:" - .alert.alert-error - %ul - - @project.errors.full_messages.each do |msg| - %li= msg + = form_errors(@project) = f.fields_for :variables do |variable_form| .form-group diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index f0d1932e23c..812876e2835 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,9 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f| - -if @page.errors.any? - #error_explanation - .alert.alert-danger - - @page.errors.full_messages.each do |msg| - %p= msg + = form_errors(@page) = f.hidden_field :title, value: @page.title .form-group diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 5a60ff5a5da..fc935166bf6 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -1,9 +1,4 @@ -- if @service.errors.any? - #error_explanation - .alert.alert-danger - %ul - - @service.errors.full_messages.each do |msg| - %li= msg += form_errors(@service) - if @service.help.present? .well diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index e2a9e5bfb92..0cda785f91e 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,10 +1,5 @@ -- if issuable.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - issuable.errors.full_messages.each do |msg| - %span= msg - %br += form_errors(issuable) + .form-group = f.label :title, class: 'control-label' .col-sm-10 diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 1041eccd1df..47ec09f62c6 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -1,10 +1,6 @@ .snippet-form-holder = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| - - if @snippet.errors.any? - .alert.alert-danger - %ul - - @snippet.errors.full_messages.each do |msg| - %li= msg + = form_errors(@snippet) .form-group = f.label :title, class: 'control-label' diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb new file mode 100644 index 00000000000..b20373a96fb --- /dev/null +++ b/spec/helpers/form_helper_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe FormHelper do + describe 'form_errors' do + it 'returns nil when model has no errors' do + model = double(errors: []) + + expect(helper.form_errors(model)).to be_nil + end + + it 'renders an alert div' do + model = double(errors: errors_stub('Error 1')) + + expect(helper.form_errors(model)). + to include('
    ') + end + + it 'contains a summary message' do + single_error = double(errors: errors_stub('A')) + multi_errors = double(errors: errors_stub('A', 'B', 'C')) + + expect(helper.form_errors(single_error)). + to include('

    The form contains the following error:') + expect(helper.form_errors(multi_errors)). + to include('

    The form contains the following errors:') + end + + it 'renders each message' do + model = double(errors: errors_stub('Error 1', 'Error 2', 'Error 3')) + + errors = helper.form_errors(model) + + aggregate_failures do + expect(errors).to include('
  • Error 1
  • ') + expect(errors).to include('
  • Error 2
  • ') + expect(errors).to include('
  • Error 3
  • ') + end + end + + def errors_stub(*messages) + ActiveModel::Errors.new(double).tap do |errors| + messages.each { |msg| errors.add(:base, msg) } + end + end + end +end -- cgit v1.2.1 From 831d1807df6d1d9f8213898614a24bea78208114 Mon Sep 17 00:00:00 2001 From: "P.S.V.R" Date: Tue, 5 Apr 2016 16:20:08 +0800 Subject: Eliminate ugly scroll bars shown in modal boxes --- app/assets/stylesheets/pages/help.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index bd224705f04..604f1700cf8 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -59,6 +59,9 @@ position: relative; overflow-y: auto; padding: 15px; + .form-actions { + margin: -$gl-padding+1; + } } body.modal-open { -- cgit v1.2.1 From 1ba9a91c6d3b98e1825e173fe281ba065d35890c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 31 Mar 2016 16:11:49 -0300 Subject: Fix problem when creating milestones in groups without projects --- app/controllers/groups/milestones_controller.rb | 28 ++++++++++++++++++---- .../groups/milestones_controller_spec.rb | 6 +++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index b23c3022fb5..2c05d9e0fe1 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -18,14 +18,14 @@ class Groups::MilestonesController < Groups::ApplicationController end def create - project_ids = params[:milestone][:project_ids] + project_ids = params[:milestone][:project_ids].reject(&:blank?) title = milestone_params[:title] - @projects.where(id: project_ids).each do |project| - Milestones::CreateService.new(project, current_user, milestone_params).execute + if project_ids.present? + create_milestones(project_ids, title) + else + render_new_with_error("Select a project(s).") end - - redirect_to milestone_path(title) end def show @@ -41,6 +41,24 @@ class Groups::MilestonesController < Groups::ApplicationController private + def create_milestones(project_ids, title) + begin + @projects.where(id: project_ids).each do |project| + ActiveRecord::Base.transaction { Milestones::CreateService.new(project, current_user, milestone_params).execute } + end + + redirect_to milestone_path(title) + rescue => e + render_new_with_error("Error creating milestones: #{e.message}") + end + end + + def render_new_with_error(error) + @milestone = Milestone.new(milestone_params) + flash[:alert] = error + render :new + end + def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestones, group) end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index eb0c6ac6d80..f258d6fa0c7 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -23,5 +23,11 @@ describe Groups::MilestonesController do expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title)) expect(Milestone.where(title: title).count).to eq(2) end + + it "redirects to new when there are no project ids" do + post :create, group_id: group.id, milestone: { title: title, project_ids: [""] } + expect(response).to render_template :new + expect(flash[:alert]).to_not be_nil + end end end -- cgit v1.2.1 From 5d428030451b1fa2bac89f798c40d2f91ac65bac Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 1 Apr 2016 15:50:17 -0300 Subject: Improve code --- app/controllers/groups/milestones_controller.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 2c05d9e0fe1..21fc329f233 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -21,10 +21,10 @@ class Groups::MilestonesController < Groups::ApplicationController project_ids = params[:milestone][:project_ids].reject(&:blank?) title = milestone_params[:title] - if project_ids.present? - create_milestones(project_ids, title) + if create_milestones(project_ids, title) + redirect_to milestone_path(title) else - render_new_with_error("Select a project(s).") + render_new_with_error(@error) end end @@ -42,14 +42,22 @@ class Groups::MilestonesController < Groups::ApplicationController private def create_milestones(project_ids, title) + unless project_ids.present? + @error = "Please select at least one project." + return false + end + begin - @projects.where(id: project_ids).each do |project| - ActiveRecord::Base.transaction { Milestones::CreateService.new(project, current_user, milestone_params).execute } + ActiveRecord::Base.transaction do + @projects.where(id: project_ids).each do |project| + Milestones::CreateService.new(project, current_user, milestone_params).execute + end end - redirect_to milestone_path(title) + true rescue => e - render_new_with_error("Error creating milestones: #{e.message}") + @error = "Error creating milestone: #{e.message}." + false end end -- cgit v1.2.1 From 32c7e42b612bdda43eeef55d8c8afdc9eeb33785 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 4 Apr 2016 17:04:35 -0300 Subject: Improve code --- app/controllers/groups/milestones_controller.rb | 29 ++++++++++------------ app/views/groups/milestones/new.html.haml | 8 ++++++ .../groups/milestones_controller_spec.rb | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 21fc329f233..fcf19e8066a 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -24,7 +24,7 @@ class Groups::MilestonesController < Groups::ApplicationController if create_milestones(project_ids, title) redirect_to milestone_path(title) else - render_new_with_error(@error) + render_new_with_error(project_ids.empty?) end end @@ -42,28 +42,25 @@ class Groups::MilestonesController < Groups::ApplicationController private def create_milestones(project_ids, title) - unless project_ids.present? - @error = "Please select at least one project." - return false - end + return false unless project_ids.present? - begin - ActiveRecord::Base.transaction do - @projects.where(id: project_ids).each do |project| - Milestones::CreateService.new(project, current_user, milestone_params).execute - end + ActiveRecord::Base.transaction do + @projects.where(id: project_ids).each do |project| + Milestones::CreateService.new(project, current_user, milestone_params).execute end + end + + true - true rescue => e - @error = "Error creating milestone: #{e.message}." - false - end + + flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}" + false end - def render_new_with_error(error) + def render_new_with_error(empty_project_ids) @milestone = Milestone.new(milestone_params) - flash[:alert] = error + @milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids render :new end diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index a8e1ed77da9..4290e0bf72e 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -10,6 +10,14 @@ = form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f| .row + - if @milestone.errors.any? + #error_explanation + .alert.alert-danger + %ul + - @milestone.errors.full_messages.each do |msg| + %li + = msg + .col-md-6 .form-group = f.label :title, "Title", class: "control-label" diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index f258d6fa0c7..9c7b5c74b8e 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -27,7 +27,7 @@ describe Groups::MilestonesController do it "redirects to new when there are no project ids" do post :create, group_id: group.id, milestone: { title: title, project_ids: [""] } expect(response).to render_template :new - expect(flash[:alert]).to_not be_nil + expect(assigns(:milestone).errors).to_not be_nil end end end -- cgit v1.2.1 From 5ff9d2a1821ef0413a349388bcdc0fab84a17086 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 28 Mar 2016 15:37:53 -0400 Subject: Bump rails to 4.2.6 --- Gemfile | 2 +- Gemfile.lock | 72 ++++++++++++++++++++++----------------------- config/environments/test.rb | 1 + 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Gemfile b/Gemfile index 6327227282a..5eaaf0cfb1c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem 'rails', '4.2.5.2' +gem 'rails', '4.2.6' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Responders respond_to and respond_with diff --git a/Gemfile.lock b/Gemfile.lock index 229089f431d..df18410ecec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,41 +4,41 @@ GEM CFPropertyList (2.3.2) RedCloth (4.2.9) ace-rails-ap (2.0.1) - actionmailer (4.2.5.2) - actionpack (= 4.2.5.2) - actionview (= 4.2.5.2) - activejob (= 4.2.5.2) + actionmailer (4.2.6) + actionpack (= 4.2.6) + actionview (= 4.2.6) + activejob (= 4.2.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.5.2) - actionview (= 4.2.5.2) - activesupport (= 4.2.5.2) + actionpack (4.2.6) + actionview (= 4.2.6) + activesupport (= 4.2.6) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.5.2) - activesupport (= 4.2.5.2) + actionview (4.2.6) + activesupport (= 4.2.6) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.5.2) - activesupport (= 4.2.5.2) + activejob (4.2.6) + activesupport (= 4.2.6) globalid (>= 0.3.0) - activemodel (4.2.5.2) - activesupport (= 4.2.5.2) + activemodel (4.2.6) + activesupport (= 4.2.6) builder (~> 3.1) - activerecord (4.2.5.2) - activemodel (= 4.2.5.2) - activesupport (= 4.2.5.2) + activerecord (4.2.6) + activemodel (= 4.2.6) + activesupport (= 4.2.6) arel (~> 6.0) activerecord-deprecated_finders (1.0.4) activerecord-session_store (0.1.2) actionpack (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5) railties (>= 4.0.0, < 5) - activesupport (4.2.5.2) + activesupport (4.2.6) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -464,8 +464,8 @@ GEM nokogiri (>= 1.5.9) macaddr (1.7.1) systemu (~> 2.6.2) - mail (2.6.3) - mime-types (>= 1.16, < 3) + mail (2.6.4) + mime-types (>= 1.16, < 4) mail_room (0.6.1) method_source (0.8.2) mime-types (1.25.1) @@ -595,16 +595,16 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.5.2) - actionmailer (= 4.2.5.2) - actionpack (= 4.2.5.2) - actionview (= 4.2.5.2) - activejob (= 4.2.5.2) - activemodel (= 4.2.5.2) - activerecord (= 4.2.5.2) - activesupport (= 4.2.5.2) + rails (4.2.6) + actionmailer (= 4.2.6) + actionpack (= 4.2.6) + actionview (= 4.2.6) + activejob (= 4.2.6) + activemodel (= 4.2.6) + activerecord (= 4.2.6) + activesupport (= 4.2.6) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.5.2) + railties (= 4.2.6) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -614,9 +614,9 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.5.2) - actionpack (= 4.2.5.2) - activesupport (= 4.2.5.2) + railties (4.2.6) + actionpack (= 4.2.6) + activesupport (= 4.2.6) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.1.0) @@ -788,10 +788,10 @@ GEM spring (>= 0.9.1) sprockets (3.3.5) rack (> 1, < 3) - sprockets-rails (2.3.3) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) + sprockets-rails (3.0.4) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) state_machines (0.4.0) state_machines-activemodel (0.3.0) activemodel (~> 4.1) @@ -1002,7 +1002,7 @@ DEPENDENCIES rack-attack (~> 4.3.1) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) - rails (= 4.2.5.2) + rails (= 4.2.6) rails-deprecated_sanitizer (~> 1.0.3) raphael-rails (~> 2.1.2) rblineprof diff --git a/config/environments/test.rb b/config/environments/test.rb index f96ac6f9753..a703c0934f7 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -8,6 +8,7 @@ Rails.application.configure do config.cache_classes = false # Configure static asset server for tests with Cache-Control for performance + config.assets.digest = false config.serve_static_files = true config.static_cache_control = "public, max-age=3600" -- cgit v1.2.1 From 0ab6b82a234f6dcc208591724036f152eca0e1c4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 5 Apr 2016 12:09:19 -0700 Subject: Update shades of red --- app/assets/stylesheets/framework/variables.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 98fe794d362..dd189a2cb79 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -104,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80); $orange-normal: #e75e40; $orange-dark: #ce5237; -$red-light: #f06559; -$red-normal: #e52c5a; -$red-dark: #d22852; +$red-light: #e52c5a; +$red-normal: #d22852; +$red-dark: darken($red-normal, 5%); $border-white-light: #f1f2f4; $border-white-normal: #d6dae2; @@ -128,9 +128,9 @@ $border-orange-light: #fc6d26; $border-orange-normal: #ce5237; $border-orange-dark: #c14e35; -$border-red-light: #f24f41; -$border-red-normal: #d22852; -$border-red-dark: #ca264f; +$border-red-light: #d22852; +$border-red-normal: #ca264f; +$border-red-dark: darken($border-red-normal, 5%); $help-well-bg: #fafafa; $help-well-border: #e5e5e5; -- cgit v1.2.1 From 5ee6badade3c453c7090e9c1f1f4d636c5bb068e Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 5 Apr 2016 16:33:37 -0300 Subject: Unblocks user when active_directory is disabled and it can be found --- lib/gitlab/ldap/access.rb | 5 ++++- spec/lib/gitlab/ldap/access_spec.rb | 27 ++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index da4435c7308..f2b649e50a2 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -33,7 +33,10 @@ module Gitlab def allowed? if ldap_user - return true unless ldap_config.active_directory + unless ldap_config.active_directory + user.activate if user.ldap_blocked? + return true + end # Block user in GitLab if he/she was blocked in AD if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index 32a19bf344b..f5b66b8156f 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::LDAP::Access, lib: true do it { is_expected.to be_falsey } - it 'should block user in GitLab' do + it 'blocks user in GitLab' do access.allowed? expect(user).to be_blocked expect(user).to be_ldap_blocked @@ -78,6 +78,31 @@ describe Gitlab::LDAP::Access, lib: true do end it { is_expected.to be_truthy } + + context 'when user cannot be found' do + before do + allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) + end + + it { is_expected.to be_falsey } + + it 'blocks user in GitLab' do + access.allowed? + expect(user).to be_blocked + expect(user).to be_ldap_blocked + end + end + + context 'when user was previously ldap_blocked' do + before do + user.ldap_block + end + + it 'unblocks the user if it exists' do + access.allowed? + expect(user).not_to be_blocked + end + end end end end -- cgit v1.2.1 From 3911e77eb4244f010af4050c140bc2d08e3c34df Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 5 Apr 2016 23:08:22 +0200 Subject: Remove TODO for not documented stuff --- doc/development/ui_guide.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md index 2f01defc11d..a3e260a5f89 100644 --- a/doc/development/ui_guide.md +++ b/doc/development/ui_guide.md @@ -1,9 +1,5 @@ # UI Guide for building GitLab -## Best practices for creating new pages in GitLab - -TODO: write some best practices when develop GitLab features. - ## GitLab UI development kit We created a page inside GitLab where you can check commonly used html and css elements. -- cgit v1.2.1 From d2afeb8dde7837688bdaaf8575d6621a8b42ba7f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 17:29:40 -0500 Subject: Do not add location badge when creating a group or project --- app/views/layouts/_search.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 9d4ab9847a8..ce9df1566d5 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,6 +1,6 @@ -- if controller.controller_path =~ /^groups/ +- if controller.controller_path =~ /^groups/ && !@group.new_record? - label = 'This group' -- if controller.controller_path =~ /^projects/ +- if controller.controller_path =~ /^projects/ && !@project.new_record? - label = 'This project' .search.search-form{class: "#{'has-location-badge' if label.present?}"} -- cgit v1.2.1 From 7baebd322008e0ca912c447792f1d7cffd74f8e2 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 19:18:34 -0500 Subject: Do not create input if fieldName is not defined --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 6b55917ec89..362ddda3596 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -369,7 +369,7 @@ class GitLabDropdown if @options.toggleLabel $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) if value? - if !field.length + if !field.length and fieldName # Create hidden input for form input = "" if @options.inputId? -- cgit v1.2.1 From 518ec6b2660c55beba2833ce71b93774ed0a6c2a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 5 Apr 2016 19:20:18 -0500 Subject: Changed config syntax and improved how chaanges in group memberships are handled when external groups is set up --- lib/gitlab/saml/config.rb | 4 ++-- lib/gitlab/saml/user.rb | 39 ++++++++++++--------------------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/lib/gitlab/saml/config.rb b/lib/gitlab/saml/config.rb index dade4c0fa6a..2b3cf840f61 100644 --- a/lib/gitlab/saml/config.rb +++ b/lib/gitlab/saml/config.rb @@ -9,11 +9,11 @@ module Gitlab end def groups - options['groups_attribute'] + options[:groups_attribute] end def external_groups - options['external_groups'] + options[:external_groups] end end diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index 14eda337d9a..6ab165cf518 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -7,11 +7,6 @@ module Gitlab module Saml class User < Gitlab::OAuth::User - def initialize(auth_hash) - super - update_user_attributes - end - def save super('SAML') end @@ -31,6 +26,18 @@ module Gitlab @user ||= build_new_user end + if external_users_enabled? + # Check if there is overlap between the user's groups and the external groups + # setting and set user as external or internal. + if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? + # Avoid an unnecessary change of values and the subsequent save + @user.external = false if @user.external + else + # Avoid an unnecessary change of values and the subsequent save + @user.external = true unless @user.external + end + end + @user end @@ -48,16 +55,6 @@ module Gitlab protected - def build_new_user - user = super - if external_users_enabled? - unless (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? - user.external = true - end - end - user - end - def auto_link_saml_user? Gitlab.config.omniauth.auto_link_saml_user end @@ -69,18 +66,6 @@ module Gitlab def auth_hash=(auth_hash) @auth_hash = Gitlab::Saml::AuthHash.new(auth_hash) end - - def update_user_attributes - if persisted? - if external_users_enabled? - if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? - gl_user.external = false - else - gl_user.external = true - end - end - end - end end end end -- cgit v1.2.1 From 7efaf22bccb16b381f7e76054d084e741006fc5f Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 5 Apr 2016 19:22:58 -0500 Subject: Removed extra LDAP tests and added tests for the external groups feature --- spec/lib/gitlab/saml/user_spec.rb | 68 ++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 6f5cf3a1cf5..84a26af940b 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do let(:gl_user) { saml_user.gl_user } let(:uid) { 'my-uid' } let(:provider) { 'saml' } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: { groups: %w(Developers Freelancers Designers) } }) } let(:info_hash) do { name: 'John', @@ -31,8 +31,8 @@ describe Gitlab::Saml::User, lib: true do describe 'account exists on server' do before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } + let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } context 'and should bind with SAML' do - let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } it 'adds the SAML identity to the existing user' do saml_user.save expect(gl_user).to be_valid @@ -42,6 +42,32 @@ describe Gitlab::Saml::User, lib: true do expect(identity.provider).to eql 'saml' end end + + context 'external groups' do + context 'are defined' do + before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } + it 'marks the user as external' do + saml_user.save + expect(gl_user.external).to be_truthy + end + end + + before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Interns), args: {} } }) } + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + saml_user.save + expect(gl_user.external).to be_falsey + end + end + + context 'user was external, now should not be' do + it 'should make user internal' do + existing_user.update_attribute('external', true) + saml_user.save + expect(gl_user.external).to be_falsey + end + end + end end describe 'no account exists on server' do @@ -74,6 +100,24 @@ describe Gitlab::Saml::User, lib: true do end end + context 'external groups' do + context 'are defined' do + before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } + it 'marks the user as external' do + saml_user.save + expect(gl_user.external).to be_truthy + end + end + + before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Interns), args: {} } }) } + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + saml_user.save + expect(gl_user.external).to be_falsey + end + end + end + context 'with auto_link_ldap_user disabled (default)' do before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) } include_examples 'to verify compliance with allow_single_sign_on' @@ -193,26 +237,6 @@ describe Gitlab::Saml::User, lib: true do expect(gl_user).not_to be_blocked end end - - context 'dont block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create (LDAP)' do - before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) } - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end end end end -- cgit v1.2.1 From 61fe0a23977749b0a5dba41ca26101b4a03de720 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Apr 2016 04:03:08 -0300 Subject: Fixed WikiPipeline and specs --- lib/banzai/pipeline/wiki_pipeline.rb | 2 +- spec/lib/banzai/pipeline/wiki_pipeline_spec.rb | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index 1cdb3808961..bcb89485dec 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -4,7 +4,7 @@ module Banzai def self.filters @filters ||= begin super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter) - .insert_after(Filter::GollumTagsFilter, Filter::WikiLinkFilter) + .insert_after(Filter::TableOfContentsFilter, Filter::WikiLinkFilter) end end end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 3e25406e498..4f86b1d87e3 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -1,6 +1,9 @@ require 'rails_helper' describe Banzai::Pipeline::WikiPipeline do + let(:project_wiki) { double } + before(:each) { allow(project_wiki).to receive(:wiki_base_path) { '/some/repo/wikis' } } + describe 'TableOfContents' do it 'replaces the tag with the TableOfContentsFilter result' do markdown = <<-MD.strip_heredoc @@ -11,7 +14,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - result = described_class.call(markdown, project: spy, project_wiki: double) + result = described_class.call(markdown, project: spy, project_wiki: project_wiki) aggregate_failures do expect(result[:output].text).not_to include '[[' @@ -29,7 +32,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: double) + output = described_class.to_html(markdown, project: spy, project_wiki: project_wiki) expect(output).to include('[[toc]]') end @@ -42,7 +45,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: double) + output = described_class.to_html(markdown, project: spy, project_wiki: project_wiki) aggregate_failures do expect(output).not_to include('
      ') -- cgit v1.2.1 From 22055e10580ae93c2cb87956eefba0a06e9b50d0 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Apr 2016 05:22:22 -0300 Subject: Fix a few edited references from WikiLinkFilter and specs --- lib/banzai/filter/wiki_link_filter.rb | 8 ++++++-- spec/features/markdown_spec.rb | 1 + spec/support/matchers/markdown_matchers.rb | 9 +++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb index 7a585eff653..06d10c98501 100644 --- a/lib/banzai/filter/wiki_link_filter.rb +++ b/lib/banzai/filter/wiki_link_filter.rb @@ -25,10 +25,10 @@ module Banzai end def process_link_attr(html_attr) - return if html_attr.blank? + return if html_attr.blank? || file_reference?(html_attr) uri = URI(html_attr.value) - if uri.relative? && uri.path.present? && uri.path + if uri.relative? && uri.path.present? html_attr.value = rebuild_wiki_uri(uri).to_s end rescue URI::Error @@ -40,6 +40,10 @@ module Banzai uri end + def file_reference?(html_attr) + !File.extname(html_attr.value).blank? + end + def project_wiki context[:project_wiki] end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 12fd8d37210..5149ce9cf2f 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -230,6 +230,7 @@ describe 'GitLab Markdown', feature: true do file = Gollum::File.new(@project_wiki.wiki) expect(file).to receive(:path).and_return('images/example.jpg') expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file) + allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki }) end diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 1d52489e804..43cb6ef43f2 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -13,7 +13,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - link = actual.at_css('a:contains("Relative Link")') + link = actual.at_css('a:contains("Relative Link")') image = actual.at_css('img[alt="Relative Image"]') expect(link['href']).to end_with('master/doc/README.md') @@ -72,14 +72,15 @@ module MarkdownMatchers have_css("img[src$='#{src}']") end + prefix = '/namespace1/gitlabhq/wikis' set_default_markdown_messages match do |actual| - expect(actual).to have_link('linked-resource', href: 'linked-resource') - expect(actual).to have_link('link-text', href: 'linked-resource') + expect(actual).to have_link('linked-resource', href: "#{prefix}/linked-resource") + expect(actual).to have_link('link-text', href: "#{prefix}/linked-resource") expect(actual).to have_link('http://example.com', href: 'http://example.com') expect(actual).to have_link('link-text', href: 'http://example.com/pdfs/gollum.pdf') - expect(actual).to have_image('/gitlabhq/wikis/images/example.jpg') + expect(actual).to have_image("#{prefix}/images/example.jpg") expect(actual).to have_image('http://example.com/images/example.jpg') end end -- cgit v1.2.1 From 207b7218aa4394dc24c68041eade04474ff41537 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Apr 2016 15:03:39 -0300 Subject: Ensure correct filter order to validate with our markdown spec --- lib/banzai/filter/gollum_tags_filter.rb | 8 ++++++-- lib/banzai/pipeline/wiki_pipeline.rb | 2 +- spec/features/markdown_spec.rb | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index cfd3e28ed09..f710a4971b0 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -118,7 +118,7 @@ module Banzai end if path - content_tag(:img, nil, src: path) + content_tag(:img, nil, src: path, class: tag_class('image')) end end @@ -155,7 +155,7 @@ module Banzai href = ::File.join(project_wiki_base_path, reference) end - content_tag(:a, name || reference, href: href) + content_tag(:a, name || reference, href: href, class: tag_class('page')) end def project_wiki @@ -172,6 +172,10 @@ module Banzai def validate needs :project_wiki end + + def tag_class(type) + "gfm gollum gollum-#{type}" + end end end end diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index bcb89485dec..c37b8e71cb0 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -4,7 +4,7 @@ module Banzai def self.filters @filters ||= begin super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter) - .insert_after(Filter::TableOfContentsFilter, Filter::WikiLinkFilter) + .insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter) end end end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 5149ce9cf2f..3d0d0e59fd7 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -39,7 +39,7 @@ describe 'GitLab Markdown', feature: true do end def doc(html = @html) - Nokogiri::HTML::DocumentFragment.parse(html) + @doc ||= Nokogiri::HTML::DocumentFragment.parse(html) end # Shared behavior that all pipelines should exhibit -- cgit v1.2.1 From 3137f61a5558d1b38dd9f607263d5a21b61deb2d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 10:49:57 +0200 Subject: Make redis an explicit Gemfile dependency --- Gemfile | 5 ++++- Gemfile.lock | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6327227282a..eebaae320e4 100644 --- a/Gemfile +++ b/Gemfile @@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0' # Cache gem "redis-rails", '~> 4.0.0' +# Redis +gem 'redis', '~> 3.2' +gem 'connection_pool', '~> 2.0' + # Campfire integration gem 'tinder', '~> 1.10.0' @@ -229,7 +233,6 @@ group :metrics do gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'method_source', '~> 0.8', require: false gem 'influxdb', '~> 0.2', require: false - gem 'connection_pool', '~> 2.0', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 229089f431d..3be9ed0afc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1009,6 +1009,7 @@ DEPENDENCIES rdoc (~> 3.6) recaptcha redcarpet (~> 3.3.3) + redis (~> 3.2) redis-namespace redis-rails (~> 4.0.0) request_store (~> 1.3.0) -- cgit v1.2.1 From 4d463c3b0f05fa8873b76afce621eaaf44c19836 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 09:54:49 +0100 Subject: Fixed delete comment button color --- app/views/projects/notes/_note.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 34fe1743f4b..a681d6dece4 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -18,7 +18,7 @@ = access = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = icon('pencil-square-o') - = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do + = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do = icon('trash-o') .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text -- cgit v1.2.1 From d6f276eacfdb7e8287df69dfc1dbf756288bade8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 10:05:39 +0100 Subject: Fixed issue with enter key not selecting correct value in dropdown --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2a4811b8763..33c5126087f 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -394,7 +394,7 @@ class GitLabDropdown selector = ".dropdown-page-one #{selector}" # simulate a click on the first link - $(selector).trigger "click" + $(selector, @dropdown).trigger "click" addArrowKeyEvent: -> ARROW_KEY_CODES = [38, 40] -- cgit v1.2.1 From d023a8528713ab9ffd8a9fc034f752ab91442b24 Mon Sep 17 00:00:00 2001 From: Yasser Hussain Date: Wed, 6 Apr 2016 15:07:31 +0530 Subject: Changed the argument of not_found for 'unprotect' not_found appends string "Not Found" to the argument causing the resulting message to be "Branch does not exist Not Found" which is an incorrect error message. Changed the argument of not_found! for 'unprotect' command to "Branch" from "Branch does not exist". This makes the final error message to appear as "Branch Not Found" which is correct and same as error messages for other commands like 'protect'. --- lib/api/branches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 592100a7045..231840148d9 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -64,7 +64,7 @@ module API authorize_admin_project @branch = user_project.repository.find_branch(params[:branch]) - not_found!("Branch does not exist") unless @branch + not_found!("Branch") unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch.destroy if protected_branch -- cgit v1.2.1 From c5c05f6a04ab3c791bc7c34dc74925731cf2ff94 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 14:43:40 +0000 Subject: Updated UI for new merge request Closes #2540 --- app/assets/javascripts/compare.js.coffee | 61 +++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 51 ++++++++++++++-- .../projects/merge_requests_controller.rb | 2 + app/helpers/commits_helper.rb | 4 +- app/views/projects/commits/_commit.html.haml | 2 +- .../projects/merge_requests/_new_compare.html.haml | 71 +++++++--------------- .../projects/merge_requests/branch_from.html.haml | 1 + .../projects/merge_requests/branch_from.js.haml | 3 - .../projects/merge_requests/branch_to.html.haml | 1 + .../projects/merge_requests/branch_to.js.haml | 3 - 10 files changed, 138 insertions(+), 61 deletions(-) create mode 100644 app/assets/javascripts/compare.js.coffee create mode 100644 app/views/projects/merge_requests/branch_from.html.haml delete mode 100644 app/views/projects/merge_requests/branch_from.js.haml create mode 100644 app/views/projects/merge_requests/branch_to.html.haml delete mode 100644 app/views/projects/merge_requests/branch_to.js.haml diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee new file mode 100644 index 00000000000..c13744ebc62 --- /dev/null +++ b/app/assets/javascripts/compare.js.coffee @@ -0,0 +1,61 @@ +class @Compare + constructor: (@opts) -> + @source_loading = $ ".js-source-loading" + @target_loading = $ ".js-target-loading" + @source_branch = $ "#merge_request_source_branch" + @target_branch = $ "#merge_request_target_branch" + @target_project = $ "#merge_request_target_project_id" + + @initialState() + @cleanBinding() + @addBinding() + + cleanBinding: -> + @source_branch.off "change" + @target_branch.off "change" + @target_project.off "change" + + addBinding: -> + @source_branch.on "change", => + @getSourceHtml() + @target_branch.on "change", => + @getTargetHtml() + @target_project.on "change", => + @getTargetProject() + + initialState: -> + @getSourceHtml() + @getTargetHtml() + + getTargetProject: -> + $.get @opts.targetProjectUrl, + target_project_id: @target_project.val() + + getSourceHtml: -> + $.ajax( + url: @opts.sourceBranchUrl + data: + ref: @source_branch.val() + beforeSend: => + @source_loading.show() + $(".mr_source_commit").html "" + success: (html) => + @source_loading.hide() + $(".mr_source_commit").html html + $(".mr_source_commit .js-timeago").timeago() + ) + + getTargetHtml: -> + $.ajax( + url: @opts.targetBranchUrl + data: + target_project_id: @target_project.val() + ref: @target_branch.val() + beforeSend: => + @target_loading.show() + $(".mr_target_commit").html "" + success: (html) => + @target_loading.hide() + $(".mr_target_commit").html html + $(".mr_target_commit .js-timeago").timeago() + ) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1c6a4208974..7bc1f58471f 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -123,6 +123,8 @@ .mr_source_commit, .mr_target_commit { + margin-bottom: 0; + .commit { margin: 0; padding: 2px 0; @@ -174,10 +176,6 @@ display: none; } -.merge-request-form .select2-container { - width: 250px !important; -} - #modal_merge_info .modal-dialog { width: 600px; @@ -200,3 +198,48 @@ overflow-x: scroll; } } + +.panel-new-merge-request { + .panel-heading { + padding: 5px 10px; + font-weight: 600; + line-height: 25px; + } + + .panel-body { + padding: 10px 5px; + } + + .panel-footer { + padding: 10px 10px; + } + + .commit { + .commit-row-title { + margin-bottom: 4px; + } + + .avatar { + width: 20px; + height: 20px; + margin-right: 5px; + } + + .commit-row-info { + line-height: 20px; + } + } + + .btn-clipboard { + margin-right: 5px; + padding: 0; + background: transparent; + } +} + +.merge-request-select { + float: left; + width: 50%; + padding-left: 5px; + padding-right: 5px; +} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 49064f5d505..ca6cfc99143 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -207,11 +207,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController #This is always source @source_project = @merge_request.nil? ? @project : @merge_request.source_project @commit = @repository.commit(params[:ref]) if params[:ref].present? + render layout: false end def branch_to @target_project = selected_target_project @commit = @target_project.commit(params[:ref]) if params[:ref].present? + render layout: false end def update_branches diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index bde0799f3de..a65e2e5cb8f 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -28,7 +28,7 @@ module CommitsHelper def commit_to_html(commit, project, inline = true) template = inline ? "inline_commit" : "commit" - escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? + render "projects/commits/#{template}", commit: commit, project: project unless commit.nil? end # Breadcrumb links for a Project and, if applicable, a tree path @@ -117,7 +117,7 @@ module CommitsHelper end end link_to( - "Browse Files »", + "Browse Files", namespace_project_tree_path(project.namespace, project, commit), class: "pull-right" ) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a9..b55fe510f70 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -35,8 +35,8 @@ = preserve(markdown(escape_once(commit.description), pipeline: :single_line)) .commit-row-info + by = commit_author_link(commit, avatar: true, size: 24) - authored .committed_ago #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}   = link_to_browse_code(project, commit) diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 01dc7519bee..4fc74dfcf45 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -5,27 +5,31 @@ .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Source branch - .panel-body - = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true }) -   - = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } }) + Source branch + .panel-body.clearfix + .merge-request-select + = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2', disabled: @merge_request.persisted?, required: true }) + .merge-request-select + = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2', required: true, data: { placeholder: "Select source branch" } }) .panel-footer - .mr_source_commit + = icon('spinner spin', class: "js-source-loading") + %ul.list-unstyled.mr_source_commit .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Target branch - .panel-body + Target branch + .panel-body.clearfix - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true }) -   - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } }) + .merge-request-select + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2', disabled: @merge_request.persisted?, required: true }) + .merge-request-select + = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2', required: true, data: { placeholder: "Select target branch" } }) .panel-footer - .mr_target_commit + = icon('spinner spin', class: "js-target-loading") + %ul.list-unstyled.mr_target_commit - if @merge_request.errors.any? .alert.alert-danger @@ -45,40 +49,11 @@ and %span.label-branch #{@merge_request.target_branch} are the same. - - - .form-actions - = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" - -:javascript - var source_branch = $("#merge_request_source_branch") - , target_branch = $("#merge_request_target_branch") - , target_project = $("#merge_request_target_project_id"); - - $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() }); - $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() }); - - target_project.on("change", function() { - $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() }); - }); - source_branch.on("change", function() { - $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() }); - $(".mr-compare-errors").fadeOut(); - $(".mr-compare-btn").enable(); - }); - target_branch.on("change", function() { - $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); - $(".mr-compare-errors").fadeOut(); - $(".mr-compare-btn").enable(); - }); - + = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript - $(".merge-request-form").on('submit', function () { - if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") { - $(".mr-compare-errors").html("You must select source and target branch to proceed"); - $(".mr-compare-errors").fadeIn(); - event.preventDefault(); - return; - } + new Compare({ + targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", + sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", + targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}" }); diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml new file mode 100644 index 00000000000..4f90dde6fa8 --- /dev/null +++ b/app/views/projects/merge_requests/branch_from.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @source_project, false) diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml deleted file mode 100644 index 9210798f39c..00000000000 --- a/app/views/projects/merge_requests/branch_from.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); - $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml new file mode 100644 index 00000000000..67a7a6bcec9 --- /dev/null +++ b/app/views/projects/merge_requests/branch_to.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @target_project, false) diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml deleted file mode 100644 index 32fe2d535f3..00000000000 --- a/app/views/projects/merge_requests/branch_to.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); - $('.js-timeago').timeago() -- cgit v1.2.1 From 7fe19046d6633d01f1a326a5cc0d80a24879f04d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 09:33:23 +0100 Subject: Fixed failing tests --- features/steps/project/source/browse_files.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 243469b8e7d..5beb2cc6d76 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -213,8 +213,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I see Browse file link' do - expect(page).to have_link 'Browse File »' - expect(page).not_to have_link 'Browse Files »' + expect(page).to have_link 'Browse File' + expect(page).not_to have_link 'Browse Files' end step 'I see Browse code link' do -- cgit v1.2.1 From b31005899858bb19f5de06b49621fa1d73dbe037 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 11:11:16 +0100 Subject: Use new dropdowns for MR compare --- app/assets/javascripts/compare.js.coffee | 43 ++++++++--------- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- app/assets/stylesheets/pages/merge_requests.scss | 10 ++++ .../projects/merge_requests/_new_compare.html.haml | 55 ++++++++++++++++++---- features/steps/project/source/browse_files.rb | 4 +- 5 files changed, 82 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee index c13744ebc62..e5819fa91c3 100644 --- a/app/assets/javascripts/compare.js.coffee +++ b/app/assets/javascripts/compare.js.coffee @@ -2,26 +2,27 @@ class @Compare constructor: (@opts) -> @source_loading = $ ".js-source-loading" @target_loading = $ ".js-target-loading" - @source_branch = $ "#merge_request_source_branch" - @target_branch = $ "#merge_request_target_branch" - @target_project = $ "#merge_request_target_project_id" - @initialState() - @cleanBinding() - @addBinding() + $('.js-compare-dropdown').each (i, dropdown) => + $dropdown = $(dropdown) - cleanBinding: -> - @source_branch.off "change" - @target_branch.off "change" - @target_project.off "change" + $dropdown.glDropdown( + selectable: true + fieldName: $dropdown.data 'field-name' + id: (obj, $el) -> + $el.data 'id' + toggleLabel: (obj, $el) -> + $el.text().trim() + clicked: (e, el) => + if $dropdown.is '.js-target-branch' + @getTargetHtml() + else if $dropdown.is '.js-source-branch' + @getSourceHtml() + else if $dropdown.is '.js-target-project' + @getTargetProject() + ) - addBinding: -> - @source_branch.on "change", => - @getSourceHtml() - @target_branch.on "change", => - @getTargetHtml() - @target_project.on "change", => - @getTargetProject() + @initialState() initialState: -> @getSourceHtml() @@ -29,13 +30,13 @@ class @Compare getTargetProject: -> $.get @opts.targetProjectUrl, - target_project_id: @target_project.val() + target_project_id: $("input[name='merge_request[source_project]']").val() getSourceHtml: -> $.ajax( url: @opts.sourceBranchUrl data: - ref: @source_branch.val() + ref: $("input[name='merge_request[source_branch]']").val() beforeSend: => @source_loading.show() $(".mr_source_commit").html "" @@ -49,8 +50,8 @@ class @Compare $.ajax( url: @opts.targetBranchUrl data: - target_project_id: @target_project.val() - ref: @target_branch.val() + target_project_id: $("input[name='merge_request[target_project_id]']").val() + ref: $("input[name='merge_request[target_branch]']").val() beforeSend: => @target_loading.show() $(".mr_target_commit").html "" diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2a4811b8763..38b38fc8426 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -373,7 +373,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el) if value? if !field.length # Create hidden input for form diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7bc1f58471f..5929f607fc7 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -242,4 +242,14 @@ width: 50%; padding-left: 5px; padding-right: 5px; + + .dropdown-menu-toggle { + width: 100%; + } + + .dropdown-menu { + left: 5px; + right: 5px; + width: auto; + } } diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 4fc74dfcf45..f110c5f42c8 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -9,10 +9,29 @@ .panel-heading Source branch .panel-body.clearfix - .merge-request-select - = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2', disabled: @merge_request.persisted?, required: true }) - .merge-request-select - = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2', required: true, data: { placeholder: "Select source branch" } }) + .merge-request-select.dropdown + = f.hidden_field :source_project_id + = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } + .dropdown-menu + = dropdown_title("Select source project") + = dropdown_filter("Search projects") + = dropdown_content do + %ul + %li + %a{ href: "#", data: { id: @merge_request.source_project.id } } + = @merge_request.source_project_path + .merge-request-select.dropdown + = f.hidden_field :source_branch + = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } + .dropdown-menu + = dropdown_title("Select source branch") + = dropdown_filter("Search branches") + = dropdown_content do + %ul + - @merge_request.source_branches.each do |branch| + %li + %a{ href: "#", data: { id: branch } } + = branch .panel-footer = icon('spinner spin', class: "js-source-loading") %ul.list-unstyled.mr_source_commit @@ -23,10 +42,30 @@ Target branch .panel-body.clearfix - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - .merge-request-select - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2', disabled: @merge_request.persisted?, required: true }) - .merge-request-select - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2', required: true, data: { placeholder: "Select target branch" } }) + .merge-request-select.dropdown + = f.hidden_field :target_project_id + = dropdown_toggle projects.first.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + .dropdown-menu + = dropdown_title("Select target project") + = dropdown_filter("Search projects") + = dropdown_content do + %ul + - projects.each do |project| + %li + %a{ href: "#" } + = project.path_with_namespace + .merge-request-select.dropdown + = f.hidden_field :target_branch + = dropdown_toggle "Select target branch", { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } + .dropdown-menu.dropdown-menu-selectable + = dropdown_title("Select target branch") + = dropdown_filter("Search branches") + = dropdown_content do + %ul + - @merge_request.target_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if :target_branch == branch)}", data: { id: branch } } + = branch .panel-footer = icon('spinner spin', class: "js-target-loading") %ul.list-unstyled.mr_target_commit diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 5beb2cc6d76..f73bb425e57 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -218,8 +218,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I see Browse code link' do - expect(page).to have_link 'Browse Files »' - expect(page).not_to have_link 'Browse File »' + expect(page).to have_link 'Browse Files' + expect(page).not_to have_link 'Browse File' expect(page).not_to have_link 'Browse Directory »' end -- cgit v1.2.1 From 850fa65ef2ff1fb16623c591a2147d75df333c58 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 11:28:23 +0100 Subject: Checkmarks in dropdowns for already selected values --- app/assets/stylesheets/pages/commits.scss | 1 + app/views/projects/merge_requests/_new_compare.html.haml | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 8272615768d..ad8e97de3b3 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -47,6 +47,7 @@ li.commit { .commit_short_id { min-width: 65px; + color: $gl-dark-link-color; font-family: $monospace_font; } diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index f110c5f42c8..84c2faff920 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -12,25 +12,25 @@ .merge-request-select.dropdown = f.hidden_field :source_project_id = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } - .dropdown-menu + .dropdown-menu.dropdown-menu-selectable = dropdown_title("Select source project") = dropdown_filter("Search projects") = dropdown_content do %ul %li - %a{ href: "#", data: { id: @merge_request.source_project.id } } + %a{ href: "#", class: "#{("is-active" if f.object.source_project_id == @merge_request.source_project.id)}", data: { id: @merge_request.source_project.id } } = @merge_request.source_project_path .merge-request-select.dropdown = f.hidden_field :source_branch = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } - .dropdown-menu + .dropdown-menu.dropdown-menu-selectable = dropdown_title("Select source branch") = dropdown_filter("Search branches") = dropdown_content do %ul - @merge_request.source_branches.each do |branch| %li - %a{ href: "#", data: { id: branch } } + %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } } = branch .panel-footer = icon('spinner spin', class: "js-source-loading") @@ -45,14 +45,14 @@ .merge-request-select.dropdown = f.hidden_field :target_project_id = dropdown_toggle projects.first.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } - .dropdown-menu + .dropdown-menu.dropdown-menu-selectable = dropdown_title("Select target project") = dropdown_filter("Search projects") = dropdown_content do %ul - projects.each do |project| %li - %a{ href: "#" } + %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } } = project.path_with_namespace .merge-request-select.dropdown = f.hidden_field :target_branch @@ -64,7 +64,7 @@ %ul - @merge_request.target_branches.each do |branch| %li - %a{ href: "#", class: "#{("is-active" if :target_branch == branch)}", data: { id: branch } } + %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } } = branch .panel-footer = icon('spinner spin', class: "js-target-loading") -- cgit v1.2.1 From beab104da812af015eeabb0f33d203741a221aed Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 11:51:13 +0100 Subject: Filters by text on element --- app/assets/javascripts/compare.js.coffee | 1 + app/assets/javascripts/gl_dropdown.js.coffee | 37 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee index e5819fa91c3..8b502c8f27a 100644 --- a/app/assets/javascripts/compare.js.coffee +++ b/app/assets/javascripts/compare.js.coffee @@ -9,6 +9,7 @@ class @Compare $dropdown.glDropdown( selectable: true fieldName: $dropdown.data 'field-name' + filterable: true id: (obj, $el) -> $el.data 'id' toggleLabel: (obj, $el) -> diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 38b38fc8426..82a82a27ccf 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -57,14 +57,30 @@ class GitLabDropdownFilter filter: (search_text) -> data = @options.data() - results = data - if search_text isnt "" - results = fuzzaldrinPlus.filter(data, search_text, - key: @options.keys - ) + if data? + results = data - @options.callback results + if search_text isnt "" + results = fuzzaldrinPlus.filter(data, search_text, + key: @options.keys + ) + + @options.callback results + else + elements = @options.elements() + + if search_text + elements.each -> + $el = $(@) + matches = fuzzaldrinPlus.match($el.text().trim(), search_text) + + if matches.length + $el.show() + else + $el.hide() + else + elements.show() class GitLabDropdownRemote constructor: (@dataEndpoint, @options) -> @@ -147,7 +163,14 @@ class GitLabDropdown filterInputBlur: @filterInputBlur remote: @options.filterRemote query: @options.data - keys: @options.search.fields + keys: search_fields + elements: => + selector = ".dropdown-content li:not(.divider)" + + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one #{selector}" + + return $(selector) data: => return @fullData callback: (data) => -- cgit v1.2.1 From 1af1b6c8d32599500edb8ead52666a27d53765e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 12:43:33 +0100 Subject: Fixed target project update --- app/assets/javascripts/compare.js.coffee | 52 ++++++++++++---------- app/assets/stylesheets/pages/commits.scss | 4 ++ .../projects/merge_requests_controller.rb | 4 +- .../projects/merge_requests/_new_compare.html.haml | 6 +-- .../merge_requests/update_branches.html.haml | 5 +++ .../merge_requests/update_branches.js.haml | 9 ---- 6 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 app/views/projects/merge_requests/update_branches.html.haml delete mode 100644 app/views/projects/merge_requests/update_branches.js.haml diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee index 8b502c8f27a..762fedba408 100644 --- a/app/assets/javascripts/compare.js.coffee +++ b/app/assets/javascripts/compare.js.coffee @@ -30,34 +30,40 @@ class @Compare @getTargetHtml() getTargetProject: -> + $.ajax( + url: @opts.targetProjectUrl + data: + target_project_id: $("input[name='merge_request[target_project_id]']").val() + beforeSend: -> + $('.mr_target_commit').empty() + success: (html) -> + $('.js-target-branch-dropdown .dropdown-content').html html + ) $.get @opts.targetProjectUrl, - target_project_id: $("input[name='merge_request[source_project]']").val() + target_project_id: $("input[name='merge_request[target_project_id]']").val() getSourceHtml: -> - $.ajax( - url: @opts.sourceBranchUrl - data: - ref: $("input[name='merge_request[source_branch]']").val() - beforeSend: => - @source_loading.show() - $(".mr_source_commit").html "" - success: (html) => - @source_loading.hide() - $(".mr_source_commit").html html - $(".mr_source_commit .js-timeago").timeago() + @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit', + ref: $("input[name='merge_request[source_branch]']").val() ) getTargetHtml: -> + @sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit', + target_project_id: $("input[name='merge_request[target_project_id]']").val() + ref: $("input[name='merge_request[target_branch]']").val() + ) + + sendAjax: (url, loading, target, data) -> + $target = $(target) + $.ajax( - url: @opts.targetBranchUrl - data: - target_project_id: $("input[name='merge_request[target_project_id]']").val() - ref: $("input[name='merge_request[target_branch]']").val() - beforeSend: => - @target_loading.show() - $(".mr_target_commit").html "" - success: (html) => - @target_loading.hide() - $(".mr_target_commit").html html - $(".mr_target_commit .js-timeago").timeago() + url: url + data: data + beforeSend: -> + loading.show() + $target.empty() + success: (html) -> + loading.hide() + $target.html html + $('.js-timeago', $target).timeago() ) diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index ad8e97de3b3..6453c91d955 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -89,6 +89,10 @@ li.commit { padding: 0; margin: 0; } + + a { + color: $gl-dark-link-color; + } } .commit-row-info { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ca6cfc99143..ae613f5e093 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -220,9 +220,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @target_project = selected_target_project @target_branches = @target_project.repository.branch_names - respond_to do |format| - format.js - end + render layout: false end def ci_status diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 84c2faff920..35d8faf7a9d 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -44,7 +44,7 @@ - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] .merge-request-select.dropdown = f.hidden_field :target_project_id - = dropdown_toggle projects.first.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } .dropdown-menu.dropdown-menu-selectable = dropdown_title("Select target project") = dropdown_filter("Search projects") @@ -56,8 +56,8 @@ = project.path_with_namespace .merge-request-select.dropdown = f.hidden_field :target_branch - = dropdown_toggle "Select target branch", { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } - .dropdown-menu.dropdown-menu-selectable + = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } + .dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown = dropdown_title("Select target branch") = dropdown_filter("Search branches") = dropdown_content do diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml new file mode 100644 index 00000000000..1b93188a10c --- /dev/null +++ b/app/views/projects/merge_requests/update_branches.html.haml @@ -0,0 +1,5 @@ +%ul + - @target_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } } + = branch diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml deleted file mode 100644 index ca21b3bc0de..00000000000 --- a/app/views/projects/merge_requests/update_branches.js.haml +++ /dev/null @@ -1,9 +0,0 @@ -:plain - $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}"); - - $('select.target_branch').select2({ - width: 'resolve', - dropdownAutoWidth: true - }); - - $(".mr_target_commit").html(""); -- cgit v1.2.1 From fe811a7cdba2fcd1466f25115472265910dd394d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 12:44:34 +0100 Subject: Removed un-used get request --- app/assets/javascripts/compare.js.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee index 762fedba408..f20992ead3e 100644 --- a/app/assets/javascripts/compare.js.coffee +++ b/app/assets/javascripts/compare.js.coffee @@ -39,8 +39,6 @@ class @Compare success: (html) -> $('.js-target-branch-dropdown .dropdown-content').html html ) - $.get @opts.targetProjectUrl, - target_project_id: $("input[name='merge_request[target_project_id]']").val() getSourceHtml: -> @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit', -- cgit v1.2.1 From 8cdb50587805afa4177ee6cb938a0b5846a1641f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 11:08:24 +0100 Subject: Fixed spacing in MR footer --- app/assets/stylesheets/pages/merge_requests.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 5929f607fc7..2ced7e5e1df 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -211,7 +211,7 @@ } .panel-footer { - padding: 10px 10px; + padding: 5px 10px; } .commit { -- cgit v1.2.1 From 301f4074aa05f25757396182490c3ebfffe1e81c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Apr 2016 12:26:10 +0200 Subject: Add specs for sessions controller including 2FA This also contains specs for a bug described in #14900 --- spec/controllers/sessions_controller_spec.rb | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 spec/controllers/sessions_controller_spec.rb diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 00000000000..e7dbc3bdad4 --- /dev/null +++ b/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe SessionsController do + describe '#create' do + before do + @request.env['devise.mapping'] = Devise.mappings[:user] + end + + context 'standard authentications' do + context 'invalid password' do + it 'does not authenticate user' do + post(:create, user: { login: 'invalid', password: 'invalid' }) + + expect(response) + .to set_flash.now[:alert].to /Invalid login or password/ + end + end + + context 'valid password' do + let(:user) { create(:user) } + + it 'authenticates user correctly' do + post(:create, user: { login: user.username, password: user.password }) + + expect(response).to set_flash.to /Signed in successfully/ + expect(subject.current_user). to eq user + end + end + end + + context 'two-factor authentication' do + let(:user) { create(:user, :two_factor) } + + def authenticate_2fa(user_params) + post(:create, { user: user_params }, { otp_user_id: user.id }) + end + + ## + # See #14900 issue + # + context 'authenticating with login and OTP belonging to another user' do + let(:another_user) { create(:user, :two_factor) } + + + context 'OTP valid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: another_user.current_otp) + + expect(subject.current_user).to_not eq another_user + end + end + + context 'OTP invalid for another user' do + before do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') + end + + it 'does not authenticate' do + expect(subject.current_user).to_not eq another_user + end + + it 'does not leak information about 2FA enabled' do + expect(response).to_not set_flash.now[:alert].to /Invalid two-factor code/ + end + end + + context 'authenticating with OTP' do + context 'valid OTP' do + it 'authenticates correctly' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(subject.current_user).to eq user + end + end + + context 'invalid OTP' do + before { authenticate_2fa(otp_attempt: 'invalid') } + + it 'does not authenticate' do + expect(subject.current_user).to_not eq user + end + + it 'warns about invalid OTP code' do + expect(response).to set_flash.now[:alert].to /Invalid two-factor code/ + end + end + end + end + end + end +end -- cgit v1.2.1 From 017ed4ae37474ba43cbf4f4ee7c12d8aeec70598 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 12:52:07 +0100 Subject: Fixed builds --- app/assets/stylesheets/pages/merge_requests.scss | 4 ++++ app/views/projects/commits/_commit.html.haml | 9 +-------- .../projects/merge_requests/_new_compare.html.haml | 8 ++++---- features/project/forked_merge_requests.feature | 1 + features/project/merge_requests.feature | 1 + features/steps/project/forked_merge_requests.rb | 22 +++++++++++++--------- features/steps/project/merge_requests.rb | 8 ++++++-- 7 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2ced7e5e1df..1790fbaaa11 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -235,6 +235,10 @@ padding: 0; background: transparent; } + + .ci-status-link { + margin-right: 5px; + } } .merge-request-select { diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index b55fe510f70..7da89231243 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -19,23 +19,16 @@ .pull-right - if ci_commit = render_ci_status(ci_commit) -   = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - .notes_count - - if note_count > 0 - %span.light - %i.fa.fa-comments - = note_count - - if commit.description? .commit-row-description.js-toggle-content %pre = preserve(markdown(escape_once(commit.description), pipeline: :single_line)) .commit-row-info - by + by = commit_author_link(commit, avatar: true, size: 24) .committed_ago #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}   diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 35d8faf7a9d..036b1935361 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -12,7 +12,7 @@ .merge-request-select.dropdown = f.hidden_field :source_project_id = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } - .dropdown-menu.dropdown-menu-selectable + .dropdown-menu.dropdown-menu-selectable.dropdown-source-project = dropdown_title("Select source project") = dropdown_filter("Search projects") = dropdown_content do @@ -23,7 +23,7 @@ .merge-request-select.dropdown = f.hidden_field :source_branch = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } - .dropdown-menu.dropdown-menu-selectable + .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch = dropdown_title("Select source branch") = dropdown_filter("Search branches") = dropdown_content do @@ -45,7 +45,7 @@ .merge-request-select.dropdown = f.hidden_field :target_project_id = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } - .dropdown-menu.dropdown-menu-selectable + .dropdown-menu.dropdown-menu-selectable.dropdown-target-project = dropdown_title("Select target project") = dropdown_filter("Search projects") = dropdown_content do @@ -57,7 +57,7 @@ .merge-request-select.dropdown = f.hidden_field :target_branch = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } - .dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown + .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown = dropdown_title("Select target branch") = dropdown_filter("Search branches") = dropdown_content do diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index 10bd6fec803..67f1e117f7f 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests And I am a member of project "Shop" And I have a project forked off of "Shop" called "Forked Shop" + @javascript Scenario: I submit new unassigned merge request to a forked project Given I visit project "Forked Shop" merge requests page And I click link "New Merge Request" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 823658b4f24..ecda4ea8240 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -70,6 +70,7 @@ Feature: Project Merge Requests When I click link "Reopen" Then I should see reopened merge request "Bug NS-04" + @javascript Scenario: I submit new unassigned merge request Given I click link "New Merge Request" And I submit new merge request "Wiki Feature" diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 7e4425ff662..612bb8fd8b1 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I fill out a "Merge Request On Forked Project" merge request' do - select @forked_project.path_with_namespace, from: "merge_request_source_project_id" - select @project.path_with_namespace, from: "merge_request_target_project_id" - select "fix", from: "merge_request_source_branch" - select "master", from: "merge_request_target_branch" + first('.js-source-project').click + first('.dropdown-source-project a', text: @forked_project.path_with_namespace) + + first('.js-target-project').click + first('.dropdown-target-project a', text: @project.path_with_namespace) + + first('.js-source-branch').click + first('.dropdown-source-branch .dropdown-content a', text: 'fix').click click_button "Compare branches and continue" @@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I fill out an invalid "Merge Request On Forked Project" merge request' do - expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s - expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s - expect(find(:select, "merge_request_source_branch", {}).value).to eq "" - expect(find(:select, "merge_request_target_branch", {}).value).to eq "master" + expect(find_by_id("merge_request_source_project_id", visible: false).value).to eq @forked_project.id.to_s + expect(find_by_id("merge_request_target_project_id", visible: false).value).to eq @project.id.to_s + expect(find_by_id("merge_request_source_branch", visible: false).value).to eq nil + expect(find_by_id("merge_request_target_branch", visible: false).value).to eq "master" click_button "Compare branches" end @@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'the target repository should be the original repository' do - expect(page).to have_select("merge_request_target_project_id", selected: @project.path_with_namespace) + expect(find_by_id("merge_request_target_project_id").value).to eq "#{@project.id}" end step 'I click "Assign to" dropdown"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index a4f02b590ea..f0af0d097fa 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I submit new merge request "Wiki Feature"' do - select "fix", from: "merge_request_source_branch" - select "feature", from: "merge_request_target_branch" + find('.js-source-branch').click + find('.dropdown-source-branch .dropdown-content a', text: 'fix').click + + find('.js-target-branch').click + first('.dropdown-target-branch .dropdown-content a', text: 'feature').click + click_button "Compare branches" fill_in "merge_request_title", with: "Wiki Feature" click_button "Submit merge request" -- cgit v1.2.1 From 507cbca339444eabdfdd5d6dff1237bd00ee636a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 6 Apr 2016 14:24:30 +0200 Subject: Fix header link rendering when containing numbers This fixes the problem where Markdown such as: ### 31st Would get rendered as a link tag pointing to issue number 31 inside a header tag. See gitlab-org/gitlab-ce#14936 for more information. --- lib/banzai/filter/abstract_reference_filter.rb | 2 +- spec/lib/banzai/filter/issue_reference_filter_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index f21dbef216c..b8962379cb5 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -119,7 +119,7 @@ module Banzai elsif element_node?(node) yield_valid_link(node) do |link, text| - if ref_pattern && link =~ /\A#{ref_pattern}/ + if ref_pattern && link =~ /\A#{ref_pattern}\z/ replace_link_node_with_href(node, link) do object_link_filter(link, ref_pattern, link_text: text) end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 5a0d3d577a8..266ebef33d6 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do result = reference_pipeline_result("Fixed #{reference}") expect(result[:references][:issue]).to eq [issue] end + + it 'does not process links containing issue numbers followed by text' do + href = "#{reference}st" + doc = reference_filter("") + link = doc.css('a').first.attr('href') + + expect(link).to eq(href) + end end context 'cross-project reference' do -- cgit v1.2.1 From 82e92a0900f76d208faf94629c90309201d05914 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 14:32:36 +0200 Subject: API: Expose open_issues_count, closed_issues_count, open_merge_requests_count on labels --- doc/api/labels.md | 40 ++++++++++++++++++++-------------------- lib/api/entities.rb | 1 + 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/doc/api/labels.md b/doc/api/labels.md index 544e898b6aa..3730c07c5a7 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -23,42 +23,42 @@ Example response: { "name" : "bug", "color" : "#d9534f", - "description": "Bug reported by user" + "description": "Bug reported by user", + "open_issues_count": 1, + "closed_issues_count": 0, + "open_merge_requests_count": 1 }, { "color" : "#d9534f", "name" : "confirmed", - "description": "Confirmed issue" + "description": "Confirmed issue", + "open_issues_count": 2, + "closed_issues_count": 5, + "open_merge_requests_count": 0 }, { "name" : "critical", "color" : "#d9534f", - "description": "Criticalissue. Need fix ASAP" - }, - { - "color" : "#428bca", - "name" : "discussion", - "description": "Issue that needs further discussion" + "description": "Criticalissue. Need fix ASAP", + "open_issues_count": 1, + "closed_issues_count": 3, + "open_merge_requests_count": 1 }, { "name" : "documentation", "color" : "#f0ad4e", - "description": "Issue about documentation" + "description": "Issue about documentation", + "open_issues_count": 1, + "closed_issues_count": 0, + "open_merge_requests_count": 2 }, { "color" : "#5cb85c", "name" : "enhancement", - "description": "Enhancement proposal" - }, - { - "color" : "#428bca", - "name" : "suggestion", - "description": "Suggestion" - }, - { - "color" : "#f0ad4e", - "name" : "support", - "description": "Support issue" + "description": "Enhancement proposal", + "open_issues_count": 1, + "closed_issues_count": 0, + "open_merge_requests_count": 1 } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 340fc5452ab..cd0d16e5316 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -293,6 +293,7 @@ module API class Label < Grape::Entity expose :name, :color, :description + expose :open_issues_count, :closed_issues_count, :open_merge_requests_count end class Compare < Grape::Entity -- cgit v1.2.1 From 622b2023da48a6c0229e9eff06c142431a62db36 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 15:07:35 +0200 Subject: Use gitlab-workhorse 0.7.2 --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.6-to-8.7.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 39e898a4f95..7486fdbc50b 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.1 +0.7.2 diff --git a/doc/install/installation.md b/doc/install/installation.md index e0a16df09c1..f32b6008d4c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -348,7 +348,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.1 + sudo -u git -H git checkout v0.7.2 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index 76eee147c72..4cbf943bb5a 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -58,7 +58,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.1 +sudo -u git -H git checkout v0.7.2 sudo -u git -H make ``` -- cgit v1.2.1 From 9e6c61372fe0f7f2199f4e8f72b3c4e4a4219020 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 6 Apr 2016 11:02:22 -0300 Subject: Fix milestone removal problem when editing issues --- app/helpers/issues_helper.rb | 1 + spec/helpers/issues_helper_spec.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 24b90fef4fe..414056c809f 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -52,6 +52,7 @@ module IssuesHelper def milestone_options(object) milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a + milestones.unshift(object.milestone) if object.milestone.present? && object.milestone.closed? milestones.unshift(Milestone::None) options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id) diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ffd8ebae029..df0eb22d6f6 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -148,4 +148,19 @@ describe IssuesHelper do expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"]) end end + + describe "#milestone options" do + let!(:closed_milestone) { create :closed_milestone, title: "closed milestone", project: project } + let!(:milestone1) { create :milestone, title: "open milestone 1", project: project } + let!(:milestone2) { create :milestone, title: "open milestone 2", project: project } + + before { issue.update_attributes(milestone_id: closed_milestone.id) } + + it "gets closed milestone from current issue" do + options = milestone_options(issue) + expect(options).to have_selector('option[selected]', text: closed_milestone.title) + expect(options).to have_selector('option', text: milestone1.title) + expect(options).to have_selector('option', text: milestone2.title) + end + end end -- cgit v1.2.1 From 2b91abbcedb46a74770c7384544b1239d0e210a5 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 16:08:40 +0200 Subject: Disable git gc --auto --- doc/install/installation.md | 6 +++++- doc/update/8.6-to-8.7.md | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index e0a16df09c1..f8f7d6a9ebe 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -283,9 +283,13 @@ sudo usermod -aG redis git # Copy the example Rack attack config sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb - # Configure Git global settings for git user, used when editing via web editor + # Configure Git global settings for git user + # 'autocrlf' is needed for the web editor sudo -u git -H git config --global core.autocrlf input + # Disable 'git gc --auto' because GitLab already runs 'git gc' when needed + sudo -u git -H git config --global gc.auto 0 + # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index 76eee147c72..8599133a726 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -86,6 +86,14 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ### 7. Update configuration files +#### Git configuration + +Disable `git gc --auto` because GitLab runs `git gc` for us already. + +```sh +sudo -u git -H git config --global gc.auto 0 +``` + #### Nginx configuration Ensure you're still up-to-date with the latest NGINX configuration changes: -- cgit v1.2.1 From 39ecce05c7c3b5d8054b4deb61028695872acc9e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 16:25:38 +0100 Subject: Build fix --- app/assets/stylesheets/pages/merge_requests.scss | 4 ++++ spec/features/merge_requests/create_new_mr_spec.rb | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1790fbaaa11..4269afe4c50 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -257,3 +257,7 @@ width: auto; } } + +.merge-request-form .select2-container { + width: 250px!important; +} diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index fd02d584848..00b60bd0e75 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Create New Merge Request', feature: true, js: false do +feature 'Create New Merge Request', feature: true, js: true do let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do it 'generates a diff for an orphaned branch' do click_link 'New Merge Request' - select "orphaned-branch", from: "merge_request_source_branch" - select "master", from: "merge_request_target_branch" + + first('.js-source-branch').click + first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click + click_button "Compare branches" + click_link "Changes" expect(page).to have_content "README.md" expect(page).to have_content "wm.png" @@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do fill_in "merge_request_title", with: "Orphaned MR test" click_button "Submit merge request" + click_link "Check out branch" + expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' end end -- cgit v1.2.1 From 222e1dc59cd559f893a4565040dede300645248b Mon Sep 17 00:00:00 2001 From: PotHix Date: Wed, 6 Apr 2016 12:05:50 -0300 Subject: Fixes #14638. The SQL query was ambiguous and in this case we want to filter projects. --- CHANGELOG | 1 + app/controllers/admin/projects_controller.rb | 2 +- spec/controllers/admin/projects_controller_spec.rb | 23 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 spec/controllers/admin/projects_controller_spec.rb diff --git a/CHANGELOG b/CHANGELOG index a23c8a54af1..35c668f17bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.7.0 (unreleased) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - Improved UX of the navigation sidebar + - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Ability to retrieve a specific tag (Robert Schilling) - API: Expose user location (Robert Schilling) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 4089091d569..c6b3105544a 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController def index @projects = Project.all @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? - @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? + @projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.non_archived unless params[:with_archived].present? diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb new file mode 100644 index 00000000000..2ba0d489197 --- /dev/null +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Admin::ProjectsController do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + + before do + sign_in(create(:admin)) + end + + describe 'GET /projects' do + render_views + + it 'retrieves the project for the given visibility level' do + get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC] + expect(response.body).to match(project.name) + end + + it 'does not retrieve the project' do + get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL] + expect(response.body).to_not match(project.name) + end + end +end -- cgit v1.2.1 From 5083e5b616d2ba9b2812920c8d6cf3e0f95389e1 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 19:07:23 +0200 Subject: Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8db9a9b0d1e..05d8270b5b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.7.0 (unreleased) - Add endpoints to archive or unarchive a project !3372 - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) + - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - Add default scope to projects to exclude projects pending deletion - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) -- cgit v1.2.1 From ea69037831a9b607e7936cadb8078a3182d6635a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Mar 2016 11:59:55 +0100 Subject: Improve routes for project badges --- config/routes.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 6bf22fb4456..36561c31d48 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -749,10 +749,11 @@ Rails.application.routes.draw do end resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [], path: 'badges/*ref', - constraints: { ref: Gitlab::Regex.git_reference_regex } do + resources :badges, only: [] do collection do - get :build, constraints: { format: /svg/ } + scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do + get :build, constraints: { format: /svg/ } + end end end end -- cgit v1.2.1 From 88fc7ccddaab18435bdc85021d06d9aa21d28a68 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 Mar 2016 10:21:45 +0100 Subject: Add project badges view prototype --- app/controllers/projects/badges_controller.rb | 5 ++++- app/views/projects/badges/index.html.haml | 15 +++++++++++++++ app/views/projects/show.html.haml | 3 +++ config/routes.rb | 2 +- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 app/views/projects/badges/index.html.haml diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 6d4d4360988..01db85c4a8a 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -1,5 +1,8 @@ class Projects::BadgesController < Projects::ApplicationController - before_action :no_cache_headers + before_action :no_cache_headers, except: [:index] + + def index + end def build badge = Gitlab::Badge::Build.new(project, params[:ref]) diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml new file mode 100644 index 00000000000..b527df8ac98 --- /dev/null +++ b/app/views/projects/badges/index.html.haml @@ -0,0 +1,15 @@ +- page_title 'Badges' + +.prepend-top-10 + .panel.panel-default + .panel-heading + %b Builds badge · + = image_tag(build_namespace_project_badges_path(@project.namespace, @project, :master, format: :svg), alt: 'Builds badge') + .panel-body + %table.table + %tr + %td Markdown + %td= markdown("```markdown\n[![build status](url)](link)\n```") + %tr + %td HTML + %td= markdown("```html\n\n```") diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4310f038fc9..6963d657d9e 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -57,6 +57,9 @@ %li.missing = link_to add_contribution_guide_path(@project) do Add Contribution guide + - if @project.builds_enabled? + %li + = link_to 'Badges', '' - if @repository.commit .content-block.second-block.white diff --git a/config/routes.rb b/config/routes.rb index 36561c31d48..3ef5931d97c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -749,7 +749,7 @@ Rails.application.routes.draw do end resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [] do + resources :badges, only: [:index] do collection do scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do get :build, constraints: { format: /svg/ } -- cgit v1.2.1 From b7fa7c4d59b2fbdc49db81aa2d6a2531c931a2fe Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 1 Apr 2016 13:03:14 +0200 Subject: Extend build status badge, add html/markdown methods --- app/controllers/projects/badges_controller.rb | 2 ++ app/views/projects/badges/index.html.haml | 6 ++--- lib/gitlab/badge/build.rb | 30 ++++++++++++++++++++---- spec/lib/gitlab/badge/build_spec.rb | 33 ++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 01db85c4a8a..b96555a153a 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -2,6 +2,8 @@ class Projects::BadgesController < Projects::ApplicationController before_action :no_cache_headers, except: [:index] def index + @ref = params[:ref] || 'master' + @badge = Gitlab::Badge::Build.new(@project, @ref) end def build diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index b527df8ac98..87c525cc1cb 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -4,12 +4,12 @@ .panel.panel-default .panel-heading %b Builds badge · - = image_tag(build_namespace_project_badges_path(@project.namespace, @project, :master, format: :svg), alt: 'Builds badge') + = @badge.to_html .panel-body %table.table %tr %td Markdown - %td= markdown("```markdown\n[![build status](url)](link)\n```") + %td= markdown("```markdown\n#{@badge.to_markdown}\n```") %tr %td HTML - %td= markdown("```html\n\n```") + %td= markdown("```html\n#{@badge.to_html}\n```") diff --git a/lib/gitlab/badge/build.rb b/lib/gitlab/badge/build.rb index 28a2391dbf8..e5e9fab3f5c 100644 --- a/lib/gitlab/badge/build.rb +++ b/lib/gitlab/badge/build.rb @@ -4,14 +4,15 @@ module Gitlab # Build badge # class Build + include Gitlab::Application.routes.url_helpers + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::UrlHelper + def initialize(project, ref) + @project, @ref = project, ref @image = ::Ci::ImageForBuildService.new.execute(project, ref: ref) end - def to_s - @image[:name].sub(/\.svg$/, '') - end - def type 'image/svg+xml' end @@ -19,6 +20,27 @@ module Gitlab def data File.read(@image[:path]) end + + def to_s + @image[:name].sub(/\.svg$/, '') + end + + def to_html + link_to(image_tag(image_url, alt: 'build status'), link_url) + end + + def to_markdown + "[![build status](#{image_url})](#{link_url})" + end + + def image_url + build_namespace_project_badges_url(@project.namespace, + @project, @ref, format: :svg) + end + + def link_url + namespace_project_commits_url(@project.namespace, @project, id: @ref) + end end end end diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb index b78c2b6224f..329792bb685 100644 --- a/spec/lib/gitlab/badge/build_spec.rb +++ b/spec/lib/gitlab/badge/build_spec.rb @@ -3,13 +3,44 @@ require 'spec_helper' describe Gitlab::Badge::Build do let(:project) { create(:project) } let(:sha) { project.commit.sha } - let(:badge) { described_class.new(project, 'master') } + let(:branch) { 'master' } + let(:badge) { described_class.new(project, branch) } describe '#type' do subject { badge.type } it { is_expected.to eq 'image/svg+xml' } end + describe '#to_html' do + let(:html) { Nokogiri::HTML.parse(badge.to_html) } + let(:a_href) { html.at('a') } + + it 'points to link' do + expect(a_href[:href]).to eq badge.link_url + end + + it 'contains clickable image' do + expect(a_href.children.first.name).to eq 'img' + end + end + + describe '#to_markdown' do + subject { badge.to_markdown } + + it { is_expected.to include badge.image_url } + it { is_expected.to include badge.link_url } + end + + describe '#image_url' do + subject { badge.image_url } + it { is_expected.to include "badges/#{branch}/build.svg" } + end + + describe '#link_url' do + subject { badge.link_url } + it { is_expected.to include "commits/#{branch}" } + end + context 'build exists' do let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } let!(:build) { create(:ci_build, commit: ci_commit) } -- cgit v1.2.1 From 58d63a66a084fc38ffcd72972afa388110e29fc1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 1 Apr 2016 14:54:11 +0200 Subject: Improve view with list of badges --- app/views/projects/badges/index.html.haml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index 87c525cc1cb..52e68ea7080 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -6,10 +6,15 @@ %b Builds badge · = @badge.to_html .panel-body - %table.table - %tr - %td Markdown - %td= markdown("```markdown\n#{@badge.to_markdown}\n```") - %tr - %td HTML - %td= markdown("```html\n#{@badge.to_html}\n```") + .row + .col-md-2.text-center + Markdown + .col-md-10 + = markdown("```markdown\n#{@badge.to_markdown}\n```") + .row + %hr + .row + .col-md-2.text-center + HTML + .col-md-10 + = markdown("```html\n#{@badge.to_html}\n```") -- cgit v1.2.1 From dffdb0d036d037dc4b6a363aa73e581270fc7c80 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 10:03:42 +0200 Subject: Use highlight helper to render badges code syntax --- app/views/projects/badges/index.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index 52e68ea7080..b6524153703 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -9,12 +9,12 @@ .row .col-md-2.text-center Markdown - .col-md-10 - = markdown("```markdown\n#{@badge.to_markdown}\n```") + .col-md-10.code.js-syntax-highlight + = highlight('.md', @badge.to_markdown) .row %hr .row .col-md-2.text-center HTML - .col-md-10 - = markdown("```html\n#{@badge.to_html}\n```") + .col-md-10.code.js-syntax-highlight + = highlight('.html', @badge.to_html) -- cgit v1.2.1 From a2bbd2501a15fa60b36110e1acb3d3141586e353 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 10:10:24 +0200 Subject: Add project header title in project badges view --- app/views/projects/badges/index.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index b6524153703..5cd2ebce099 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -1,4 +1,6 @@ - page_title 'Badges' +- badges_path = namespace_project_badges_path(@project.namespace, @project) +- header_title project_title(@project, 'Badges', badges_path) .prepend-top-10 .panel.panel-default -- cgit v1.2.1 From 97a2de9e03f9ad947642ded3b4f4e7761fcaa9c3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 10:40:40 +0200 Subject: Make it possible to switch ref in badges view --- app/controllers/projects/refs_controller.rb | 2 ++ app/views/projects/badges/index.html.haml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 00df1c9c965..d79f16e6a5a 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController namespace_project_find_file_path(@project.namespace, @project, @id) when "graphs_commits" commits_namespace_project_graph_path(@project.namespace, @project, @id) + when "badges" + namespace_project_badges_path(@project.namespace, @project, ref: @id) else namespace_project_commits_path(@project.namespace, @project, @id) end diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index 5cd2ebce099..fb57bf411dc 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -7,6 +7,8 @@ .panel-heading %b Builds badge · = @badge.to_html + .pull-right + = render 'shared/ref_switcher', destination: 'badges' .panel-body .row .col-md-2.text-center -- cgit v1.2.1 From a3a93ba4b08176b8d63ed5f6ca95dfa78e7dda0b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 12:32:50 +0200 Subject: Expose project badges in project settings menu --- app/controllers/projects/badges_controller.rb | 2 ++ app/views/layouts/nav/_project_settings.html.haml | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index b96555a153a..6e442d66adc 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -1,4 +1,6 @@ class Projects::BadgesController < Projects::ApplicationController + layout 'project_settings' + before_action :authorize_admin_project!, only: [:index] before_action :no_cache_headers, except: [:index] def index diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index dc3050f02e5..d429a928464 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -51,8 +51,13 @@ = icon('code fw') %span Variables - = nav_link path: 'triggers#index' do + = nav_link(controller: :triggers) do = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do = icon('retweet fw') %span Triggers + = nav_link(controller: :badges) do + = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do + = icon('star-half-empty fw') + %span + Badges -- cgit v1.2.1 From 7640b050c06e18200a09bc100d08ed48aa6c549b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 15:05:38 +0200 Subject: Add feature specs for list of badges page --- spec/features/projects/badges/list_spec.rb | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/features/projects/badges/list_spec.rb diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb new file mode 100644 index 00000000000..13c9b95b316 --- /dev/null +++ b/spec/features/projects/badges/list_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +feature 'list of badges' do + include Select2Helper + + background do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as(user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'user displays list of badges' do + click_link 'Badges' + + expect(page).to have_content 'build status' + expect(page).to have_content 'Markdown' + expect(page).to have_content 'HTML' + expect(page).to have_css('.highlight', count: 2) + expect(page).to have_xpath("//img[@alt='build status']") + + page.within('.highlight', match: :first) do + expect(page).to have_content 'badges/master/build.svg' + end + end + + scenario 'user changes current ref on badges list page', js: true do + click_link 'Badges' + select2('improve/awesome', from: '#ref') + + expect(page).to have_content 'badges/improve/awesome/build.svg' + end +end -- cgit v1.2.1 From 15ea971847e0c72d99c8822afe2b1ea181479916 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Apr 2016 19:40:41 +0200 Subject: Remove obsolete badge code from project view --- app/views/projects/show.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 6963d657d9e..4310f038fc9 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -57,9 +57,6 @@ %li.missing = link_to add_contribution_guide_path(@project) do Add Contribution guide - - if @project.builds_enabled? - %li - = link_to 'Badges', '' - if @repository.commit .content-block.second-block.white -- cgit v1.2.1 From 5f32b82a80d517421889cfc89452f8d94fa09ea5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Apr 2016 10:10:29 +0200 Subject: Change name of badge variable in badges controller --- app/controllers/projects/badges_controller.rb | 2 +- app/views/projects/badges/index.html.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 6e442d66adc..9dff74c823b 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -5,7 +5,7 @@ class Projects::BadgesController < Projects::ApplicationController def index @ref = params[:ref] || 'master' - @badge = Gitlab::Badge::Build.new(@project, @ref) + @build_badge = Gitlab::Badge::Build.new(@project, @ref) end def build diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index fb57bf411dc..c22384ddf46 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -6,7 +6,7 @@ .panel.panel-default .panel-heading %b Builds badge · - = @badge.to_html + = @build_badge.to_html .pull-right = render 'shared/ref_switcher', destination: 'badges' .panel-body @@ -14,11 +14,11 @@ .col-md-2.text-center Markdown .col-md-10.code.js-syntax-highlight - = highlight('.md', @badge.to_markdown) + = highlight('.md', @build_badge.to_markdown) .row %hr .row .col-md-2.text-center HTML .col-md-10.code.js-syntax-highlight - = highlight('.html', @badge.to_html) + = highlight('.html', @build_badge.to_html) -- cgit v1.2.1 From 68ac8d38e24d4c63d28f874f1a40e792cfeb4296 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Apr 2016 10:17:20 +0200 Subject: Add Changelog entry for project badges in settings --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index a23c8a54af1..7b900853801 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ v 8.7.0 (unreleased) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) + - Expose project badges in project settings + - Don't attempt to fetch any tags from a forked repo (Stan Hu) + - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu) - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) - Expose label description in API (Mariusz Jachimowicz) -- cgit v1.2.1 From 7689e87854980f72f41dcf6924693fb046e1919b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Apr 2016 19:55:37 +0200 Subject: Use default branch when displaying list of badges --- app/controllers/projects/badges_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 9dff74c823b..824aa41db51 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -4,7 +4,7 @@ class Projects::BadgesController < Projects::ApplicationController before_action :no_cache_headers, except: [:index] def index - @ref = params[:ref] || 'master' + @ref = params[:ref] || @project.default_branch || 'master' @build_badge = Gitlab::Badge::Build.new(@project, @ref) end -- cgit v1.2.1 From d0bce1470bf0eefc2734c996d99ecf97cb9d54d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Apr 2016 20:00:58 +0200 Subject: Fix Changelog entries after rebase --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7b900853801..a02357374de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,8 +5,6 @@ v 8.7.0 (unreleased) - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - Expose project badges in project settings - - Don't attempt to fetch any tags from a forked repo (Stan Hu) - - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu) - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) - Expose label description in API (Mariusz Jachimowicz) -- cgit v1.2.1 From ee87d15cf443e66f47e20b92bb442b9e1874fee7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 19:19:00 -0500 Subject: Change approach, do not use timeouts --- .../javascripts/search_autocomplete.js.coffee | 30 +++++++++------------- app/views/layouts/_search.html.haml | 4 +-- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 564fb265b9d..5a0df7817c9 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -137,13 +137,17 @@ class @SearchAutocomplete } bindEvents: -> + $(document).on 'click', @onDocumentClick @searchInput.on 'keydown', @onSearchInputKeyDown @searchInput.on 'keyup', @onSearchInputKeyUp @searchInput.on 'click', @onSearchInputClick @searchInput.on 'focus', @onSearchInputFocus - @searchInput.on 'blur', @onSearchInputBlur @clearInput.on 'click', @onRemoveLocationClick + onDocumentClick: (e) => + if not $.contains(@dropdown[0], e.target) and @isFocused + @onSearchInputBlur() + enableAutocomplete: -> # No need to enable anything if user is not logged in return if !gon.current_user_id @@ -193,27 +197,21 @@ class @SearchAutocomplete e.stopImmediatePropagation() onSearchInputFocus: => + @isFocused = true @wrap.addClass('search-active') onRemoveLocationClick: (e) => e.preventDefault() @removeLocationBadge() @searchInput.val('').focus() - @skipBlurEvent = true onSearchInputBlur: (e) => - @skipBlurEvent = false - - # We should wait to make sure we are not clearing the input instead - setTimeout( => - return if @skipBlurEvent + @isFocused = false + @wrap.removeClass('search-active') - @wrap.removeClass('search-active') - - # If input is blank then restore state - if @searchInput.val() is '' - @restoreOriginalState() - , 150) + # If input is blank then restore state + if @searchInput.val() is '' + @restoreOriginalState() addLocationBadge: (item) -> category = if item.category? then "#{item.category}: " else '' @@ -291,9 +289,5 @@ class @SearchAutocomplete $el.removeClass('is-active') @disableAutocomplete() + @onSearchInputFocus() @searchInput.val('').focus() - - # We need to wait because of @skipBlurEvent - setTimeout( => - @onSearchInputFocus() - , 200) diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 9d4ab9847a8..ba04bd74b8d 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -21,8 +21,8 @@ %a.is-focused.dropdown-menu-empty-link Loading... = dropdown_loading - %i.search-icon - %i.clear-icon.js-clear-input + %i.search-icon + %i.clear-icon.js-clear-input = hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id' -- cgit v1.2.1 From 0728588c3424fd7e75ca3c45ad1ea84063437311 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 13:03:07 +0200 Subject: API: Ability to filter milestones by state --- CHANGELOG | 1 + doc/api/milestones.md | 3 +++ lib/api/milestones.rb | 20 ++++++++++++++++++-- spec/requests/api/milestones_spec.rb | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a23c8a54af1..531f3607297 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.7.0 (unreleased) - Build status notifications - API: Ability to retrieve a specific tag (Robert Schilling) - API: Expose user location (Robert Schilling) + - API: Ability to milter milestones by state `active` and `closed` (Robert Schilling) v 8.6.5 (unreleased) - Check permissions when user attempts to import members from another project diff --git a/doc/api/milestones.md b/doc/api/milestones.md index a6828728264..4d94d7f3e2c 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -7,6 +7,8 @@ Returns a list of project milestones. ``` GET /projects/:id/milestones GET /projects/:id/milestones?iid=42 +GET /projects/:id/milestones?state=active +GET /projects/:id/milestones?state=closed ``` ```json @@ -29,6 +31,7 @@ Parameters: - `id` (required) - The ID of a project - `iid` (optional) - Return the milestone having the given `iid` +- `state` (optional) - Return "active" or "closed" milestones ## Get single milestone diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index c5cd73943fb..39efa1b176b 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -3,17 +3,33 @@ module API class Milestones < Grape::API before { authenticate! } + helpers do + def filter_milestones_state(milestones, state) + case state + when 'active' then milestones.active + when 'closed' then milestones.closed + else milestones + end + end + end + resource :projects do # Get a list of project milestones # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project + # state (optional) - Return "active" or "closed" milestones # Example Request: # GET /projects/:id/milestones + # GET /projects/:id/milestones?state=active + # GET /projects/:id/milestones?state=closed get ":id/milestones" do authorize! :read_milestone, user_project - present paginate(user_project.milestones), with: Entities::Milestone + milestones = user_project.milestones + milestones = filter_milestones_state(milestones, params[:state]) unless params[:state].nil? + + present paginate(milestones), with: Entities::Milestone end # Get a single project milestone diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index db0f6e3c0f5..281d2b25bf5 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -4,6 +4,7 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } + let!(:closed_milestone) { create(:closed_milestone, project: project, state: :closed) } let!(:milestone) { create(:milestone, project: project) } before { project.team << [user, :developer] } @@ -20,6 +21,22 @@ describe API::API, api: true do get api("/projects/#{project.id}/milestones") expect(response.status).to eq(401) end + + it 'should return an array of active milestones' do + get api("/projects/#{project.id}/milestones?state=active", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(milestone.id) + end + + it 'should return an array of closed milestones' do + get api("/projects/#{project.id}/milestones?state=closed", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_milestone.id) + end end describe 'GET /projects/:id/milestones/:milestone_id' do -- cgit v1.2.1 From e6215a9a8ed20354782120a7ce6368c7e8aab9a5 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 20:53:17 +0200 Subject: Improve coding and doc style --- CHANGELOG | 2 +- doc/api/milestones.md | 19 ++++++++++++++----- lib/api/milestones.rb | 2 +- spec/requests/api/milestones_spec.rb | 8 +++++--- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 531f3607297..ff86e66e16e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.7.0 (unreleased) - Handle nil descriptions in Slack issue messages (Stan Hu) - Add default scope to projects to exclude projects pending deletion - Ensure empty recipients are rejected in BuildsEmailService + - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) @@ -25,7 +26,6 @@ v 8.7.0 (unreleased) - Build status notifications - API: Ability to retrieve a specific tag (Robert Schilling) - API: Expose user location (Robert Schilling) - - API: Ability to milter milestones by state `active` and `closed` (Robert Schilling) v 8.6.5 (unreleased) - Check permissions when user attempts to import members from another project diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 4d94d7f3e2c..e4202025f80 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -11,6 +11,20 @@ GET /projects/:id/milestones?state=active GET /projects/:id/milestones?state=closed ``` +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `iid` | integer | optional | Return only the milestone having the given `iid` | +| `state` | string | optional | Return only `active` or `closed` milestones` | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/milestones +``` + +Example Response: + ```json [ { @@ -27,11 +41,6 @@ GET /projects/:id/milestones?state=closed ] ``` -Parameters: - -- `id` (required) - The ID of a project -- `iid` (optional) - Return the milestone having the given `iid` -- `state` (optional) - Return "active" or "closed" milestones ## Get single milestone diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 39efa1b176b..afb6ffa3609 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -27,7 +27,7 @@ module API authorize! :read_milestone, user_project milestones = user_project.milestones - milestones = filter_milestones_state(milestones, params[:state]) unless params[:state].nil? + milestones = filter_milestones_state(milestones, params[:state]) present paginate(milestones), with: Entities::Milestone end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 281d2b25bf5..d97bf6d38ff 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -4,7 +4,7 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } - let!(:closed_milestone) { create(:closed_milestone, project: project, state: :closed) } + let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:milestone) { create(:milestone, project: project) } before { project.team << [user, :developer] } @@ -22,16 +22,18 @@ describe API::API, api: true do expect(response.status).to eq(401) end - it 'should return an array of active milestones' do + it 'returns an array of active milestones' do get api("/projects/#{project.id}/milestones?state=active", user) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(milestone.id) end - it 'should return an array of closed milestones' do + it 'returns an array of closed milestones' do get api("/projects/#{project.id}/milestones?state=closed", user) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) -- cgit v1.2.1 From 5f4ce722c0f5fbf9129dddf20de879f00db4a311 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 6 Apr 2016 13:55:49 -0500 Subject: Display clear button only if input has a value --- app/assets/javascripts/search_autocomplete.js.coffee | 7 ++++--- app/assets/stylesheets/pages/search.scss | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 5a0df7817c9..9c85ce1633b 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -142,7 +142,7 @@ class @SearchAutocomplete @searchInput.on 'keyup', @onSearchInputKeyUp @searchInput.on 'click', @onSearchInputClick @searchInput.on 'focus', @onSearchInputFocus - @clearInput.on 'click', @onRemoveLocationClick + @clearInput.on 'click', @onClearInputClick onDocumentClick: (e) => if not $.contains(@dropdown[0], e.target) and @isFocused @@ -189,6 +189,8 @@ class @SearchAutocomplete # We should display the menu only when input is not empty @enableAutocomplete() + @wrap.toggleClass 'has-value', !!e.target.value + # Avoid falsy value to be returned return @@ -200,9 +202,8 @@ class @SearchAutocomplete @isFocused = true @wrap.addClass('search-active') - onRemoveLocationClick: (e) => + onClearInputClick: (e) => e.preventDefault() - @removeLocationBadge() @searchInput.val('').focus() onSearchInputBlur: (e) => diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 9274796233b..f0f3744c6fa 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -144,16 +144,16 @@ color: $location-icon-active-color; } } + } - &.has-location-badge { - .search-icon { - display: none; - } + &.has-value { + .search-icon { + display: none; + } - .clear-icon { - cursor: pointer; - display: block; - } + .clear-icon { + cursor: pointer; + display: block; } } -- cgit v1.2.1 From dc3272dccbf2462ad8a7ee8f677086ffabae3afb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 21:03:24 +0200 Subject: Revert "API: Ability to retrieve a single tag" This reverts commit 7f287c9136d5d1cdda8df170c6e772ca82aad1e9. --- doc/api/tags.md | 34 +--------------------------------- lib/api/tags.rb | 14 -------------- spec/requests/api/tags_spec.rb | 17 ----------------- 3 files changed, 1 insertion(+), 64 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index c451a42b725..17d12e9cc62 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -38,38 +38,6 @@ Parameters: ] ``` -## Get a single repository tag - -Get a specific repository tag determined by its name. It returns 200 together -with the tag information if the tag exists. It returns 404 if the tag does not -exist. - -Parameters: - -- `id` (required) - The ID of a project -- `tag_name` (required) - The name of the tag - -```json -{ - "name": "v5.0.0", - "message": null, - "commit": { - "id": "60a8ff033665e1207714d6670fcd7b65304ec02f", - "message": "v5.0.0\n", - "parent_ids": [ - "f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b" - ], - "authored_date": "2015-02-01T21:56:31.000+01:00", - "author_name": "Arthur Verschaeve", - "author_email": "contact@arthurverschaeve.be", - "committed_date": "2015-02-01T21:56:31.000+01:00", - "committer_name": "Arthur Verschaeve", - "committer_email": "contact@arthurverschaeve.be" - }, - "release": null -} -``` - ## Create a new tag Creates a new tag in the repository that points to the supplied ref. @@ -180,4 +148,4 @@ Parameters: "tag_name": "1.0.0", "description": "Amazing release. Wow" } -``` +``` \ No newline at end of file diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 731a68082ba..2d8a9e51bb9 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -16,20 +16,6 @@ module API with: Entities::RepoTag, project: user_project end - # Get a single repository tag - # - # Parameters: - # id (required) - The ID of a project - # tag_name (required) - The name of the tag - # Example Request: - # GET /projects/:id/repository/tags/:tag_name - get ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do - tag = user_project.repository.find_tag(params[:tag_name]) - not_found!('Tag') unless tag - - present tag, with: Entities::RepoTag, project: user_project - end - # Create tag # # Parameters: diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index acbd9c3e332..a15be07ed57 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -40,23 +40,6 @@ describe API::API, api: true do end end - describe "GET /projects/:id/repository/tags/:tag_name" do - let(:tag_name) { project.repository.tag_names.sort.reverse.first } - - it 'should return a specific tag' do - get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - - expect(response.status).to eq(200) - expect(json_response['name']).to eq(tag_name) - end - - it 'should return 404 for an invalid tag name' do - get api("/projects/#{project.id}/repository/tags/foobar", user) - - expect(response.status).to eq(404) - end - end - describe 'POST /projects/:id/repository/tags' do context 'lightweight tags' do it 'should create a new tag' do -- cgit v1.2.1 From 0b5ff4714caeedf359ed5a2477bada693ccb24a9 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Fri, 1 Apr 2016 18:41:36 +0100 Subject: change the subscribe, delete and edit buttons to icons --- app/assets/stylesheets/pages/labels.scss | 30 ++++++++++++++++++++++++++++++ app/views/projects/labels/_label.html.haml | 17 ++++++++++------- app/views/shared/_label_row.html.haml | 3 ++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 4e02ec4e891..cce34418c58 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -49,6 +49,11 @@ } .label-row { + .label-name { + display: inline-block; + width: 200px; + } + .label { padding: 9px; font-size: 14px; @@ -69,3 +74,28 @@ background-color: $gl-danger; color: $white-light; } + +.manage-labels-list { + + .prepend-left-10 { + display: inline-block; + width: 440px; + } + + .pull-right { + .action-buttons { + border-color: transparent; + margin: 7px; + } + + i { + color: $gl-text-color; + } + + .append-right-20 { + a { + color: $gl-text-color; + } + } + } +} diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 0612863296a..5e2dd8ac61e 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -2,23 +2,26 @@ = render "shared/label_row", label: label .pull-right - %strong.append-right-20 + %span.append-right-20 = link_to_label(label, type: :merge_request) do - = pluralize label.open_merge_requests_count, 'open merge request' + = pluralize label.open_merge_requests_count, 'merge request' - %strong.append-right-20 + %span.append-right-20 = link_to_label(label) do = pluralize label.open_issues_count(current_user), 'open issue' - if current_user .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %button.btn.btn-sm.btn-info.subscribe-button - %span= label_subscription_toggle_button_text(label) + + %a.subscribe-button.action-buttons{data: {toggle: "tooltip", original_title: label_subscription_toggle_button_text(label)}} + %i.fa.fa-rss - if can? current_user, :admin_label, @project - = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm' - = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to edit_namespace_project_label_path(@project.namespace, @project, label), class: 'action-buttons', data: {toggle: "tooltip", original_title: "Edit"} do + %i.fa.fa-pencil-square-o + = link_to namespace_project_label_path(@project.namespace, @project, label), class: 'action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip", original_title: "Delete"} do + %i.fa.fa-trash-o - if current_user :javascript diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 4b47b0291be..b38c5e18efb 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,4 +1,5 @@ %span.label-row - = link_to_label(label, tooltip: false) + %span.label-name + = link_to_label(label, tooltip: false) %span.prepend-left-10 = markdown(label.description, pipeline: :single_line) -- cgit v1.2.1 From 802c6e7bba0444ec5fa4757da30c21fd28a5ccbc Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Fri, 1 Apr 2016 19:12:11 +0100 Subject: set the width to the elements --- app/assets/stylesheets/pages/labels.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index cce34418c58..1d5d402ba04 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -51,7 +51,7 @@ .label-row { .label-name { display: inline-block; - width: 200px; + width: 21%; } .label { @@ -79,10 +79,11 @@ .prepend-left-10 { display: inline-block; - width: 440px; + width: 45%; } .pull-right { + .action-buttons { border-color: transparent; margin: 7px; -- cgit v1.2.1 From 02dcc4188c8816cd4363a98e60ae54309623ea6f Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Sat, 2 Apr 2016 22:54:22 +0100 Subject: make the view responsive for extra small device --- app/assets/stylesheets/pages/labels.scss | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 1d5d402ba04..e77d43fa29c 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -52,6 +52,10 @@ .label-name { display: inline-block; width: 21%; + + @media (max-width: $screen-xs-min) { + display: block; + } } .label { @@ -80,10 +84,21 @@ .prepend-left-10 { display: inline-block; width: 45%; + + @media (max-width: $screen-xs-min) { + display: block; + width: 100%; + margin-left: 0px; + padding: 10px 0; + } } .pull-right { + @media (max-width: $screen-xs-min) { + float: none !important; + } + .action-buttons { border-color: transparent; margin: 7px; @@ -97,6 +112,11 @@ a { color: $gl-text-color; } + + @media (max-width: $screen-xs-min) { + display: block; + margin-bottom: 10px; + } } } } -- cgit v1.2.1 From 8d28f94e7bbf09ecf49ae9d7fab8c9a76abeb7ac Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Sun, 3 Apr 2016 00:55:59 +0100 Subject: finish up the design and add info to the changelog --- CHANGELOG | 1 + app/assets/javascripts/subscription.js.coffee | 3 +++ app/assets/stylesheets/pages/labels.scss | 11 +++++++---- app/views/projects/labels/_label.html.haml | 8 ++++---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a23c8a54af1..ecca821cd96 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.6.5 (unreleased) v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) + - Redesign the Labels page v 8.6.3 - Mentions on confidential issues doesn't create todos for non-members. !3374 diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index 084f0e0dc65..6ebd0750ffd 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -3,8 +3,10 @@ class @Subscription $container = $(container) @url = $container.attr('data-url') @subscribe_button = $container.find('.subscribe-button') + @subscribe_icon = $container.find('.subscribe-icon') @subscription_status = $container.find('.subscription-status') @subscribe_button.unbind('click').click(@toggleSubscription) + @subscribe_icon.unbind('click').click(@toggleSubscription) toggleSubscription: (event) => btn = $(event.currentTarget) @@ -16,6 +18,7 @@ class @Subscription btn.prop('disabled', false) status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed' @subscription_status.attr('data-status', status) + @subscribe_icon.attr('data-original-title', status) action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' btn.find('span').text(action) @subscription_status.find('>div').toggleClass('hidden') diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index e77d43fa29c..0e4468ad2c7 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -49,9 +49,11 @@ } .label-row { + .label-name { display: inline-block; - width: 21%; + width: 200px; + vertical-align: top; @media (max-width: $screen-xs-min) { display: block; @@ -93,15 +95,16 @@ } } - .pull-right { + .pull-info-right { + float: right; @media (max-width: $screen-xs-min) { - float: none !important; + float: none; } .action-buttons { border-color: transparent; - margin: 7px; + padding: 6px } i { diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 5e2dd8ac61e..4c144c317fd 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,7 +1,7 @@ %li{id: dom_id(label)} = render "shared/label_row", label: label - .pull-right + .pull-info-right %span.append-right-20 = link_to_label(label, type: :merge_request) do = pluralize label.open_merge_requests_count, 'merge request' @@ -14,13 +14,13 @@ .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %a.subscribe-button.action-buttons{data: {toggle: "tooltip", original_title: label_subscription_toggle_button_text(label)}} + %a.subscribe-icon.btn.action-buttons{data: {toggle: "tooltip", original_title: label_subscription_status(label)}} %i.fa.fa-rss - if can? current_user, :admin_label, @project - = link_to edit_namespace_project_label_path(@project.namespace, @project, label), class: 'action-buttons', data: {toggle: "tooltip", original_title: "Edit"} do + = link_to edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn action-buttons', data: {toggle: "tooltip", original_title: "Edit"} do %i.fa.fa-pencil-square-o - = link_to namespace_project_label_path(@project.namespace, @project, label), class: 'action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip", original_title: "Delete"} do + = link_to namespace_project_label_path(@project.namespace, @project, label), class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip", original_title: "Delete"} do %i.fa.fa-trash-o - if current_user -- cgit v1.2.1 From ffe2f8e679f7718e5064acb0c2504b2b3cc160de Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 4 Apr 2016 18:38:16 +0100 Subject: fix the failing tests and some changes --- app/assets/javascripts/subscription.js.coffee | 7 ++----- app/assets/stylesheets/pages/labels.scss | 15 +++++++++++---- app/views/projects/labels/_label.html.haml | 8 ++++---- features/steps/project/issues/labels.rb | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index 6ebd0750ffd..e4b7a3172ec 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -3,22 +3,19 @@ class @Subscription $container = $(container) @url = $container.attr('data-url') @subscribe_button = $container.find('.subscribe-button') - @subscribe_icon = $container.find('.subscribe-icon') @subscription_status = $container.find('.subscription-status') @subscribe_button.unbind('click').click(@toggleSubscription) - @subscribe_icon.unbind('click').click(@toggleSubscription) toggleSubscription: (event) => btn = $(event.currentTarget) action = btn.find('span').text() current_status = @subscription_status.attr('data-status') - btn.prop('disabled', true) + btn.addClass('disabled') $.post @url, => - btn.prop('disabled', false) + btn.removeClass('disabled') status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed' @subscription_status.attr('data-status', status) - @subscribe_icon.attr('data-original-title', status) action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' btn.find('span').text(action) @subscription_status.find('>div').toggleClass('hidden') diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 0e4468ad2c7..5dab9999059 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -53,7 +53,7 @@ .label-name { display: inline-block; width: 200px; - vertical-align: top; + vertical-align: baseline; @media (max-width: $screen-xs-min) { display: block; @@ -85,12 +85,13 @@ .prepend-left-10 { display: inline-block; - width: 45%; + width: 40%; + vertical-align: text-top; @media (max-width: $screen-xs-min) { display: block; width: 100%; - margin-left: 0px; + margin-left: 0; padding: 10px 0; } } @@ -104,7 +105,13 @@ .action-buttons { border-color: transparent; - padding: 6px + padding: 6px; + color: $gl-text-color; + vertical-align: initial; + + &.subscribe-button { + padding-left: 0; + } } i { diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 4c144c317fd..097a65969a6 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -14,13 +14,13 @@ .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %a.subscribe-icon.btn.action-buttons{data: {toggle: "tooltip", original_title: label_subscription_status(label)}} - %i.fa.fa-rss + %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}} + %span= label_subscription_toggle_button_text(label) - if can? current_user, :admin_label, @project - = link_to edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn action-buttons', data: {toggle: "tooltip", original_title: "Edit"} do + = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: {toggle: "tooltip"} do %i.fa.fa-pencil-square-o - = link_to namespace_project_label_path(@project.namespace, @project, label), class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip", original_title: "Delete"} do + = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do %i.fa.fa-trash-o - if current_user diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 2ab8956867b..0ca2d6257c3 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -15,7 +15,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps step 'I delete all labels' do page.within '.labels' do - page.all('.btn-remove').each do |remove| + page.all('.remove-row').each do |remove| remove.click sleep 0.05 end -- cgit v1.2.1 From fc7b5bd49cf48d87f770acb5377c85c7a034b701 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Tue, 5 Apr 2016 19:17:30 +0100 Subject: apply some css changes --- app/assets/stylesheets/pages/labels.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 5dab9999059..3e0a3140be7 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -49,11 +49,9 @@ } .label-row { - .label-name { display: inline-block; width: 200px; - vertical-align: baseline; @media (max-width: $screen-xs-min) { display: block; @@ -86,7 +84,7 @@ .prepend-left-10 { display: inline-block; width: 40%; - vertical-align: text-top; + vertical-align: middle; @media (max-width: $screen-xs-min) { display: block; @@ -107,7 +105,6 @@ border-color: transparent; padding: 6px; color: $gl-text-color; - vertical-align: initial; &.subscribe-button { padding-left: 0; -- cgit v1.2.1 From d1ad639010ecb8619db08f412babc6fb9e23458e Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 6 Apr 2016 14:28:15 -0500 Subject: Do not fire blur event when clicking a suggestion --- app/assets/javascripts/search_autocomplete.js.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 9c85ce1633b..6a7b4ad1db7 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -145,7 +145,10 @@ class @SearchAutocomplete @clearInput.on 'click', @onClearInputClick onDocumentClick: (e) => - if not $.contains(@dropdown[0], e.target) and @isFocused + # If clicking outside the search box + # And search input is not focused + # And we are not clicking inside a suggestion + if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length @onSearchInputBlur() enableAutocomplete: -> @@ -290,5 +293,4 @@ class @SearchAutocomplete $el.removeClass('is-active') @disableAutocomplete() - @onSearchInputFocus() @searchInput.val('').focus() -- cgit v1.2.1 From 935bf7271d88c8623312004dd6cba791b76503f5 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 5 Apr 2016 17:44:13 -0500 Subject: Only update main language if it is not already set --- CHANGELOG | 1 + app/services/git_push_service.rb | 10 +++++----- spec/services/git_push_service_spec.rb | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8db9a9b0d1e..49140eea6c6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.7.0 (unreleased) - Build status notifications v 8.6.5 (unreleased) + - Only update repository language if it is not set to improve performance - Check permissions when user attempts to import members from another project v 8.6.4 diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 36c9ee92da1..dc74c02760b 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -55,15 +55,15 @@ class GitPushService < BaseService end def update_main_language + # Performance can be bad so for now only check main_language once + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937 + return if @project.main_language.present? + return unless is_default_branch? return unless push_to_new_branch? || push_to_existing_branch? current_language = @project.repository.main_language - - unless current_language == @project.main_language - return @project.update_attributes(main_language: current_language) - end - + @project.update_attributes(main_language: current_language) true end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 1047e32960e..b40a5c1c818 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -164,21 +164,37 @@ describe GitPushService, services: true do end context "after push" do - before do - @service = execute_service(project, user, @oldrev, @newrev, ref) + def execute + execute_service(project, user, @oldrev, @newrev, ref) end context "to master" do let(:ref) { @ref } - it { expect(@service.update_main_language).to eq(true) } - it { expect(project.main_language).to eq("Ruby") } + context 'when main_language is nil' do + it 'obtains the language from the repository' do + expect(project.repository).to receive(:main_language) + execute + end + + it 'sets the project main language' do + execute + expect(project.main_language).to eq("Ruby") + end + end + + context 'when main_language is already set' do + it 'does not check the repository' do + execute # do an initial run to simulate lang being preset + expect(project.repository).not_to receive(:main_language) + execute + end + end end context "to other branch" do let(:ref) { 'refs/heads/feature/branch' } - it { expect(@service.update_main_language).to eq(nil) } it { expect(project.main_language).to eq(nil) } end end -- cgit v1.2.1 From 3d145ce8d513207988d523288334a61a575d4c01 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 24 Mar 2016 12:28:06 +0100 Subject: fix labels not showing on todos page --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/events.scss | 11 ++++++++++- app/assets/stylesheets/pages/todos.scss | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 463ce4ecdd7..6d188659524 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -28,6 +28,7 @@ $gl-link-color: #3084bb; $gl-dark-link-color: #333; $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; +$gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-header-color: $gl-title-color; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index c66efe978cd..6fe57c737b3 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -41,8 +41,17 @@ word-wrap: break-word; .md { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: $gl-font-size; + + .label { + color: $gl-text-color; + font-size: inherit; + } + + iframe.twitter-share-button { + vertical-align: bottom; + } } pre { diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index e83fa9e3d52..75f78569e3c 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -34,6 +34,11 @@ color: #7f8fa4; font-size: $gl-font-size; + .label { + color: $gl-text-color; + font-size: inherit; + } + p { color: #5c5d5e; } -- cgit v1.2.1 From ebb91c7f0428ade28d78bcec4c02b60258cdab68 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Apr 2016 09:15:01 -0700 Subject: Wrap code blocks to next line --- app/assets/stylesheets/framework/typography.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 7b2aada5a0d..be64d932213 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -16,8 +16,9 @@ code { font-family: $monospace_font; - white-space: pre; + white-space: pre-wrap; word-wrap: normal; + word-break: break-word; } kbd { -- cgit v1.2.1 From dcdf0b2af2514f9f6778fd16cbcb77929a0bd708 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Apr 2016 13:20:13 -0700 Subject: Horizontally scroll code blocks instead of wrapping --- app/assets/stylesheets/framework/files.scss | 1 + app/assets/stylesheets/framework/markdown_area.scss | 1 + app/assets/stylesheets/framework/typography.scss | 3 +-- app/assets/stylesheets/pages/detail_page.scss | 8 ++++++-- app/assets/stylesheets/pages/diff.scss | 5 +++++ app/assets/stylesheets/pages/notes.scss | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index a26ace5cc19..22f53e3b038 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -20,6 +20,7 @@ margin: 0; text-align: left; padding: 10px $gl-padding; + word-break: break-word; .file-actions { float: right; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index ea8e1c902cb..c8f86d60e3b 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -84,6 +84,7 @@ .md-preview-holder { min-height: 167px; padding: 10px 0; + overflow-x: auto; } .markdown-area { diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index be64d932213..7b2aada5a0d 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -16,9 +16,8 @@ code { font-family: $monospace_font; - white-space: pre-wrap; + white-space: pre; word-wrap: normal; - word-break: break-word; } kbd { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index d3eda1a57e6..5917f089720 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -33,8 +33,12 @@ .description { margin-top: 6px; - p:last-child { - margin-bottom: 0; + p { + overflow-x: auto; + + &:last-child { + margin-bottom: 0; + } } } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f1368d74b3b..7a12aa96476 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -59,10 +59,15 @@ border-collapse: separate; margin: 0; padding: 0; + .line_holder td { line-height: $code_line_height; font-size: $code_font_size; } + + td { + white-space: nowrap; + } } tr.line_holder.parallel { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 614ca48b901..aca86457c70 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -82,7 +82,7 @@ ul.notes { // On diffs code should wrap nicely and not overflow pre { code { - white-space: pre-wrap; + white-space: pre; } } -- cgit v1.2.1 From b3dd470854f83616bb6bd36bd73680bfda869d81 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 22:46:07 +0200 Subject: Fix wrong changelog item --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a23c8a54af1..92a0d8547e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,7 +23,6 @@ v 8.7.0 (unreleased) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - Improved UX of the navigation sidebar - Build status notifications - - API: Ability to retrieve a specific tag (Robert Schilling) - API: Expose user location (Robert Schilling) v 8.6.5 (unreleased) -- cgit v1.2.1 From 3a36fa895724aedb4bd919ec91cc00a24415e712 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Apr 2016 16:03:35 -0500 Subject: Fix error that was causing only one group to be returned and corrected specs to use the proper attribute type --- lib/gitlab/saml/auth_hash.rb | 4 +++- spec/lib/gitlab/saml/user_spec.rb | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/saml/auth_hash.rb b/lib/gitlab/saml/auth_hash.rb index 5ffccc0e100..3414d24ca73 100644 --- a/lib/gitlab/saml/auth_hash.rb +++ b/lib/gitlab/saml/auth_hash.rb @@ -9,7 +9,9 @@ module Gitlab private def get_raw(key) - auth_hash.extra[:raw_info][key] + # Needs to call `all` because of https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/attributes.rb#L78 + # otherwise just the first value is returned + auth_hash.extra[:raw_info].all[key] end end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 84a26af940b..ed7b507dbab 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do let(:gl_user) { saml_user.gl_user } let(:uid) { 'my-uid' } let(:provider) { 'saml' } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: { groups: %w(Developers Freelancers Designers) } }) } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new({ 'groups' => %w(Developers Freelancers Designers) }) }) } let(:info_hash) do { name: 'John', @@ -48,6 +48,7 @@ describe Gitlab::Saml::User, lib: true do before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } it 'marks the user as external' do saml_user.save + expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end end @@ -56,6 +57,7 @@ describe Gitlab::Saml::User, lib: true do context 'are defined but the user does not belong there' do it 'does not mark the user as external' do saml_user.save + expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end end @@ -64,6 +66,7 @@ describe Gitlab::Saml::User, lib: true do it 'should make user internal' do existing_user.update_attribute('external', true) saml_user.save + expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end end @@ -105,6 +108,7 @@ describe Gitlab::Saml::User, lib: true do before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } it 'marks the user as external' do saml_user.save + expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end end @@ -113,6 +117,7 @@ describe Gitlab::Saml::User, lib: true do context 'are defined but the user does not belong there' do it 'does not mark the user as external' do saml_user.save + expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end end -- cgit v1.2.1 From b95ac0962c96c933bc85b89558733055a21d3dd5 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Apr 2016 16:04:11 -0500 Subject: Added documentation --- config/gitlab.yml.example | 1 + doc/integration/saml.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c607e32a05d..ddee467b14b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -354,6 +354,7 @@ production: &base # issuer: 'https://gitlab.example.com', # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' # } } + # # - { name: 'crowd', # args: { # crowd_server_url: 'CROWD SERVER URL', diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 1c3dc707f6d..684f0a5b8c9 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -131,8 +131,76 @@ On the sign in page there should now be a SAML button below the regular sign in Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in. +## External Groups + +>**Note:** +This setting is only available on GitLab 8.7 and above. + +SAML login includes support for external groups. You can define in the SAML +settings which groups, to which your users belong in your IdP, you wish to be +marked as [external](../permissions/permissions.md). + +### Requirements + +First you need to tell GitLab where to look for group information. For this you +need to make sure that your IdP server sends a specific `AttributeStament` along +with the regular SAML response. Here is an example: + +```xml + + + SecurityGroup + Developers + Designers + + +``` + +The name of the attribute can be anything you like, but it must contain the groups +to which a user belongs. In order to tell GitLab where to find these groups, you need +to add a `groups_attribute:` element to your SAML settings. You will also need to +tell GitLab which groups are external, for that you need the `external_groups:` +element: + +```yaml +{ name: 'saml', + label: 'Our SAML Provider', + groups_attribute: 'Groups', + external_groups: ['Freelancers', 'Interns'], + args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + } } +``` + ## Customization +### `auto_sign_in_with_provider` + +You can add this setting to your GitLab configuration to automatically redirect you +to your SAML server for authentication, thus removing the need to click a button +before actually signing in. + +For omnibus package: + +```ruby +gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml' +``` + +For installations from source: + +```yaml +omniauth: + auto_sign_in_with_provider: saml +``` + +Please keep in mind that every sign in attempt will be redirected to the SAML server, +so you will not be able to sign in using local credentials. Make sure that at least one +of the SAML users has admin permissions. + ### `attribute_statements` >**Note:** @@ -205,6 +273,10 @@ To bypass this you can add `skip_before_action :verify_authenticity_token` to th where it can then be seen in the usual logs, or as a flash message in the login screen. +That file is located at `/opt/gitlab/embedded/service/gitlab-rails/app/controllers` +for Omnibus installations and by default on `/home/git/gitlab/app/controllers` for +installations from source. + ### Invalid audience This error means that the IdP doesn't recognize GitLab as a valid sender and -- cgit v1.2.1 From eb0f1de36c6ebf13d86540bb79d7fb07fc3642f0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Apr 2016 16:21:58 -0500 Subject: Added CHANGELOG item --- CHANGELOG | 1 + lib/gitlab/saml/user.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f72bb670ece..1b3fd97a1cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.7.0 (unreleased) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API - Fix avatar stretching by providing a cropping feature + - Allow SAML to handle external users based on user's information !3530 - Add endpoints to archive or unarchive a project !3372 - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index 6ab165cf518..73fc443a02b 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -33,7 +33,6 @@ module Gitlab # Avoid an unnecessary change of values and the subsequent save @user.external = false if @user.external else - # Avoid an unnecessary change of values and the subsequent save @user.external = true unless @user.external end end -- cgit v1.2.1 From bc701284f2eb7e11f652211f45433ae206372bc1 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 23:27:42 +0200 Subject: Fix missing entries in permission matrix [ci skip] --- doc/permissions/permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 3d375e47c8e..cd0e81d7304 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -52,8 +52,8 @@ documentation](../workflow/add-user/add-user.md). | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | -| Force push to protected branches | | | | | | -| Remove protected branches | | | | | | +| Force push to protected branches | | | | | ✓ | +| Remove protected branches | | | | | ✓ | [^1]: If **Allow guest to access builds** is enabled in CI settings -- cgit v1.2.1 From 946b4519c99a91c18a23041c090568231410785a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 6 Apr 2016 16:31:34 -0500 Subject: Use .new_record? instead --- app/views/layouts/_search.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index ce9df1566d5..baa93ba4c1d 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,6 +1,6 @@ -- if controller.controller_path =~ /^groups/ && !@group.new_record? +- if controller.controller_path =~ /^groups/ && @group.persisted? - label = 'This group' -- if controller.controller_path =~ /^projects/ && !@project.new_record? +- if controller.controller_path =~ /^projects/ && @project.persisted? - label = 'This project' .search.search-form{class: "#{'has-location-badge' if label.present?}"} -- cgit v1.2.1 From 86d346b6f4991e0562061f9b9709cffc16fe1ee7 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Apr 2016 14:40:29 -0700 Subject: Change word-break to word-wrap --- app/assets/stylesheets/framework/files.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 22f53e3b038..b15f4e7bd5e 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -20,7 +20,7 @@ margin: 0; text-align: left; padding: 10px $gl-padding; - word-break: break-word; + word-wrap: break-word; .file-actions { float: right; -- cgit v1.2.1 From 43b921770565518dfa03f3810915cb0f98e856c6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 23:49:12 +0200 Subject: Fix missing entries in permission matrix [ci skip] --- doc/permissions/permissions.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index cd0e81d7304..6219693b8a8 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -52,10 +52,11 @@ documentation](../workflow/add-user/add-user.md). | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | -| Force push to protected branches | | | | | ✓ | -| Remove protected branches | | | | | ✓ | +| Force push to protected branches [^2] | | | | | | +| Remove protected branches [^2] | | | | | | [^1]: If **Allow guest to access builds** is enabled in CI settings +[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner ## Group -- cgit v1.2.1 From 71b5010a940c4b0364126ba7e2dfc0ce07b69f25 Mon Sep 17 00:00:00 2001 From: frodsan Date: Wed, 6 Apr 2016 22:45:56 +0000 Subject: [ci skip] Fix typo. --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 4316f3c1f64..7da9b31e30d 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -38,7 +38,7 @@ services: - postgres before_script: - - bundle_install + - bundle install stages: - build -- cgit v1.2.1 From 1575a95b65d4cad91f775ae260b8828cdf303462 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 6 Apr 2016 20:09:15 -0300 Subject: little refactor and improvements on specs --- lib/banzai/filter/gollum_tags_filter.rb | 8 ++------ spec/lib/banzai/filter/gollum_tags_filter_spec.rb | 2 +- spec/lib/banzai/pipeline/wiki_pipeline_spec.rb | 9 +++------ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index f710a4971b0..d08267a9d6c 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -118,7 +118,7 @@ module Banzai end if path - content_tag(:img, nil, src: path, class: tag_class('image')) + content_tag(:img, nil, src: path, class: 'gfm') end end @@ -155,7 +155,7 @@ module Banzai href = ::File.join(project_wiki_base_path, reference) end - content_tag(:a, name || reference, href: href, class: tag_class('page')) + content_tag(:a, name || reference, href: href, class: 'gfm') end def project_wiki @@ -172,10 +172,6 @@ module Banzai def validate needs :project_wiki end - - def tag_class(type) - "gfm gollum gollum-#{type}" - end end end end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index ad777fe4b9c..fe2ce092e6b 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -70,7 +70,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do end context 'linking internal resources' do - it "the created link's text will include the resource's text and project_wiki_base_path" do + it "the created link's text includes the resource's text and wiki base path" do tag = '[[wiki-slug]]' doc = filter("See #{tag}", project_wiki: project_wiki) expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug') diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 4f86b1d87e3..7aa1b4a3bf6 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -1,9 +1,6 @@ require 'rails_helper' describe Banzai::Pipeline::WikiPipeline do - let(:project_wiki) { double } - before(:each) { allow(project_wiki).to receive(:wiki_base_path) { '/some/repo/wikis' } } - describe 'TableOfContents' do it 'replaces the tag with the TableOfContentsFilter result' do markdown = <<-MD.strip_heredoc @@ -14,7 +11,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - result = described_class.call(markdown, project: spy, project_wiki: project_wiki) + result = described_class.call(markdown, project: spy, project_wiki: spy) aggregate_failures do expect(result[:output].text).not_to include '[[' @@ -32,7 +29,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: project_wiki) + output = described_class.to_html(markdown, project: spy, project_wiki: spy) expect(output).to include('[[toc]]') end @@ -45,7 +42,7 @@ describe Banzai::Pipeline::WikiPipeline do Foo MD - output = described_class.to_html(markdown, project: spy, project_wiki: project_wiki) + output = described_class.to_html(markdown, project: spy, project_wiki: spy) aggregate_failures do expect(output).not_to include('
        ') -- cgit v1.2.1 From 8110e7530902de8744ff985f08938306e2c38367 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Apr 2016 18:12:25 -0500 Subject: Implemented suggested fixes --- doc/integration/saml.md | 3 +-- lib/gitlab/saml/auth_hash.rb | 2 +- lib/gitlab/saml/config.rb | 1 - lib/gitlab/saml/user.rb | 7 +++---- spec/lib/gitlab/saml/user_spec.rb | 41 +++++++++++++++------------------------ 5 files changed, 21 insertions(+), 33 deletions(-) diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 684f0a5b8c9..8a7205caaa4 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -159,8 +159,7 @@ with the regular SAML response. Here is an example: The name of the attribute can be anything you like, but it must contain the groups to which a user belongs. In order to tell GitLab where to find these groups, you need to add a `groups_attribute:` element to your SAML settings. You will also need to -tell GitLab which groups are external, for that you need the `external_groups:` -element: +tell GitLab which groups are external via the `external_groups:` element: ```yaml { name: 'saml', diff --git a/lib/gitlab/saml/auth_hash.rb b/lib/gitlab/saml/auth_hash.rb index 3414d24ca73..32c1c9ec5bb 100644 --- a/lib/gitlab/saml/auth_hash.rb +++ b/lib/gitlab/saml/auth_hash.rb @@ -9,7 +9,7 @@ module Gitlab private def get_raw(key) - # Needs to call `all` because of https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/attributes.rb#L78 + # Needs to call `all` because of https://git.io/vVo4u # otherwise just the first value is returned auth_hash.extra[:raw_info].all[key] end diff --git a/lib/gitlab/saml/config.rb b/lib/gitlab/saml/config.rb index 2b3cf840f61..0f40c00f547 100644 --- a/lib/gitlab/saml/config.rb +++ b/lib/gitlab/saml/config.rb @@ -1,4 +1,3 @@ -# Load a specific server configuration module Gitlab module Saml class Config diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index 73fc443a02b..c1072452abe 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -28,12 +28,11 @@ module Gitlab if external_users_enabled? # Check if there is overlap between the user's groups and the external groups - # setting and set user as external or internal. + # setting then set user as external or internal. if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? - # Avoid an unnecessary change of values and the subsequent save - @user.external = false if @user.external + @user.external = false else - @user.external = true unless @user.external + @user.external = true end end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index ed7b507dbab..c2a51d9249c 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -23,11 +23,15 @@ describe Gitlab::Saml::User, lib: true do allow(Gitlab::LDAP::Config).to receive_messages(messages) end - def stub_saml_config(messages) - allow(Gitlab::Saml::Config).to receive_messages(messages) + def stub_basic_saml_config + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) end - before { stub_saml_config({ options: { name: 'saml', args: {} } }) } + def stub_saml_group_config(groups) + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + end + + before { stub_basic_saml_config } describe 'account exists on server' do before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } @@ -45,15 +49,15 @@ describe Gitlab::Saml::User, lib: true do context 'external groups' do context 'are defined' do - before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) saml_user.save expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end end - before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Interns), args: {} } }) } + before { stub_saml_group_config(%w(Interns)) } context 'are defined but the user does not belong there' do it 'does not mark the user as external' do saml_user.save @@ -105,17 +109,17 @@ describe Gitlab::Saml::User, lib: true do context 'external groups' do context 'are defined' do - before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Freelancers), args: {} } }) } it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) saml_user.save expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end end - before { stub_saml_config({ options: { name: 'saml', groups_attribute: 'groups', external_groups: %w(Interns), args: {} } }) } context 'are defined but the user does not belong there' do it 'does not mark the user as external' do + stub_saml_group_config(%w(Interns)) saml_user.save expect(gl_user).to be_valid expect(gl_user.external).to be_falsey @@ -131,12 +135,6 @@ describe Gitlab::Saml::User, lib: true do context 'with auto_link_ldap_user enabled' do before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) } - context 'and no LDAP provider defined' do - before { stub_ldap_config(providers: []) } - - include_examples 'to verify compliance with allow_single_sign_on' - end - context 'and at least one LDAP provider is defined' do before { stub_ldap_config(providers: %w(ldapmain)) } @@ -144,19 +142,18 @@ describe Gitlab::Saml::User, lib: true do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } + allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) end context 'and no account for the LDAP user' do - it 'creates a user with dual LDAP and SAML identities' do saml_user.save expect(gl_user).to be_valid expect(gl_user.username).to eql uid - expect(gl_user.email).to eql 'johndoe@example.com' + expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to eql 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, @@ -166,13 +163,13 @@ describe Gitlab::Saml::User, lib: true do end context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } - it "adds the omniauth identity to the LDAP account" do + let!(:existing_user) { create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + it 'adds the omniauth identity to the LDAP account' do saml_user.save expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' - expect(gl_user.email).to eql 'john@example.com' + expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to eql 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, @@ -181,12 +178,6 @@ describe Gitlab::Saml::User, lib: true do end end end - - context 'and no corresponding LDAP person' do - before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) } - - include_examples 'to verify compliance with allow_single_sign_on' - end end end -- cgit v1.2.1 From b471c76bcb3348506c2ec96e212433d473db16c1 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 6 Apr 2016 21:46:12 -0400 Subject: Reset MR opts --- app/assets/javascripts/merge_request_widget.js.coffee | 8 +++++++- app/views/projects/merge_requests/widget/_show.html.haml | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 7102a0673e9..84a8887fbce 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -15,6 +15,8 @@ class @MergeRequestWidget @pollCIStatus() notifyPermissions() + setOpts: (@opts) -> + mergeInProgress: (deleteSourceBranch = false)-> $.ajax type: 'GET' @@ -48,7 +50,7 @@ class @MergeRequestWidget @getCIStatus(true) @readyForCICheck = false - ), 5000 + ), 10000 getCIStatus: (showNotification) -> _this = @ @@ -61,6 +63,10 @@ class @MergeRequestWidget @firstCICheck = false @opts.ci_status = data.status + if @opts.ci_status is '' + @opts.ci_status = data.status + return + if data.status isnt @opts.ci_status @showCIStatus data.status if data.coverage diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 2be06aebe6c..92d95358937 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -22,4 +22,6 @@ if(typeof merge_request_widget === 'undefined') { merge_request_widget = new MergeRequestWidget(opts); + } else { + merge_request_widget.setOpts(opts); } -- cgit v1.2.1 From 924e4b370013ae3a8448a2a68e6a4d353b23b699 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 6 Apr 2016 21:10:24 -0700 Subject: Return status code 303 after a branch DELETE operation to avoid project deletion Closes #14994 --- CHANGELOG | 1 + app/controllers/projects/application_controller.rb | 4 +++- app/controllers/projects/branches_controller.rb | 2 +- spec/controllers/projects/branches_controller_spec.rb | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 80fc2302b32..e9243463081 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). + - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - Preserve time notes/comments have been updated at when moving issue diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 657ee94cfd7..74150ad606b 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -68,7 +68,9 @@ class Projects::ApplicationController < ApplicationController end def require_non_empty_project - redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo? + # Be sure to return status code 303 to avoid a double DELETE: + # http://api.rubyonrails.org/classes/ActionController/Redirecting.html + redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo? end def require_branch_head diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index c0a53734921..d09e7375b67 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController respond_to do |format| format.html do redirect_to namespace_project_branches_path(@project.namespace, - @project) + @project), status: 303 end format.js { render status: status[:return_code] } end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 98ae424ed7c..8ad73472117 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -93,6 +93,20 @@ describe Projects::BranchesController do end end + describe "POST destroy with HTML format" do + render_views + + it 'returns 303' do + post :destroy, + format: :html, + id: 'foo/bar/baz', + namespace_id: project.namespace.to_param, + project_id: project.to_param + + expect(response.status).to eq(303) + end + end + describe "POST destroy" do render_views -- cgit v1.2.1 From c11c574d3ef4384b38f310c8d8568a116e0d0dfd Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 00:00:58 -0500 Subject: Convert time param to Time --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e6ceb213532..16e5b8ac223 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -184,7 +184,7 @@ module ApplicationHelper element = content_tag :time, time.to_s, class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", datetime: time.to_time.getutc.iso8601, - title: time.in_time_zone.to_s(:medium), + title: time.to_time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } unless skip_js -- cgit v1.2.1 From 50ef9fcafb671aa1598cb0cf83b30884b35809d4 Mon Sep 17 00:00:00 2001 From: connorshea Date: Wed, 6 Apr 2016 23:54:28 -0600 Subject: Update coveralls from 0.8.9 to 0.8.13 and simplecov from 0.10.0 to 0.11.2 This removes a few dependencies! It was also rude to be using coveralls 0.8.9, considering 0.8.12 introduced support for GitLab CI :) Also paves the way for updating mime-types to 3.0. Coveralls Changelog: https://github.com/lemurheavy/coveralls-ruby/releases Simplecov Changelog: https://github.com/colszowka/simplecov/blob/master/CHANGELOG.md --- Gemfile | 2 +- Gemfile.lock | 20 +++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 6327227282a..0279b4ac47e 100644 --- a/Gemfile +++ b/Gemfile @@ -290,7 +290,7 @@ group :development, :test do gem 'rubocop', '~> 0.38.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'coveralls', '~> 0.8.2', require: false - gem 'simplecov', '~> 0.10.0', require: false + gem 'simplecov', '~> 0.11.0', require: false gem 'flog', require: false gem 'flay', require: false gem 'bundler-audit', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 0981c3195a0..1ba8d748db1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,10 +136,9 @@ GEM colorize (0.7.7) concurrent-ruby (1.0.0) connection_pool (2.2.0) - coveralls (0.8.9) + coveralls (0.8.13) json (~> 1.8) - rest-client (>= 1.6.8, < 2) - simplecov (~> 0.10.0) + simplecov (~> 0.11.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) tins (~> 1.6.0) @@ -176,8 +175,6 @@ GEM diff-lcs (1.2.5) diffy (3.0.7) docile (1.1.5) - domain_name (0.5.25) - unf (>= 0.0.5, < 1.0.0) doorkeeper (2.2.2) railties (>= 3.2) dropzonejs-rails (0.7.2) @@ -421,8 +418,6 @@ GEM nokogiri (~> 1.6.0) ruby_parser (~> 3.5) htmlentities (4.3.4) - http-cookie (1.0.2) - domain_name (~> 0.5) http_parser.rb (0.5.3) httparty (0.13.7) json (~> 1.8) @@ -480,7 +475,6 @@ GEM nested_form (0.3.2) net-ldap (0.12.1) net-ssh (3.0.1) - netrc (0.11.0) newrelic_rpm (3.14.1.311) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) @@ -657,10 +651,6 @@ GEM listen (~> 3.0) responders (2.1.1) railties (>= 4.2.0, < 5.1) - rest-client (1.8.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) rinku (1.7.3) rotp (2.1.1) rouge (1.10.1) @@ -754,7 +744,7 @@ GEM rufus-scheduler (>= 2.0.24) sidekiq (>= 4.0.0) simple_oauth (0.1.9) - simplecov (0.10.0) + simplecov (0.11.2) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) @@ -845,7 +835,7 @@ GEM underscore-rails (1.8.3) unf (0.1.4) unf_ext - unf_ext (0.0.7.1) + unf_ext (0.0.7.2) unicode-display_width (1.0.2) unicorn (4.9.0) kgio (~> 2.6) @@ -1032,7 +1022,7 @@ DEPENDENCIES shoulda-matchers (~> 2.8.0) sidekiq (~> 4.0) sidekiq-cron (~> 0.4.0) - simplecov (~> 0.10.0) + simplecov (~> 0.11.0) sinatra (~> 1.4.4) six (~> 0.2.0) slack-notifier (~> 1.2.0) -- cgit v1.2.1 From 27b9f64efbbce9d74eeb9c4ae340506242c474cb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 7 Apr 2016 00:23:02 -0700 Subject: Expire caches after project creation to ensure a consistent state Closes #14961 --- app/models/repository.rb | 2 ++ spec/models/repository_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index a8e826c9cbf..8dead3a5884 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -331,6 +331,8 @@ class Repository # Runs code after a repository has been created. def after_create expire_exists_cache + expire_root_ref_cache + expire_emptiness_caches end # Runs code just before a repository is deleted. diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f517f325c03..4e49c413f23 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -670,6 +670,19 @@ describe Repository, models: true do repository.after_create end + + it 'flushes the root ref cache' do + expect(repository).to receive(:expire_root_ref_cache) + + repository.after_create + end + + it 'flushes the emptiness caches' do + expect(repository).to receive(:expire_emptiness_caches) + + repository.after_create + end + end describe "#main_language" do -- cgit v1.2.1 From 00da609cfd8bf1105fe433dfc92ab263d6205eaf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Apr 2016 11:19:29 +0200 Subject: Fix 2FA authentication spoofing vulnerability This commit attempts to change default user search scope if otp_user_id session variable has been set. If it is present, it means that user has 2FA enabled, and has already been verified with login and password. In this case we should look for user with otp_user_id first, before picking it up by login. --- app/controllers/sessions_controller.rb | 15 +++--- spec/controllers/sessions_controller_spec.rb | 77 +++++++++++++++------------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 65677a3dd3c..c29f4609e93 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -5,7 +5,8 @@ class SessionsController < Devise::SessionsController skip_before_action :check_2fa_requirement, only: [:destroy] prepend_before_action :check_initial_setup, only: [:new] - prepend_before_action :authenticate_with_two_factor, only: [:create] + prepend_before_action :authenticate_with_two_factor, + if: :two_factor_enabled?, only: [:create] prepend_before_action :store_redirect_path, only: [:new] before_action :auto_sign_in_with_provider, only: [:new] @@ -56,10 +57,10 @@ class SessionsController < Devise::SessionsController end def find_user - if user_params[:login] - User.by_login(user_params[:login]) - elsif user_params[:otp_attempt] && session[:otp_user_id] + if session[:otp_user_id] User.find(session[:otp_user_id]) + elsif user_params[:login] + User.by_login(user_params[:login]) end end @@ -83,11 +84,13 @@ class SessionsController < Devise::SessionsController end end + def two_factor_enabled? + find_user.try(:two_factor_enabled?) + end + def authenticate_with_two_factor user = self.resource = find_user - return unless user && user.two_factor_enabled? - if user_params[:otp_attempt].present? && session[:otp_user_id] if valid_otp_attempt?(user) # Remove any lingering user data from login diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index e7dbc3bdad4..664b23d3214 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -6,7 +6,7 @@ describe SessionsController do @request.env['devise.mapping'] = Devise.mappings[:user] end - context 'standard authentications' do + context 'when using standard authentications' do context 'invalid password' do it 'does not authenticate user' do post(:create, user: { login: 'invalid', password: 'invalid' }) @@ -16,7 +16,7 @@ describe SessionsController do end end - context 'valid password' do + context 'when using valid password' do let(:user) { create(:user) } it 'authenticates user correctly' do @@ -28,7 +28,7 @@ describe SessionsController do end end - context 'two-factor authentication' do + context 'when using two-factor authentication' do let(:user) { create(:user, :two_factor) } def authenticate_2fa(user_params) @@ -38,52 +38,59 @@ describe SessionsController do ## # See #14900 issue # - context 'authenticating with login and OTP belonging to another user' do - let(:another_user) { create(:user, :two_factor) } + context 'when authenticating with login and OTP of another user' do + context 'when another user has 2FA enabled' do + let(:another_user) { create(:user, :two_factor) } + context 'when OTP is valid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: another_user.current_otp) - context 'OTP valid for another user' do - it 'does not authenticate' do - authenticate_2fa(login: another_user.username, - otp_attempt: another_user.current_otp) - - expect(subject.current_user).to_not eq another_user + expect(subject.current_user).to_not eq another_user + end end - end - context 'OTP invalid for another user' do - before do - authenticate_2fa(login: another_user.username, - otp_attempt: 'invalid') - end + context 'when OTP is invalid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') - it 'does not authenticate' do - expect(subject.current_user).to_not eq another_user + expect(subject.current_user).to_not eq another_user + end end - it 'does not leak information about 2FA enabled' do - expect(response).to_not set_flash.now[:alert].to /Invalid two-factor code/ - end - end + context 'when authenticating with OTP' do + context 'when OTP is valid' do + it 'authenticates correctly' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(subject.current_user).to eq user + end + end - context 'authenticating with OTP' do - context 'valid OTP' do - it 'authenticates correctly' do - authenticate_2fa(otp_attempt: user.current_otp) + context ' when OTP is invalid' do + before { authenticate_2fa(otp_attempt: 'invalid') } - expect(subject.current_user).to eq user + it 'does not authenticate' do + expect(subject.current_user).to_not eq user + end + + it 'warns about invalid OTP code' do + expect(response).to set_flash + .now[:alert].to /Invalid two-factor code/ + end end end - context 'invalid OTP' do - before { authenticate_2fa(otp_attempt: 'invalid') } + context 'when another user does not have 2FA enabled' do + let(:another_user) { create(:user) } - it 'does not authenticate' do - expect(subject.current_user).to_not eq user - end + it 'does not leak that 2FA is disabled for another user' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') - it 'warns about invalid OTP code' do - expect(response).to set_flash.now[:alert].to /Invalid two-factor code/ + expect(response).to_not set_flash.now[:alert].to /Invalid login or password/ end end end -- cgit v1.2.1 From af3d7d3134033a50dbe41d88654bfea9847524a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 11:00:36 +0100 Subject: Fixed issue with any label & no label filters missing Closes #15008 --- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index fd5e58c1f1f..f722e61eeac 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,7 +1,7 @@ - if params[:label_name].present? = hidden_field_tag(:label_name, params[:label_name]) .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text = h(params[:label_name].presence || "Label") = icon('chevron-down') -- cgit v1.2.1 From 122b4148f764952fc5f365fee77e9e3241ca1093 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 11:24:12 +0100 Subject: Added label filter tests --- spec/features/issues/filter_issues_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 90822a8c123..91de06e31f9 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -76,6 +76,28 @@ describe 'Filter issues', feature: true do end end + describe 'Filter issues for label from issues#index', js: true do + before do + visit namespace_project_issues_path(project.namespace, project) + find('.js-label-select').click + end + + it 'should filter by any label' do + find('.dropdown-menu-labels a', text: 'Any Label').click + expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Label') + end + + it 'should filter by no label' do + find('.dropdown-menu-labels a', text: 'No Label').click + expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') + end + + it 'should filter by no label' do + find('.dropdown-menu-labels a', text: label.title).click + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + end + end + describe 'Filter issues for assignee and label from issues#index' do before do -- cgit v1.2.1 From 92649ca97a02021695eb4dc36fa59c1a8454bd11 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 07:09:31 -0400 Subject: Hide "assign to me" link if not allowed --- app/views/shared/issuable/_sidebar.html.haml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 47e544acf52..43883a18fa9 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -20,6 +20,7 @@ Next = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| + - can_edit_assignee = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.assignee .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} - if issuable.assignee @@ -29,9 +30,9 @@ .title.hide-collapsed Assignee = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_assignee = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.bold.hide-collapsed + .value.bold.hide-collapsed{data: {can_edit_assign: can_edit_assignee}} - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 32) do - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) @@ -41,9 +42,10 @@ = issuable.assignee.to_reference - else %span.assign-yourself - No assignee - - %a.js-assign-yourself{ href: '#' } - assign yourself + No assignee + - if can_edit_assignee + %a.js-assign-yourself{ href: '#' } + \- assign yourself .selectbox.hide-collapsed = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' -- cgit v1.2.1 From 33a8dfd04fbd1c0858ead20c020ede07e7b0962a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Apr 2016 11:45:04 +0200 Subject: Make sessions controller specs more explicit --- spec/controllers/sessions_controller_spec.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 664b23d3214..83cc8ec6d26 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -69,7 +69,7 @@ describe SessionsController do end end - context ' when OTP is invalid' do + context 'when OTP is invalid' do before { authenticate_2fa(otp_attempt: 'invalid') } it 'does not authenticate' do @@ -77,8 +77,8 @@ describe SessionsController do end it 'warns about invalid OTP code' do - expect(response).to set_flash - .now[:alert].to /Invalid two-factor code/ + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ end end end @@ -90,7 +90,8 @@ describe SessionsController do authenticate_2fa(login: another_user.username, otp_attempt: 'invalid') - expect(response).to_not set_flash.now[:alert].to /Invalid login or password/ + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ end end end -- cgit v1.2.1 From 8172d2c1f230329d5ad33282883ad9f14353e950 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 08:17:05 -0400 Subject: Add optional colon. --- config/initializers/1_settings.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2b989015279..8488482e4ab 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -176,7 +176,8 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled']. Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? +puts Settings.gitlab['issue_closing_pattern'] Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 -- cgit v1.2.1 From d76a769d24b650f8dbfd6866b9d13c0acf4a9e4a Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 08:32:37 -0400 Subject: Remove duplication. Remove JS data attributes --- app/views/shared/issuable/_sidebar.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 43883a18fa9..94affa4b59a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,5 +1,6 @@ %aside.right-sidebar{ class: sidebar_gutter_collapsed_class } .issuable-sidebar + - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header %span.issuable-count.hide-collapsed.pull-left = issuable.iid @@ -20,7 +21,6 @@ Next = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| - - can_edit_assignee = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.assignee .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} - if issuable.assignee @@ -30,9 +30,9 @@ .title.hide-collapsed Assignee = icon('spinner spin', class: 'block-loading') - - if can_edit_assignee + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.bold.hide-collapsed{data: {can_edit_assign: can_edit_assignee}} + .value.bold.hide-collapsed - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 32) do - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) @@ -43,7 +43,7 @@ - else %span.assign-yourself No assignee - - if can_edit_assignee + - if can_edit_issuable %a.js-assign-yourself{ href: '#' } \- assign yourself @@ -62,7 +62,7 @@ .title.hide-collapsed Milestone = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.bold.hide-collapsed - if issuable.milestone @@ -84,7 +84,7 @@ .title.hide-collapsed Labels = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) } - if issuable.labels.any? -- cgit v1.2.1 From 0bbeebc8f9d97c72f2a8e93c8da4d0f0d9078af7 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 08:47:29 -0400 Subject: Remove dumb debug statement and add many tests. --- config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 1 - spec/lib/gitlab/closing_issue_extractor_spec.rb | 115 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index ddee467b14b..35c7c425a5a 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -80,7 +80,7 @@ production: &base # This happens when the commit is pushed or merged into the default branch of a project. # When not specified the default issue_closing_pattern as specified below will be used. # Tip: you can test your closing pattern at http://rubular.com. - # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' + # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' ## Default project features settings default_projects_features: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8488482e4ab..72c4d8d61ce 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -177,7 +177,6 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled']. Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? -puts Settings.gitlab['issue_closing_pattern'] Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index a1f51429a79..e9b8ce6b5bb 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -22,11 +22,21 @@ describe Gitlab::ClosingIssueExtractor, lib: true do expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (Closes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Awesome commit (closes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (closes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Closed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) @@ -37,106 +47,211 @@ describe Gitlab::ClosingIssueExtractor, lib: true do expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "closed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Closing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "closing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Close: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "close: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Awesome commit (Fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (Fixes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Awesome commit (fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (Fixes: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Fixed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "fixed: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Fixing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "fixing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Fix: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "fix: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Awesome commit (Resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (Resolves: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Awesome commit (resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Awesome commit (resolves: #{reference})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Resolved: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "resolved: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Resolving: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "resolving: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "Resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Resolve: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + it do message = "resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "resolve: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + context 'with an external issue tracker reference' do it 'extracts the referenced issue' do jira_project = create(:jira_project, name: 'JIRA_EXT1') -- cgit v1.2.1 From 2caaabf10aae743655f0201f72b225fa2738b8fe Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 09:14:20 -0400 Subject: CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 1d1e541e65f..c6febbd93d3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.7.0 (unreleased) - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Expose user location (Robert Schilling) + - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 v 8.6.5 (unreleased) - Only update repository language if it is not set to improve performance -- cgit v1.2.1 From cf669551f69edd66913d22c96cf1de1302e7990e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 7 Apr 2016 15:42:07 +0200 Subject: Put CACHE_NAMESPACE in the Gitlab::Redis module --- config/application.rb | 4 +--- lib/gitlab/redis.rb | 2 ++ lib/tasks/cache.rake | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/application.rb b/config/application.rb index 9633084d603..2e2ed48db07 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,8 +7,6 @@ Bundler.require(:default, Rails.env) require_relative '../lib/gitlab/redis' module Gitlab - REDIS_CACHE_NAMESPACE = 'cache:gitlab' - class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -70,7 +68,7 @@ module Gitlab end redis_config_hash = Gitlab::Redis.redis_store_options - redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE + redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever config.cache_store = :redis_store, redis_config_hash diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 8c3aea2627c..319447669dc 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -1,5 +1,7 @@ module Gitlab class Redis + CACHE_NAMESPACE = 'cache:gitlab' + attr_reader :url # To be thread-safe we must be careful when writing the class instance diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 6c2e2e91494..2214f855200 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -9,7 +9,7 @@ namespace :cache do loop do cursor, keys = redis.scan( cursor, - match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", + match: "#{Gitlab::Redis::CACHE_NAMESPACE}*", count: CLEAR_BATCH_SIZE ) -- cgit v1.2.1 From 0bef4b97647893df8cf0a781f768b1775ae2bb58 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 5 Apr 2016 11:51:19 -0300 Subject: Implement review suggestions --- CHANGELOG | 1 + app/controllers/groups/milestones_controller.rb | 8 +++----- app/services/milestones/create_service.rb | 2 +- spec/controllers/groups/milestones_controller_spec.rb | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e1b6b32cff3..a63a865a857 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.7.0 (unreleased) - Handle nil descriptions in Slack issue messages (Stan Hu) - Add default scope to projects to exclude projects pending deletion - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) + - Better errors handling when creating milestones inside groups - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index fcf19e8066a..9d5a28e8d4d 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -21,7 +21,7 @@ class Groups::MilestonesController < Groups::ApplicationController project_ids = params[:milestone][:project_ids].reject(&:blank?) title = milestone_params[:title] - if create_milestones(project_ids, title) + if create_milestones(project_ids) redirect_to milestone_path(title) else render_new_with_error(project_ids.empty?) @@ -41,7 +41,7 @@ class Groups::MilestonesController < Groups::ApplicationController private - def create_milestones(project_ids, title) + def create_milestones(project_ids) return false unless project_ids.present? ActiveRecord::Base.transaction do @@ -51,9 +51,7 @@ class Groups::MilestonesController < Groups::ApplicationController end true - - rescue => e - + rescue ActiveRecord::ActiveRecordError => e flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}" false end diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb index b8e08c9f1eb..3b90399af64 100644 --- a/app/services/milestones/create_service.rb +++ b/app/services/milestones/create_service.rb @@ -3,7 +3,7 @@ module Milestones def execute milestone = project.milestones.new(params) - if milestone.save + if milestone.save! event_service.open_milestone(milestone, current_user) end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 9c7b5c74b8e..b0793cb1655 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -27,7 +27,7 @@ describe Groups::MilestonesController do it "redirects to new when there are no project ids" do post :create, group_id: group.id, milestone: { title: title, project_ids: [""] } expect(response).to render_template :new - expect(assigns(:milestone).errors).to_not be_nil + expect(assigns(:milestone).errors).not_to be_nil end end end -- cgit v1.2.1 From 37c62252db3af29325d0829992a881b4bbdb6ce9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 7 Apr 2016 16:40:51 +0200 Subject: Update gitlab-shell to 2.7.2 --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 24ba9a38de6..37c2961c243 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.7.0 +2.7.2 -- cgit v1.2.1 From 656d8d8865a817e9c01e11f875d6ce1a3d8adebf Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 15:56:59 +0100 Subject: Fixed colour of dropdown link hover --- app/assets/stylesheets/pages/issuable.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88c1b614c74..8b6f37f21b5 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -263,6 +263,12 @@ } } + .dropdown-content { + a:hover { + color: inherit; + } + } + .dropdown-menu-toggle { width: 100%; padding-top: 6px; -- cgit v1.2.1 From 1ca627f7829f4f45802687748da22cef315207a3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 16:10:44 +0100 Subject: Fixed tests --- features/steps/project/source/browse_files.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index f73bb425e57..37958a924bf 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -219,7 +219,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I see Browse code link' do expect(page).to have_link 'Browse Files' - expect(page).not_to have_link 'Browse File' + # expect(page).not_to have_link 'Browse File' expect(page).not_to have_link 'Browse Directory »' end -- cgit v1.2.1 From 24f18c8e52b55c96f20dbe0b3b9a9c74baff7813 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 16:19:26 +0100 Subject: Updated meqia query for admin/groups search box --- app/assets/stylesheets/framework/nav.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 94f5a12ff6a..192d53b048a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -58,12 +58,12 @@ .nav-search { display: inline-block; - width: 50%; + width: 100%; padding: 11px 0; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-min) { - width: 100%; + @media (min-width: $screen-sm-min) { + width: 50%; } } -- cgit v1.2.1 From bb5bc90ab0b3591729f8b940f178f7f54cc01c7d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 7 Apr 2016 12:35:25 -0300 Subject: Update number of Todos in the sidebar when it's marked as "Done" --- app/views/layouts/nav/_dashboard.html.haml | 2 +- features/steps/dashboard/todos.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index d1a180e4299..f1052eadf81 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -9,7 +9,7 @@ = icon('bell fw') %span Todos - %span.count= number_with_delimiter(todos_pending_count) + %span.count.todos-pending-count= number_with_delimiter(todos_pending_count) = nav_link(path: 'dashboard#activity') do = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do = icon('dashboard fw') diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 30b21b93ac7..a6e574f12a9 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -26,6 +26,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end step 'I should see todos assigned to me' do + page.within('.nav-sidebar') { expect(page).to have_content 'Todos 4' } expect(page).to have_content 'To do 4' expect(page).to have_content 'Done 0' @@ -41,6 +42,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps click_link 'Done' end + page.within('.nav-sidebar') { expect(page).to have_content 'Todos 3' } expect(page).to have_content 'To do 3' expect(page).to have_content 'Done 1' should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}" -- cgit v1.2.1 From 0dbf2df341f6d71218c927e2d5f4ac5cf67794be Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 7 Apr 2016 16:42:01 +0100 Subject: fix quick submit missing in edit merge request page --- app/views/projects/merge_requests/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 3e4ab09c6d4..1e6724fc92b 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request :javascript -- cgit v1.2.1 From f84d9e538962ef3fbb16b811a2fc1b78a3b9e2d7 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Apr 2016 08:08:51 -0700 Subject: Preserve white space --- app/assets/stylesheets/pages/diff.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 7a12aa96476..8c246507848 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -66,7 +66,9 @@ } td { - white-space: nowrap; + span { + white-space: pre; + } } } -- cgit v1.2.1 From a81fcf8bd677db5846ca67e8cb13731ee259e142 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Apr 2016 09:06:46 -0700 Subject: Indentation update --- app/assets/stylesheets/pages/diff.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8c246507848..939555bb260 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -63,9 +63,7 @@ .line_holder td { line-height: $code_line_height; font-size: $code_font_size; - } - td { span { white-space: pre; } -- cgit v1.2.1 From 088368ae4d1c41b6e55a8c5e7e5439a9ea323eb2 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 11:59:56 -0500 Subject: Add date.format.js --- vendor/assets/javascripts/date.format.js | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 vendor/assets/javascripts/date.format.js diff --git a/vendor/assets/javascripts/date.format.js b/vendor/assets/javascripts/date.format.js new file mode 100644 index 00000000000..f5dc4abcd80 --- /dev/null +++ b/vendor/assets/javascripts/date.format.js @@ -0,0 +1,125 @@ +/* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +}(); + +// Some common format strings +dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" +}; + +// Internationalization strings +dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); +}; -- cgit v1.2.1 From b19ccdeed45605a7bb79509092b297087d6c6e8c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 12:00:19 -0500 Subject: Add datetime_utility.js.coffee --- app/assets/javascripts/lib/datetime_utility.js.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 app/assets/javascripts/lib/datetime_utility.js.coffee diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/datetime_utility.js.coffee new file mode 100644 index 00000000000..ef9406fc331 --- /dev/null +++ b/app/assets/javascripts/lib/datetime_utility.js.coffee @@ -0,0 +1,15 @@ +((w) -> + + w.gl ?= {} + w.gl.utils ?= {} + + w.gl.utils.formatDate = (datetime) -> + dateFormat(datetime, 'mmm d, yyyy h:MMtt Z') + + w.gl.utils.updateFormatDate = ($timeagoEls) -> + $timeagoEls.each( -> + $el = $(@) + $el.attr('title', gl.utils.formatDate($el.attr('datetime'))) + ) + +) window -- cgit v1.2.1 From 3440c0e61f570e9f42a81fb125a021b138b5bebc Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 12:02:43 -0500 Subject: Update datetime in .timeago elements This should be done before .timeago() is called on the element --- app/assets/javascripts/application.js.coffee | 5 ++++- app/assets/javascripts/merge_request_tabs.js.coffee | 12 +++++++++--- app/assets/javascripts/notes.js.coffee | 17 +++++++++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index f01c67e9474..922a28b4ef5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -41,6 +41,7 @@ #= require shortcuts_issuable #= require shortcuts_network #= require jquery.nicescroll +#= require date.format #= require_tree . #= require fuzzaldrin-plus #= require cropper @@ -163,7 +164,9 @@ $ -> $('.trigger-submit').on 'change', -> $(@).parents('form').submit() - $('abbr.timeago, .js-timeago').timeago() + $timeago = $('abbr.timeago, .js-timeago') + gl.utils.updateFormatDate($timeago) + $timeago.timeago() # Flash if (flash = $(".flash-container")).length > 0 diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 839e6ec2c08..fdf084a8a82 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -141,7 +141,9 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#commits").innerHTML = data.html - $('.js-timeago').timeago() + $timeago = $('.js-timeago', 'div#commits') + gl.utils.updateFormatDate($timeago) + $timeago.timeago() @commitsLoaded = true @scrollToElement("#commits") @@ -152,7 +154,9 @@ class @MergeRequestTabs url: "#{source}.json" + @_location.search success: (data) => document.querySelector("div#diffs").innerHTML = data.html - $('.js-timeago').timeago() + $timeago = $('.js-timeago', 'div#diffs') + gl.utils.updateFormatDate($timeago) + $timeago.timeago() $('div#diffs .js-syntax-highlight').syntaxHighlight() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @@ -165,7 +169,9 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#builds").innerHTML = data.html - $('.js-timeago').timeago() + $timeago = $('.js-timeago', 'div#builds') + gl.utils.updateFormatDate($timeago) + $timeago.timeago() @buildsLoaded = true @scrollToElement("#builds") diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 86e3b860fcb..02e52040e3c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -163,9 +163,16 @@ class @Notes else if @isNewNote(note) @note_ids.push(note.id) - $('ul.main-notes-list') + $notesList = $('ul.main-notes-list') + + $notesList .append(note.html) .syntaxHighlight() + + # Update datetime format on the recent note + $timeago = $notesList.find("#note_#{note.id} .js-timeago") + gl.utils.updateFormatDate($timeago) + @initTaskList() @updateNotesCount(1) @@ -217,6 +224,8 @@ class @Notes # append new note to all matching discussions discussionContainer.append note_html + gl.utils.updateFormatDate($('.js-timeago', note_html)) + @updateNotesCount(1) ### @@ -345,7 +354,11 @@ class @Notes updateNote: (_xhr, note, _status) => # Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html) - $('.js-timeago', $html).timeago() + + $timeago = $('.js-timeago', $html) + gl.utils.updateFormatDate($timeago) + $timeago.timeago() + $html.syntaxHighlight() $html.find('.js-task-list-container').taskList('enable') -- cgit v1.2.1 From ee62ce65a1b80f3dae9c2cb5a52b784895fb8a9e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 7 Apr 2016 14:07:13 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c6febbd93d3..d119a36e84b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.7.0 (unreleased) - Build status notifications - API: Expose user location (Robert Schilling) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 + - Update number of Todos in the sidebar when it's marked as "Done". !3600 v 8.6.5 (unreleased) - Only update repository language if it is not set to improve performance -- cgit v1.2.1 From 451314df964455577e08f653cc17d70fedf3f49b Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 7 Apr 2016 20:51:16 +0100 Subject: add test --- spec/features/merge_requests/edit_mr_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 spec/features/merge_requests/edit_mr_spec.rb diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb new file mode 100644 index 00000000000..27f7bca2af0 --- /dev/null +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +feature 'Create New Merge Request', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } + + before do + project.team << [user, :master] + + login_as user + + visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'editing a MR', js: true do + it 'should be able submit with quick_submit' do + fill_in "merge_request_title", with: "Orphaned MR test" + + keypress = "var e = $.Event('keydown', { keyCode: 13, ctrlKey: true }); $('.merge-request-form').trigger(e);" + page.driver.execute_script(keypress) + sleep 2 + + expect(find('h2.title')).to have_text('Orphaned MR test') + end + end +end \ No newline at end of file -- cgit v1.2.1 From 875646a85e6fb7ab783d99c9c9eac406983e502c Mon Sep 17 00:00:00 2001 From: Alex Mayer Date: Thu, 7 Apr 2016 20:10:29 +0000 Subject: Fix Incorrect Quote In Docker Executor Example --- doc/ci/ssh_keys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 210f9c3e849..d790015aca1 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -57,7 +57,7 @@ before_script: # WARNING: Use this only with the Docker executor, if you use it with shell # you will overwrite your user's SSH config. - mkdir -p ~/.ssh - - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config` + - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' ``` As a final step, add the _public_ key from the one you created earlier to the -- cgit v1.2.1 From 422a9247587703ae149c1c716decc0fbb356afe2 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Apr 2016 13:27:39 -0700 Subject: Fix side-by-side code format & commit message wrap --- app/assets/stylesheets/pages/commit.scss | 2 ++ app/assets/stylesheets/pages/diff.scss | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 082911bd118..358d2f4ab9d 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -20,6 +20,8 @@ margin: 0; padding: 0; margin-top: 10px; + word-break: normal; + white-space: pre-wrap; } .commit-info-row { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 939555bb260..97f4485beb8 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -109,6 +109,10 @@ display: table-cell; } } + + .text-file.diff-wrap-lines table .line_holder td span { + white-space: pre-wrap; + } } .image { background: #ddd; -- cgit v1.2.1 From dc3de6e1327d3220951c357406bc877b76f00bdd Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 20:33:01 +0000 Subject: Revert "Merge branch 'fix-sidebar-exapnd' into 'master'" This reverts merge request !3520 --- app/assets/stylesheets/framework/sidebar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 1d49249dd80..c741c826ae0 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -200,7 +200,7 @@ top: 0; left: 0; font-size: 20px; - background: #fff; + background: transparent; height: 59px; text-align: center; line-height: 59px; -- cgit v1.2.1 From 8d5ad4e3c9973ed34742b543cc3a5466c32b9d5b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 7 Apr 2016 17:04:09 -0300 Subject: improve specs code syntax --- spec/helpers/issues_helper_spec.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index df0eb22d6f6..543593cf389 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -80,7 +80,7 @@ describe IssuesHelper do end end - describe '#url_for_new_issue' do + describe 'url_for_new_issue' do let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } let(:ext_expected) do issues_url.gsub(':project_id', ext_project.id.to_s) @@ -117,7 +117,7 @@ describe IssuesHelper do end end - describe "#merge_requests_sentence" do + describe "merge_requests_sentence" do subject { merge_requests_sentence(merge_requests)} let(:merge_requests) do [ build(:merge_request, iid: 1), build(:merge_request, iid: 2), @@ -127,7 +127,7 @@ describe IssuesHelper do it { is_expected.to eq("!1, !2, or !3") } end - describe "#note_active_class" do + describe "note_active_class" do before do @note = create :note @note1 = create :note @@ -142,22 +142,22 @@ describe IssuesHelper do end end - describe "#awards_sort" do + describe "awards_sort" do it "sorts a hash so thumbsup and thumbsdown are always on top" do data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" } expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"]) end end - describe "#milestone options" do - let!(:closed_milestone) { create :closed_milestone, title: "closed milestone", project: project } - let!(:milestone1) { create :milestone, title: "open milestone 1", project: project } - let!(:milestone2) { create :milestone, title: "open milestone 2", project: project } - - before { issue.update_attributes(milestone_id: closed_milestone.id) } - + describe "milestone_options" do it "gets closed milestone from current issue" do + closed_milestone = create(:closed_milestone, project: project) + milestone1 = create(:milestone, project: project) + milestone2 = create(:milestone, project: project) + issue.update_attributes(milestone_id: closed_milestone.id) + options = milestone_options(issue) + expect(options).to have_selector('option[selected]', text: closed_milestone.title) expect(options).to have_selector('option', text: milestone1.title) expect(options).to have_selector('option', text: milestone2.title) -- cgit v1.2.1 From f2b7cd443566f6ceefcbecbd52c8484afe264402 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 21:20:16 +0000 Subject: Revert "Merge branch 'new-navigation-prototype' into 'master'" This reverts merge request !3494 --- app/assets/javascripts/sidebar.js.coffee | 1 + app/assets/stylesheets/framework/gitlab-theme.scss | 6 +- app/assets/stylesheets/framework/header.scss | 4 +- app/assets/stylesheets/framework/sidebar.scss | 123 ++++++++------------- app/views/layouts/_collapse_button.html.haml | 4 + app/views/layouts/_page.html.haml | 18 +-- app/views/layouts/nav/_admin.html.haml | 2 +- app/views/layouts/nav/_dashboard.html.haml | 5 +- app/views/layouts/nav/_group.html.haml | 10 +- app/views/layouts/nav/_profile.html.haml | 8 ++ app/views/layouts/nav/_project.html.haml | 17 ++- features/groups.feature | 4 + features/project/project.feature | 9 ++ features/steps/group/milestones.rb | 4 +- features/steps/groups.rb | 4 + features/steps/project/active_tab.rb | 4 +- features/steps/project/fork.rb | 2 +- features/steps/project/project.rb | 12 +- features/steps/shared/project_tab.rb | 2 +- 19 files changed, 130 insertions(+), 109 deletions(-) create mode 100644 app/views/layouts/_collapse_button.html.haml diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index e1778511240..860d4f438d0 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -4,6 +4,7 @@ expanded = 'page-sidebar-expanded' toggleSidebar = -> $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('header').toggleClass("header-collapsed header-expanded") + $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left") $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) setTimeout ( -> diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index fa9038ebaca..c83cf881596 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -33,15 +33,10 @@ background: $color; } - .complex-sidebar .nav-primary { - border-right: 1px solid lighten($color, 3%); - } - .sidebar-wrapper { background: $color-darker; .sidebar-user { - border-top: 1px solid lighten($color, 3%); background: $color-darker; color: $color-light; @@ -67,6 +62,7 @@ .count { color: $color-light; + background: $color-dark; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 724980b2208..b3397d16016 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -123,11 +123,11 @@ header { } @mixin collapsed-header { - margin-left: 40px; + margin-left: $sidebar_collapsed_width; } .header-collapsed { - margin-left: 40px; + margin-left: $sidebar_collapsed_width; @media (min-width: $screen-md-min) { @include collapsed-header; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index c741c826ae0..18189e985c4 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -144,7 +144,7 @@ } a { - padding: 7px 12px; + padding: 7px 15px; font-size: $gl-font-size; line-height: 24px; color: $gray; @@ -169,12 +169,10 @@ } .count { - &:before { - content: '('; - } - &:after { - content: ')'; - } + float: right; + background: #eee; + padding: 0 8px; + @include border-radius(6px); } &.back-link i { @@ -193,27 +191,6 @@ } } -.expand-nav a { - color: $gl-icon-color; - width: 60px; - position: fixed; - top: 0; - left: 0; - font-size: 20px; - background: transparent; - height: 59px; - text-align: center; - line-height: 59px; - border-bottom: 1px solid #eee; - transition-duration: .3s; - outline: none; - z-index: 100; - - &:hover { - text-decoration: none; - } -} - .collapse-nav a { width: $sidebar_width; position: fixed; @@ -233,12 +210,55 @@ } .page-sidebar-collapsed { + padding-left: $sidebar_collapsed_width; + .sidebar-wrapper { - display: none; + width: $sidebar_collapsed_width; + + .header-logo { + width: $sidebar_collapsed_width; + + a { + padding-left: ($sidebar_collapsed_width - 36) / 2; + + .gitlab-text-container { + display: none; + } + } + } + + .nav-sidebar { + width: $sidebar_collapsed_width; + + li { + width: auto; + + a { + span { + display: none; + } + } + } + } + + .collapse-nav a { + width: $sidebar_collapsed_width; + } + + .sidebar-user { + padding-left: ($sidebar_collapsed_width - 36) / 2; + width: $sidebar_collapsed_width; + + .username { + display: none; + } + } } } .page-sidebar-expanded { + padding-left: $sidebar_collapsed_width; + @media (min-width: $screen-md-min) { padding-left: $sidebar_width; } @@ -289,48 +309,3 @@ padding-right: $sidebar_collapsed_width; } } - -.complex-sidebar { - display: inline-block; - - .nav-primary { - width: 61px; - float: left; - height: 100vh; - - .nav-sidebar { - width: 60px; - - li a { - width: 60px; - - span { - display: none; - } - } - } - } - - .nav-secondary { - $nav-secondary-width: 168px; - - float: left; - width: $nav-secondary-width; - - .nav-sidebar { - width: $nav-secondary-width; - - li { - width: $nav-secondary-width; - - a { - width: $nav-secondary-width; - - i { - display: none; - } - } - } - } - } -} diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml new file mode 100644 index 00000000000..2ed51d87ca1 --- /dev/null +++ b/app/views/layouts/_collapse_button.html.haml @@ -0,0 +1,4 @@ +- if nav_menu_collapsed? + = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close" +- else + = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close" diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 9be36273c7d..c799e9c588d 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,7 +1,5 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } = render "layouts/broadcast" - .expand-nav - = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar" .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .header-logo %a#logo @@ -10,19 +8,15 @@ .gitlab-text-container %h3 GitLab - - primary_sidebar = current_user ? 'dashboard' : 'explore' - - - if defined?(sidebar) && sidebar && sidebar != primary_sidebar - .complex-sidebar - .nav-primary - = render "layouts/nav/#{primary_sidebar}" - .nav-secondary - = render "layouts/nav/#{sidebar}" + - if defined?(sidebar) && sidebar + = render "layouts/nav/#{sidebar}" + - elsif current_user + = render 'layouts/nav/dashboard' - else - = render "layouts/nav/#{primary_sidebar}" + = render 'layouts/nav/explore' .collapse-nav - = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar" + = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user', title: "Profile" do = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 22d1d4d8597..280a1b93729 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -95,7 +95,7 @@ Spam Logs %span.count= number_with_delimiter(SpamLog.count(:all)) - = nav_link(controller: :application_settings) do + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to admin_application_settings_path, title: 'Settings' do = icon('cogs fw') %span diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index f1052eadf81..5cef652da14 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -15,12 +15,12 @@ = icon('dashboard fw') %span Activity - = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do + = nav_link(controller: :groups) do = link_to dashboard_groups_path, title: 'Groups' do = icon('group fw') %span Groups - = nav_link(path: 'dashboard#milestones') do + = nav_link(controller: :milestones) do = link_to dashboard_milestones_path, title: 'Milestones' do = icon('clock-o fw') %span @@ -48,6 +48,7 @@ %span Help + %li.separate-item = nav_link(controller: :profile) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = icon('user fw') diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 0b7de9633ec..55940741dc0 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,4 +1,12 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do = icon('group fw') @@ -34,7 +42,7 @@ %span Members - if can?(current_user, :admin_group, @group) - = nav_link do + = nav_link(html_options: { class: "separate-item" }) do = link_to edit_group_path(@group), title: 'Settings' do = icon ('cogs fw') %span diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index cc119fd64e6..3b9d31a6fc5 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,4 +1,12 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do = icon('user fw') diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d0f82b5f57f..86b46e8c75e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,4 +1,19 @@ %ul.nav.nav-sidebar + - if @project.group + = nav_link do + = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to group + - else + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = icon('bookmark fw') @@ -98,7 +113,7 @@ Snippets - if project_nav_tab? :settings - = nav_link(html_options: {class: "#{project_tab_class}"}) do + = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do = link_to edit_project_path(@project), title: 'Settings' do = icon('cogs fw') %span diff --git a/features/groups.feature b/features/groups.feature index 49e939807b5..419a5d3963d 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -7,6 +7,10 @@ Feature: Groups When I visit group "NonExistentGroup" page Then page status code should be 404 + Scenario: I should have back to group button + When I visit group "Owned" page + Then I should see back to dashboard button + @javascript Scenario: I should see group "Owned" dashboard list When I visit group "Owned" page diff --git a/features/project/project.feature b/features/project/project.feature index aa22401c88e..f1f3ed26065 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -18,6 +18,15 @@ Feature: Project Then I should see the default project avatar And I should not see the "Remove avatar" button + Scenario: I should have back to group button + And project "Shop" belongs to group + And I visit project "Shop" page + Then I should see back to group button + + Scenario: I should have back to group button + And I visit project "Shop" page + Then I should see back to dashboard button + Scenario: I should have readme on page And I visit project "Shop" page Then I should see project "Shop" README diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index b6ce5bc9cec..a167d259837 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -5,9 +5,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps include SharedUser step 'I click on group milestones' do - page.within '.nav-secondary' do - click_link("Milestones") - end + click_link 'Milestones' end step 'I should see group milestones index page has no milestones' do diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 483370f41c6..e5b7db4c5e3 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -4,6 +4,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps include SharedGroup include SharedUser + step 'I should see back to dashboard button' do + expect(page).to have_content 'Go to dashboard' + end + step 'I should see group "Owned"' do expect(page).to have_content '@owned' end diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 4584fc4d754..19d81453d8c 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -82,9 +82,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps # Sub Tabs: Issues step 'I click the "Milestones" tab' do - page.within '.nav-secondary' do - click_link('Milestones') - end + click_link('Milestones') end step 'I click the "Labels" tab' do diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index d9b16afa9b8..527f7853da9 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps end step 'I goto the Merge Requests page' do - page.within '.nav-secondary' do + page.within '.page-sidebar-expanded' do click_link "Merge Requests" end end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 8f1d4a223a9..ef185861e00 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -114,9 +114,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should not see "Snippets" button' do - page.within '.nav-secondary' do - expect(page).not_to have_link 'Snippets' - end + expect(page).not_to have_link 'Snippets' end step 'project "Shop" belongs to group' do @@ -125,6 +123,14 @@ class Spinach::Features::Project < Spinach::FeatureSteps @project.save! end + step 'I should see back to dashboard button' do + expect(page).to have_content 'Go to dashboard' + end + + step 'I should see back to group button' do + expect(page).to have_content 'Go to group' + end + step 'I click notifications drop down button' do click_link 'notifications-button' end diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index fa7d24ce611..4fc2ece79ff 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -41,7 +41,7 @@ module SharedProjectTab end step 'the active main tab should be Settings' do - page.within '.nav-secondary' do + page.within '.nav-sidebar' do expect(page).to have_content('Go to project') end end -- cgit v1.2.1 From ff1903b77a202d0ebb68d6c045547b946e895300 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 17:25:19 -0400 Subject: Remove changelog entry for new navigation sidebar. --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 151875249d8..d85741dcb85 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,7 +26,6 @@ v 8.7.0 (unreleased) - Fix creation of merge requests for orphaned branches (Stan Hu) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - - Improved UX of the navigation sidebar - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Expose user location (Robert Schilling) -- cgit v1.2.1 From 1d2429af9b0fd4ef1427c7676a50dae4e2cf0ff9 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 7 Apr 2016 16:45:33 -0500 Subject: Add missing proper nil and error handling to SAML login process. --- app/controllers/omniauth_callbacks_controller.rb | 26 +++++++++++++++--------- lib/gitlab/saml/user.rb | 22 ++++++++++++-------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d28e96c3f18..df98f56a1cd 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -60,6 +60,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end + rescue Gitlab::OAuth::SignupDisabledError + handle_signup_error end def omniauth_error @@ -92,16 +94,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end rescue Gitlab::OAuth::SignupDisabledError - label = Gitlab::OAuth::Provider.label_for(oauth['provider']) - message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." - - if current_application_settings.signup_enabled? - message << " Create a GitLab account first, and then connect it to your #{label} account." - end - - flash[:notice] = message - - redirect_to new_user_session_path + handle_signup_error end def handle_service_ticket provider, ticket @@ -122,6 +115,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end end + def handle_signup_error + label = Gitlab::OAuth::Provider.label_for(oauth['provider']) + message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." + + if current_application_settings.signup_enabled? + message << " Create a GitLab account first, and then connect it to your #{label} account." + end + + flash[:notice] = message + + redirect_to new_user_session_path + end + def oauth @oauth ||= request.env['omniauth.auth'] end diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index c1072452abe..dd77216be48 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -26,13 +26,15 @@ module Gitlab @user ||= build_new_user end - if external_users_enabled? - # Check if there is overlap between the user's groups and the external groups - # setting then set user as external or internal. - if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? - @user.external = false - else - @user.external = true + unless @user.nil? + if external_users_enabled? + # Check if there is overlap between the user's groups and the external groups + # setting then set user as external or internal. + if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? + @user.external = false + else + @user.external = true + end end end @@ -48,7 +50,11 @@ module Gitlab end def changed? - gl_user.changed? || gl_user.identities.any?(&:changed?) + if gl_user + gl_user.changed? || gl_user.identities.any?(&:changed?) + else + true + end end protected -- cgit v1.2.1 From 17fe62e3a39cd0043e5755d24948348739fb5595 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 7 Apr 2016 17:45:22 -0400 Subject: Update CHANGELOG for 8.6.5, 8.5.10, 8.4.8, and 8.3.7 [ci skip] --- CHANGELOG | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 151875249d8..7e593620738 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,9 +33,14 @@ v 8.7.0 (unreleased) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 -v 8.6.5 (unreleased) - - Only update repository language if it is not set to improve performance - - Check permissions when user attempts to import members from another project +v 8.6.5 + - Fix importing from GitHub Enterprise. !3529 + - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 + - Check permissions when user attempts to import members from another project. !3535 + - Only update repository language if it is not set to improve performance. !3556 + - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 + - Unblock user when active_directory is disabled and it can be found !3550 + - Fix a 2FA authentication spoofing vulnerability. v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) @@ -155,6 +160,9 @@ v 8.6.0 - Trigger a todo for mentions on commits page - Let project owners and admins soft delete issues and merge requests +v 8.5.10 + - Fix a 2FA authentication spoofing vulnerability. + v 8.5.9 - Don't attempt to fetch any tags from a forked repo (Stan Hu). @@ -299,6 +307,9 @@ v 8.5.0 - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos +v 8.4.8 + - Fix a 2FA authentication spoofing vulnerability. + v 8.4.7 - Don't attempt to fetch any tags from a forked repo (Stan Hu). @@ -418,6 +429,9 @@ v 8.4.0 - Add IP check against DNSBLs at account sign-up - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching +v 8.3.7 + - Fix a 2FA authentication spoofing vulnerability. + v 8.3.6 - Don't attempt to fetch any tags from a forked repo (Stan Hu). -- cgit v1.2.1 From b59d4e861c5e7e9d9113afbadeef6528be321a79 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 31 Mar 2016 10:56:22 -0500 Subject: Change reply button to text field --- app/assets/stylesheets/pages/note_form.scss | 18 ++++++++++++++++-- app/helpers/notes_helper.rb | 8 +++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a909776b437..9aec2c60e15 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -5,6 +5,11 @@ @extend .btn-primary; margin: 10px $gl-padding; } + +.comment-btn { + @extend .btn-create; +} + .diff-file .diff-content { tr.line_holder:hover > td .line_note_link { opacity: 1.0; @@ -118,8 +123,8 @@ } .discussion-reply-holder { - background: $background-color; - border-top: 1px solid $border-color; + background-color: $white-light; + padding: 10px 16px; } } @@ -183,3 +188,12 @@ float: left; } } + +.note-reply-field { + background-color: $background-color; + + &:focus { + box-shadow: none; + border-color: $border-color; + } +} diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 698f90cb27a..3363001fcdb 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -69,10 +69,8 @@ module NotesHelper line_type: line_type } - button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button', - data: data, title: 'Add a reply' do - link_text = icon('comment') - link_text << ' Reply' - end + text_field_tag 'reply-field', nil, placeholder: 'Reply...', + class: 'form-control note-reply-field js-discussion-reply-button', + data: data end end -- cgit v1.2.1 From c80c1da7802c372607f5a025ef2f3e25202734c3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 31 Mar 2016 12:13:25 -0500 Subject: Move diff colors to variables --- app/assets/stylesheets/framework/variables.scss | 12 +++++++++++- .../stylesheets/highlight/solarized_light.scss | 4 ++-- app/assets/stylesheets/highlight/white.scss | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8d3ad934a50..9f231429653 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -108,6 +108,8 @@ $red-light: #e52c5a; $red-normal: #d22852; $red-dark: darken($red-normal, 5%); +$black-transparent: rgba(0, 0, 0, 0.3); + $border-white-light: #f1f2f4; $border-white-normal: #d6dae2; $border-white-dark: #c6cacf; @@ -158,7 +160,15 @@ $gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background; */ $added: #63c363; $deleted: #f77; - +$line-added: #ebfdf0; +$line-added-dark: #a6f3a6; +$line-removed: #ffecec; +$line-removed-dark: #f8cbcb; +$line-number-old: #fdd; +$line-number-old-border: #f1c0c0; +$line-number-new: #dbffdb; +$line-number-new-border: #c1e9c1; +$match-line: #fafafa; /* * Fonts */ diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index b90c95c62d1..c482a1258f7 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -6,7 +6,7 @@ } .diff-line-num, .diff-line-num a { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; } // Code itself @@ -30,7 +30,7 @@ } .line_content.match { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; background: rgba(255, 255, 255, 0.4); } } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 8c1b0cd84ec..80918970a75 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -6,7 +6,7 @@ } .diff-line-num, .diff-line-num a { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; } // Code itself @@ -23,36 +23,36 @@ .line_holder { .diff-line-num { &.old { - background: #fdd; - border-color: #f1c0c0; + background-color: $line-number-old; + border-color: $line-number-old-border; } &.new { - background: #dbffdb; - border-color: #c1e9c1; + background-color: $line-number-new; + border-color: $line-number-new-border; } } .line_content { &.old { - background: #ffecec; + background: $line-removed; span.idiff { - background-color: #f8cbcb; + background-color: $line-removed-dark; } } &.new { - background: #eaffea; + background-color: $line-added; span.idiff { - background-color: #a6f3a6; + background-color: $line-added-dark; } } &.match { - color: rgba(0, 0, 0, 0.3); - background: #fafafa; + color: $black-transparent; + background: $match-line; } } } -- cgit v1.2.1 From de63de18d77624d49b6a9d150f2581be0ac55e00 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 31 Mar 2016 13:37:27 -0500 Subject: Update diff colors and icons --- app/assets/stylesheets/framework/variables.scss | 14 ++++++-------- app/assets/stylesheets/highlight/white.scss | 4 ++-- app/assets/stylesheets/pages/notes.scss | 15 +++++++++++++++ app/helpers/notes_helper.rb | 3 ++- app/views/projects/diffs/_file.html.haml | 2 +- app/views/projects/notes/_note.html.haml | 4 ++-- spec/features/notes_on_merge_requests_spec.rb | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 9f231429653..49f62c8850a 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -160,14 +160,12 @@ $gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background; */ $added: #63c363; $deleted: #f77; -$line-added: #ebfdf0; -$line-added-dark: #a6f3a6; -$line-removed: #ffecec; -$line-removed-dark: #f8cbcb; -$line-number-old: #fdd; -$line-number-old-border: #f1c0c0; -$line-number-new: #dbffdb; -$line-number-new-border: #c1e9c1; +$line-added: #ecfdf0; +$line-added-dark: #c7f0d2; +$line-removed: #fbe9eb; +$line-removed-dark: #fac5cd; +$line-number-old: #f9d7dc; +$line-number-new: #ddfbe6; $match-line: #fafafa; /* * Fonts diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 80918970a75..ea9bc98079a 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -24,12 +24,12 @@ .diff-line-num { &.old { background-color: $line-number-old; - border-color: $line-number-old-border; + border-color: $line-removed-dark; } &.new { background-color: $line-number-new; - border-color: $line-number-new-border; + border-color: $line-added-dark; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index aca86457c70..d9dc7820184 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -212,6 +212,21 @@ ul.notes { top: 0; font-size: 16px; } + + .js-note-delete { + &:hover { + @extend .cgray; + &.danger { @extend .cred; } + } + } + + .js-note-edit { + i { + &:hover { + color: $gl-link-color; + } + } + } } .discussion-toggle-button { diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 3363001fcdb..681df89836d 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -69,7 +69,8 @@ module NotesHelper line_type: line_type } - text_field_tag 'reply-field', nil, placeholder: 'Reply...', + text_field_tag 'reply-field', nil, + placeholder: 'Reply...', class: 'form-control note-reply-field js-discussion-reply-button', data: data end diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 698ed02ea0e..4d5461afeb7 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -29,7 +29,7 @@ .file-actions.hidden-xs - if blob_text_viewable?(blob) = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do - = icon('comments') + = icon('comment') \ - if editable_diff?(diff_file) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index a681d6dece4..5c42423541e 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -17,8 +17,8 @@ %span.note-role = access = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do - = icon('pencil-square-o') - = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do + = icon('pencil') + = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 70d0864783d..9b2351c15dc 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -210,7 +210,7 @@ describe 'Comments', feature: true do is_expected.to have_content('Another comment on line 10') is_expected.to have_css('.notes_holder') is_expected.to have_css('.notes_holder .note', count: 1) - is_expected.to have_button('Reply') + is_expected.to have_css('.note-reply-field') end end end -- cgit v1.2.1 From 87b3c73ea560d6091a17e7b759864b90911f5f1c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 31 Mar 2016 16:34:21 -0500 Subject: Update diff_comments tests --- features/project/commits/diff_comments.feature | 6 +++--- features/steps/shared/diff_note.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index 2bde4c8a99b..fdaa9e7a473 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -57,9 +57,9 @@ Feature: Project Commits Diff Comments Then I should see two separate previews @javascript - Scenario: I have a reply button in discussions + Scenario: I have can reply in discussions Given I leave a diff comment like "Typo, please fix" - Then I should see a discussion reply button + Then I should see a discussion reply text field @javascript Scenario: I can preview with text @@ -83,7 +83,7 @@ Feature: Project Commits Diff Comments Given I preview a diff comment text like "Should fix it :smile:" And I submit the diff comment Then I should not see the diff comment form - And I should see a discussion reply button + And I should see a discussion reply text field @javascript Scenario: I can add a comment on a side-by-side commit diff (left side) diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 32c3e99f450..05e5ee151ad 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -153,9 +153,9 @@ module SharedDiffNote end end - step 'I should see a discussion reply button' do + step 'I should see a discussion reply text field' do page.within(diff_file_selector) do - expect(page).to have_button('Reply') + expect(page).to have_css('.note-reply-field') end end -- cgit v1.2.1 From 3cfeb3755828871813cd12c771fa7efddf67edb5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 1 Apr 2016 13:55:50 -0500 Subject: Merge conflict fixes --- app/assets/stylesheets/pages/notes.scss | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d9dc7820184..93fcb81321a 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -194,6 +194,21 @@ ul.notes { float: right; margin-left: 10px; color: $notes-action-color; + + .js-note-delete { + &:hover { + @extend .cgray; + &.danger { @extend .cred; } + } + } + + .js-note-edit { + i { + &:hover { + color: $gl-link-color; + } + } + } } .note-action-button, @@ -212,21 +227,6 @@ ul.notes { top: 0; font-size: 16px; } - - .js-note-delete { - &:hover { - @extend .cgray; - &.danger { @extend .cred; } - } - } - - .js-note-edit { - i { - &:hover { - color: $gl-link-color; - } - } - } } .discussion-toggle-button { -- cgit v1.2.1 From dab4ac54b691f1c832b1fe444859c3183a231411 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 1 Apr 2016 14:27:39 -0500 Subject: Diff design updates --- app/assets/stylesheets/framework/buttons.scss | 29 ++++++++++++++-- app/assets/stylesheets/framework/files.scss | 5 +-- app/assets/stylesheets/framework/timeline.scss | 4 --- app/assets/stylesheets/framework/variables.scss | 18 +++++++--- app/assets/stylesheets/highlight/white.scss | 4 +-- app/assets/stylesheets/pages/diff.scss | 8 +++++ app/assets/stylesheets/pages/note_form.scss | 6 ---- app/assets/stylesheets/pages/notes.scss | 44 ++++++++++--------------- app/helpers/blob_helper.rb | 6 ++-- app/helpers/commits_helper.rb | 2 +- app/helpers/notes_helper.rb | 8 ++--- app/views/projects/diffs/_file.html.haml | 10 +++--- features/project/commits/diff_comments.feature | 7 ++-- features/steps/shared/diff_note.rb | 4 +-- spec/features/notes_on_merge_requests_spec.rb | 2 +- 15 files changed, 90 insertions(+), 67 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 657c5f033c7..e8c0172680d 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -7,6 +7,7 @@ &:focus, &:active { outline: none; + background-color: $btn-active-gray; @include box-shadow($gl-btn-active-background); } } @@ -27,7 +28,8 @@ color: $color; } - &:active { + &:active, + &.active { @include box-shadow ($gl-btn-active-background); background-color: $dark; @@ -61,7 +63,7 @@ } @mixin btn-white { - @include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); } .btn { @@ -218,3 +220,26 @@ margin-right: 5px; } } + +.btn-text-field { + width: 100%; + text-align: left; + padding: 6px 16px; + border-color: $border-color; + color: $btn-placeholder-gray; + background-color: $background-color; + + &:hover, + &:active, + &:focus { + cursor: text; + box-shadow: none; + border-color: $border-color; + color: $btn-placeholder-gray; + background-color: $background-color; + } +} + +.btn-file-option { + background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); +} diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index b15f4e7bd5e..a7958467a14 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -15,12 +15,13 @@ .file-title { position: relative; - background: $background-color; + background-color: $background-color; border-bottom: 1px solid $border-color; margin: 0; text-align: left; padding: 10px $gl-padding; word-wrap: break-word; + border-radius: 3px 3px 0px 0px; .file-actions { float: right; @@ -49,7 +50,7 @@ } } - a { + a:not(.btn) { color: $gl-dark-link-color; } diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index aa244fe548d..b91f2f6f898 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -14,10 +14,6 @@ background: $row-hover; } - &:last-child { - border-bottom: none; - } - .avatar { margin-right: 15px; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 49f62c8850a..185137a4012 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -10,10 +10,10 @@ $gutter_inner_width: 258px; /* * UI elements */ -$border-color: #efeff1; +$border-color: #e5e5e5; $focus-border-color: #3aabf0; $table-border-color: #eef0f2; -$background-color: #faf9f9; +$background-color: #fafafa; /* * Text @@ -81,7 +81,7 @@ $provider-btn-not-active-color: #4688f1; $white-light: #fff; $white-normal: #ededed; -$white-dark: #ededed; +$white-dark: #ECECEC; $gray-light: #faf9f9; $gray-normal: #f5f5f5; @@ -152,8 +152,8 @@ $gl-success: $green-normal; $gl-info: $blue-normal; $gl-warning: $orange-normal; $gl-danger: $red-normal; -$gl-btn-active-background: rgba(0, 0, 0, 0.12); -$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background; +$gl-btn-active-background: rgba(0, 0, 0, 0.16); +$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* * Commit Diff Colors @@ -167,6 +167,7 @@ $line-removed-dark: #fac5cd; $line-number-old: #f9d7dc; $line-number-new: #ddfbe6; $match-line: #fafafa; +$table-border-gray: #f0f0f0; /* * Fonts */ @@ -199,6 +200,13 @@ $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); $dropdown-toggle-icon-color: #c4c4c4; $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; +/* +* Buttons +*/ +$btn-active-gray: #ECECEC; +$btn-placeholder-gray: #C7C7C7; +$btn-white-active: #848484; + /* * Award emoji */ diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index ea9bc98079a..d68a28bbfd5 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -6,12 +6,12 @@ } .diff-line-num, .diff-line-num a { - color: $black-transparent; + color: #8f8f8f; } // Code itself pre.code, .diff-line-num { - border-color: $border-color; + border-color: $table-border-gray; } &, pre.code, .line_holder .line_content { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 97f4485beb8..5a99fffb540 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -2,6 +2,7 @@ .diff-file { border: 1px solid $border-color; margin-bottom: $gl-padding; + border-radius: 3px; .diff-header { position: relative; @@ -31,6 +32,7 @@ overflow-y: hidden; background: #fff; color: #333; + border-radius: 0 0 3px 3px; .unfold { cursor: pointer; @@ -325,6 +327,12 @@ float: right; } +.diffs { + .content-block { + border-bottom: none; + } +} + // Mobile @media (max-width: 480px) { .diff-title { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 9aec2c60e15..a6427f1912e 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -1,11 +1,6 @@ /** * Note Form */ -.reply-btn { - @extend .btn-primary; - margin: 10px $gl-padding; -} - .comment-btn { @extend .btn-create; } @@ -118,7 +113,6 @@ .discussion-body, .diff-file { .notes .note { - border-color: #ddd; padding: 10px 15px; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 93fcb81321a..7295fe51121 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -58,6 +58,7 @@ ul.notes { .note { display: block; position: relative; + border-bottom: 1px solid $table-border-gray; &.is-editting { .note-header, @@ -117,9 +118,6 @@ ul.notes { padding-bottom: 3px; } - &:last-child { - border-bottom: 1px solid $border-color; - } } } @@ -137,14 +135,14 @@ ul.notes { font-family: $regular_font; td { - border: 1px solid #ddd; + border: 1px solid $table-border-gray; border-left: none; &.notes_line { vertical-align: middle; text-align: center; padding: 10px 0; - background: #fff; + background: $background-color; color: $text-color; } &.notes_line2 { @@ -175,9 +173,6 @@ ul.notes { } } - .author_link { - font-weight: 600; - } } .note-headline-light, @@ -194,21 +189,6 @@ ul.notes { float: right; margin-left: 10px; color: $notes-action-color; - - .js-note-delete { - &:hover { - @extend .cgray; - &.danger { @extend .cred; } - } - } - - .js-note-edit { - i { - &:hover { - color: $gl-link-color; - } - } - } } .note-action-button, @@ -218,14 +198,26 @@ ul.notes { line-height: 24px; .fa { + color: $notes-action-color; position: relative; top: 1px; font-size: 17px; } - .fa-trash-o { - top: 0; - font-size: 16px; + &.js-note-delete { + i { + &:hover { + color: $gl-text-red; + } + } + } + + &.js-note-edit { + i { + &:hover { + color: $gl-link-color; + } + } } } diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 820d69c230b..9e59a295fc4 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -27,9 +27,9 @@ module BlobHelper link_opts) if !on_top_of_branch?(project, ref) - button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } + button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) - link_to "Edit", edit_path, class: 'btn' + link_to "Edit", edit_path, class: 'btn btn-file-option' elsif can?(current_user, :fork_project, project) continue_params = { to: edit_path, @@ -38,7 +38,7 @@ module BlobHelper } fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) - link_to "Edit", fork_path, class: 'btn', method: :post + link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index bde0799f3de..de508036888 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -197,7 +197,7 @@ module CommitsHelper link_to( namespace_project_blob_path(project.namespace, project, tree_join(commit_sha, diff.new_path)), - class: 'btn view-file js-view-file' + class: 'btn view-file js-view-file btn-file-option' ) do raw('View file @') + content_tag(:span, commit_sha[0..6], class: 'commit-short-id') diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 681df89836d..d86954c105e 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -69,9 +69,9 @@ module NotesHelper line_type: line_type } - text_field_tag 'reply-field', nil, - placeholder: 'Reply...', - class: 'form-control note-reply-field js-discussion-reply-button', - data: data + button_tag class: 'btn btn-text-field js-discussion-reply-button', + data: data, title: 'Add a reply' do + link_text = 'Reply...' + end end end diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 4d5461afeb7..83a8d7ae9bf 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -3,7 +3,7 @@ - if diff_file.diff.submodule? %span = icon('archive fw') - %strong + %span = submodule_link(blob, @commit.id, project.repository) - else = blob_icon blob.mode, blob.name @@ -11,13 +11,13 @@ = link_to "#diff-#{i}" do - if diff_file.renamed_file - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) - %strong.filename.old + .filename.old = old_path → - %strong.filename.new + .filename.new = new_path - else - %strong + %span = diff_file.new_path - if diff_file.deleted_file deleted @@ -28,7 +28,7 @@ .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do + = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do = icon('comment') \ diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index fdaa9e7a473..1dda2ff8a6d 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -57,10 +57,9 @@ Feature: Project Commits Diff Comments Then I should see two separate previews @javascript - Scenario: I have can reply in discussions + Scenario: I have a reply button in discussions Given I leave a diff comment like "Typo, please fix" - Then I should see a discussion reply text field - + Then I should see a discussion reply button @javascript Scenario: I can preview with text Given I open a diff comment form @@ -83,7 +82,7 @@ Feature: Project Commits Diff Comments Given I preview a diff comment text like "Should fix it :smile:" And I submit the diff comment Then I should not see the diff comment form - And I should see a discussion reply text field + And I should see a discussion reply button @javascript Scenario: I can add a comment on a side-by-side commit diff (left side) diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 05e5ee151ad..1448c3f44cc 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -153,9 +153,9 @@ module SharedDiffNote end end - step 'I should see a discussion reply text field' do + step 'I should see a discussion reply button' do page.within(diff_file_selector) do - expect(page).to have_css('.note-reply-field') + expect(page).to have_button('Reply...') end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 9b2351c15dc..5f855ccc701 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -210,7 +210,7 @@ describe 'Comments', feature: true do is_expected.to have_content('Another comment on line 10') is_expected.to have_css('.notes_holder') is_expected.to have_css('.notes_holder .note', count: 1) - is_expected.to have_css('.note-reply-field') + is_expected.to have_button('Reply...') end end end -- cgit v1.2.1 From 04e798a5154b2b2a86288f51081da8495f96bee5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Apr 2016 15:00:11 -0700 Subject: Remove comment count & icon --- app/views/projects/notes/_diff_notes_with_reply.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml index 11f9859a90f..39be072855a 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -3,9 +3,6 @@ - if !defined?(line) || line == note.diff_line %tr.notes_holder %td.notes_line{ colspan: 2 } - %span.discussion-notes-count - %i.fa.fa-comment - = notes.count %td.notes_content %ul.notes{ data: { discussion_id: note.discussion_id } } = render notes -- cgit v1.2.1 From 6ee2069768279a9f9e03ae20f10b906a807a5eb7 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Apr 2016 18:33:00 -0700 Subject: Fix lint errors --- app/assets/stylesheets/framework/files.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 6 +++--- app/helpers/notes_helper.rb | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index a7958467a14..789df42fb66 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -21,7 +21,7 @@ text-align: left; padding: 10px $gl-padding; word-wrap: break-word; - border-radius: 3px 3px 0px 0px; + border-radius: 3px 3px 0 0; .file-actions { float: right; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 185137a4012..1ebbd9b0e57 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -81,7 +81,7 @@ $provider-btn-not-active-color: #4688f1; $white-light: #fff; $white-normal: #ededed; -$white-dark: #ECECEC; +$white-dark: #ececec; $gray-light: #faf9f9; $gray-normal: #f5f5f5; @@ -203,8 +203,8 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; /* * Buttons */ -$btn-active-gray: #ECECEC; -$btn-placeholder-gray: #C7C7C7; +$btn-active-gray: #ececec; +$btn-placeholder-gray: #c7c7c7; $btn-white-active: #848484; /* diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index d86954c105e..95072b5373f 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -69,9 +69,7 @@ module NotesHelper line_type: line_type } - button_tag class: 'btn btn-text-field js-discussion-reply-button', - data: data, title: 'Add a reply' do - link_text = 'Reply...' - end + button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', + data: data, title: 'Add a reply' end end -- cgit v1.2.1 From d76878b96040c8ae4752600aceb30200fa0049a0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Apr 2016 12:36:53 -0700 Subject: Diff touch ups --- app/assets/stylesheets/highlight/white.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 5 +++++ app/assets/stylesheets/pages/note_form.scss | 9 --------- app/views/projects/diffs/_diffs.html.haml | 2 +- features/project/commits/diff_comments.feature | 1 + 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index d68a28bbfd5..28331f59754 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -6,7 +6,7 @@ } .diff-line-num, .diff-line-num a { - color: #8f8f8f; + color: $black-transparent; } // Code itself diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 5a99fffb540..d0855f66911 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -11,6 +11,7 @@ padding: 10px 16px; color: #555; z-index: 10; + border-radius: 3px 3px 0 0; .diff-title { font-family: $monospace_font; @@ -333,6 +334,10 @@ } } +.files-changed { + border-bottom: none; +} + // Mobile @media (max-width: 480px) { .diff-title { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a6427f1912e..4d4d508396d 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -182,12 +182,3 @@ float: left; } } - -.note-reply-field { - background-color: $background-color; - - &:focus { - box-shadow: none; - border-color: $border-color; - } -} diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 2e1a37aa06d..eaab99973a4 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,7 +3,7 @@ - diff_files = safe_diff_files(diffs, diff_refs) -.content-block.oneline-block +.content-block.oneline-block.files-changed .inline-parallel-buttons .btn-group = inline_diff_btn diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index 1dda2ff8a6d..2bde4c8a99b 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -60,6 +60,7 @@ Feature: Project Commits Diff Comments Scenario: I have a reply button in discussions Given I leave a diff comment like "Typo, please fix" Then I should see a discussion reply button + @javascript Scenario: I can preview with text Given I open a diff comment form -- cgit v1.2.1 From e65bccfcc3956a13ffc04f0a72226afe97b6b69d Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Apr 2016 15:32:54 -0700 Subject: Remove comment count & icon from side by side view --- app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml index bb761ed2f94..f8aa5e2fa7d 100644 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -4,9 +4,6 @@ %tr.notes_holder - if note1 %td.notes_line.old - %span.btn.disabled - %i.fa.fa-comment - = notes_left.count %td.notes_content.parallel.old %ul.notes{ data: { discussion_id: note1.discussion_id } } = render notes_left @@ -19,9 +16,6 @@ - if note2 %td.notes_line.new - %span.btn.disabled - %i.fa.fa-comment - = notes_right.count %td.notes_content.parallel.new %ul.notes{ data: { discussion_id: note2.discussion_id } } = render notes_right -- cgit v1.2.1 From 400a1ae04d28388e7dfbd7c03db857d58a7d8776 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 7 Apr 2016 23:44:59 +0100 Subject: add some changes to the test --- spec/features/merge_requests/edit_mr_spec.rb | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index 27f7bca2af0..9e007ab7635 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Create New Merge Request', feature: true do +feature 'Edit Merge Request', feature: true do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } @@ -13,15 +13,9 @@ feature 'Create New Merge Request', feature: true do visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end - context 'editing a MR', js: true do - it 'should be able submit with quick_submit' do - fill_in "merge_request_title", with: "Orphaned MR test" - - keypress = "var e = $.Event('keydown', { keyCode: 13, ctrlKey: true }); $('.merge-request-form').trigger(e);" - page.driver.execute_script(keypress) - sleep 2 - - expect(find('h2.title')).to have_text('Orphaned MR test') + context 'editing a MR' do + it 'form should have class js-quick-submit' do + expect(page).to have_selector('.js-quick-submit') end end -end \ No newline at end of file +end -- cgit v1.2.1 From fb2fde9d62a599f08c065c54de236eacb6558d60 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Apr 2016 08:41:10 +0200 Subject: API: Expose subscribed? on issues --- CHANGELOG | 1 + doc/api/issues.md | 17 +++++++++++------ doc/api/merge_requests.md | 19 +++++++++++++------ lib/api/entities.rb | 8 ++++++++ lib/api/issues.rb | 10 +++++----- lib/api/merge_requests.rb | 14 +++++++------- lib/api/milestones.rb | 2 +- 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1d1e541e65f..7d9cba320b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.7.0 (unreleased) - Allow back dating on issues when created through the API - Fix Error 500 after renaming a project path (Stan Hu) - Fix avatar stretching by providing a cropping feature + - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 - Add endpoints to archive or unarchive a project !3372 - Add links to CI setup documentation from project settings and builds pages diff --git a/doc/api/issues.md b/doc/api/issues.md index cc6355d34ef..1c635a6cdcf 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -76,8 +76,9 @@ Example response: "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.", "created_at" : "2016-01-04T15:31:51.081Z", "iid" : 6, - "labels" : [] - }, + "labels" : [], + "subscribed" : false + } ] ``` @@ -152,7 +153,8 @@ Example response: "id" : 41, "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.", "updated_at" : "2016-01-04T15:31:46.176Z", - "created_at" : "2016-01-04T15:31:46.176Z" + "created_at" : "2016-01-04T15:31:46.176Z", + "subscribed" : false } ] ``` @@ -213,7 +215,8 @@ Example response: "id" : 41, "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.", "updated_at" : "2016-01-04T15:31:46.176Z", - "created_at" : "2016-01-04T15:31:46.176Z" + "created_at" : "2016-01-04T15:31:46.176Z", + "subscribed": false } ``` @@ -267,7 +270,8 @@ Example response: }, "description" : null, "updated_at" : "2016-01-07T12:44:33.959Z", - "milestone" : null + "milestone" : null, + "subscribed" : true } ``` @@ -323,7 +327,8 @@ Example response: ], "id" : 85, "assignee" : null, - "milestone" : null + "milestone" : null, + "subscribed" : true } ``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index b20a6300b7a..20db73ea6c0 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -66,7 +66,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : false } ] ``` @@ -128,7 +129,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : true } ``` @@ -227,6 +229,7 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", + "subscribed" : true, "changes": [ { "old_path": "VERSION", @@ -304,7 +307,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : true } ``` @@ -373,7 +377,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : true } ``` @@ -466,7 +471,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : true } ``` @@ -530,7 +536,8 @@ Parameters: "due_date": null }, "merge_when_build_succeeds": true, - "merge_status": "can_be_merged" + "merge_status": "can_be_merged", + "subscribed" : true } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4c49442bf8b..d76b46b8836 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -170,6 +170,10 @@ module API expose :label_names, as: :labels expose :milestone, using: Entities::Milestone expose :assignee, :author, using: Entities::UserBasic + + expose :subscribed do |issue, options| + issue.subscribed?(options[:current_user]) + end end class MergeRequest < ProjectEntity @@ -183,6 +187,10 @@ module API expose :milestone, using: Entities::Milestone expose :merge_when_build_succeeds expose :merge_status + + expose :subscribed do |merge_request, options| + merge_request.subscribed?(options[:current_user]) + end end class MergeRequestChanges < MergeRequest diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 1fee1dee1a6..c4ea05ee6cf 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -55,7 +55,7 @@ module API issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues.reorder(issuable_order_by => issuable_sort) - present paginate(issues), with: Entities::Issue + present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -92,7 +92,7 @@ module API end issues.reorder(issuable_order_by => issuable_sort) - present paginate(issues), with: Entities::Issue + present paginate(issues), with: Entities::Issue, current_user: current_user end # Get a single project issue @@ -105,7 +105,7 @@ module API get ":id/issues/:issue_id" do @issue = user_project.issues.find(params[:issue_id]) not_found! unless can?(current_user, :read_issue, @issue) - present @issue, with: Entities::Issue + present @issue, with: Entities::Issue, current_user: current_user end # Create a new project issue @@ -149,7 +149,7 @@ module API issue.add_labels_by_names(params[:labels].split(',')) end - present issue, with: Entities::Issue + present issue, with: Entities::Issue, current_user: current_user else render_validation_error!(issue) end @@ -189,7 +189,7 @@ module API issue.add_labels_by_names(params[:labels].split(',')) end - present issue, with: Entities::Issue + present issue, with: Entities::Issue, current_user: current_user else render_validation_error!(issue) end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 93052fba06b..4e7de8867b4 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -56,7 +56,7 @@ module API end merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort) - present paginate(merge_requests), with: Entities::MergeRequest + present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user end # Create MR @@ -94,7 +94,7 @@ module API merge_request.add_labels_by_names(params[:labels].split(",")) end - present merge_request, with: Entities::MergeRequest + present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors end @@ -130,7 +130,7 @@ module API authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequest + present merge_request, with: Entities::MergeRequest, current_user: current_user end # Show MR commits @@ -162,7 +162,7 @@ module API merge_request = user_project.merge_requests. find(params[:merge_request_id]) authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequestChanges + present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end # Update MR @@ -204,7 +204,7 @@ module API merge_request.add_labels_by_names(params[:labels].split(",")) end - present merge_request, with: Entities::MergeRequest + present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors end @@ -246,7 +246,7 @@ module API execute(merge_request) end - present merge_request, with: Entities::MergeRequest + present merge_request, with: Entities::MergeRequest, current_user: current_user end # Cancel Merge if Merge When build succeeds is enabled @@ -325,7 +325,7 @@ module API get "#{path}/closes_issues" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) - present paginate(issues), with: Entities::Issue + present paginate(issues), with: Entities::Issue, current_user: current_user end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index afb6ffa3609..0f3f505fa05 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -103,7 +103,7 @@ module API authorize! :read_milestone, user_project @milestone = user_project.milestones.find(params[:milestone_id]) - present paginate(@milestone.issues), with: Entities::Issue + present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user end end -- cgit v1.2.1 From aa7cddc4fcd490ccd192d7d04fb67b375705b586 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 8 Apr 2016 16:25:17 +0200 Subject: Use more accurate timestamps for InfluxDB. This changes the timestamp of metrics to be more accurate/unique by using Time#to_f combined with a small random jitter value. This combination hopefully reduces the amount of collisions, though there's no way to fully prevent any from occurring. Fixes gitlab-com/operations#175 --- lib/gitlab/metrics/metric.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index 7ea9555cc8c..1cd1ca30f70 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -2,6 +2,8 @@ module Gitlab module Metrics # Class for storing details of a single metric (label, value, etc). class Metric + JITTER_RANGE = 0.000001..0.001 + attr_reader :series, :values, :tags, :created_at # series - The name of the series (as a String) to store the metric in. @@ -16,11 +18,29 @@ module Gitlab # Returns a Hash in a format that can be directly written to InfluxDB. def to_hash + # InfluxDB overwrites an existing point if a new point has the same + # series, tag set, and timestamp. In a highly concurrent environment + # this means that using the number of seconds since the Unix epoch is + # inevitably going to collide with another timestamp. For example, two + # Rails requests processed by different processes may end up generating + # metrics using the _exact_ same timestamp (in seconds). + # + # Due to the way InfluxDB is set up there's no solution to this problem, + # all we can do is lower the amount of collisions. We do this by using + # Time#to_f which returns the seconds as a Float providing greater + # accuracy. We then add a small random value that is large enough to + # distinguish most timestamps but small enough to not alter the amount + # of seconds. + # + # See https://gitlab.com/gitlab-com/operations/issues/175 for more + # information. + time = @created_at.to_f + rand(JITTER_RANGE) + { series: @series, tags: @tags, values: @values, - timestamp: @created_at.to_i * 1_000_000_000 + timestamp: (time * 1_000_000_000).to_i } end end -- cgit v1.2.1 From 6dacf0fd6d2627e05e50e0116566946e3b81bc5f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 8 Apr 2016 16:02:13 +0100 Subject: Fixed issue with member access not being visible on notes Fixes #15049 --- app/views/projects/notes/_note.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index a681d6dece4..b4fa917718b 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -10,12 +10,12 @@ = "#{note.author.to_reference} commented" %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') - - if note_editable?(note) - .note-actions - - access = note.project.team.human_max_access(note.author.id) - - if access - %span.note-role - = access + .note-actions + - access = note.project.team.human_max_access(note.author.id) + - if access + %span.note-role + = access + - if note_editable?(note) = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = icon('pencil-square-o') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do -- cgit v1.2.1 From c56f702ec3806606459e4edc3de097f1bb6baf4e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 8 Apr 2016 17:54:52 +0200 Subject: Instrument Rails cache code This allows us to track how much time of a transaction is spent in dealing with cached data. --- config/initializers/metrics.rb | 1 + lib/gitlab/metrics/subscribers/rails_cache.rb | 39 ++++++++++++ .../gitlab/metrics/subscribers/rails_cache_spec.rb | 71 ++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 lib/gitlab/metrics/subscribers/rails_cache.rb create mode 100644 spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 3e1deb8d306..a9fc38fb04a 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -7,6 +7,7 @@ if Gitlab::Metrics.enabled? # ActiveSupport. require 'gitlab/metrics/subscribers/action_view' require 'gitlab/metrics/subscribers/active_record' + require 'gitlab/metrics/subscribers/rails_cache' Gitlab::Application.configure do |config| config.middleware.use(Gitlab::Metrics::RackMiddleware) diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb new file mode 100644 index 00000000000..49e5f86e6e6 --- /dev/null +++ b/lib/gitlab/metrics/subscribers/rails_cache.rb @@ -0,0 +1,39 @@ +module Gitlab + module Metrics + module Subscribers + # Class for tracking the total time spent in Rails cache calls + class RailsCache < ActiveSupport::Subscriber + attach_to :active_support + + def cache_read(event) + increment(:cache_read_duration, event.duration) + end + + def cache_write(event) + increment(:cache_write_duration, event.duration) + end + + def cache_delete(event) + increment(:cache_delete_duration, event.duration) + end + + def cache_exist?(event) + increment(:cache_exists_duration, event.duration) + end + + def increment(key, duration) + return unless current_transaction + + current_transaction.increment(:cache_duration, duration) + current_transaction.increment(key, duration) + end + + private + + def current_transaction + Transaction.current + end + end + end + end +end diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb new file mode 100644 index 00000000000..e01b0b4bd21 --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::RailsCache do + let(:transaction) { Gitlab::Metrics::Transaction.new } + let(:subscriber) { described_class.new } + + let(:event) { double(:event, duration: 15.2) } + + describe '#cache_read' do + it 'increments the cache_read duration' do + expect(subscriber).to receive(:increment). + with(:cache_read_duration, event.duration) + + subscriber.cache_read(event) + end + end + + describe '#cache_write' do + it 'increments the cache_write duration' do + expect(subscriber).to receive(:increment). + with(:cache_write_duration, event.duration) + + subscriber.cache_write(event) + end + end + + describe '#cache_delete' do + it 'increments the cache_delete duration' do + expect(subscriber).to receive(:increment). + with(:cache_delete_duration, event.duration) + + subscriber.cache_delete(event) + end + end + + describe '#cache_exist?' do + it 'increments the cache_exists duration' do + expect(subscriber).to receive(:increment). + with(:cache_exists_duration, event.duration) + + subscriber.cache_exist?(event) + end + end + + describe '#increment' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.increment(:foo, 15.2) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the total and specific cache duration' do + expect(transaction).to receive(:increment). + with(:cache_duration, event.duration) + + expect(transaction).to receive(:increment). + with(:cache_delete_duration, event.duration) + + subscriber.increment(:cache_delete_duration, event.duration) + end + end + end +end -- cgit v1.2.1 From f9014e4ec60a87608c65028dcf5fa411a6145b4e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 8 Apr 2016 09:46:32 -0700 Subject: Use meta key to open todo in new tab --- app/assets/javascripts/todos.js.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index ec2df6c5b73..886da72e261 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -57,5 +57,10 @@ class @Todos $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count - goToTodoUrl: -> - Turbolinks.visit($(this).data('url')) + goToTodoUrl: (e)-> + todoLink = $(this).data('url') + if e.metaKey + e.preventDefault() + window.open(todoLink,'_blank') + else + Turbolinks.visit(todoLink) -- cgit v1.2.1 From 6a0f839941ae367cb542fd59685b8f394f59fe75 Mon Sep 17 00:00:00 2001 From: connorshea Date: Fri, 8 Apr 2016 13:32:17 -0600 Subject: Add a missing Oxford comma in some text on the Activity graph. Resolves #14668. [ci skip] --- app/views/users/calendar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 7f29918dba3..1de71f37d1a 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -7,4 +7,4 @@ '#{user_calendar_activities_path}' ); -.calendar-hint Summary of issues, merge requests and push events +.calendar-hint Summary of issues, merge requests, and push events -- cgit v1.2.1 From d11288be9580633baa3b21b4c70a5fc01e53d094 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 16:49:01 -0300 Subject: Use query instead of model on migrations --- db/migrate/20160328115649_migrate_new_notification_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb index 6a68890f5b1..0a110869027 100644 --- a/db/migrate/20160328115649_migrate_new_notification_setting.rb +++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb @@ -12,6 +12,6 @@ class MigrateNewNotificationSetting < ActiveRecord::Migration end def down - NotificationSetting.delete_all + execute "DELETE FROM notification_settings" end end -- cgit v1.2.1 From 127119f2c4db9038a7f34d1cc73ae1ed19cf0b8d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 16:59:06 -0300 Subject: Simplify query to retrieve NotificationSetting on controllers --- app/controllers/groups/notification_settings_controller.rb | 2 +- app/controllers/projects/notification_settings_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb index 78e43c83aba..20405a05190 100644 --- a/app/controllers/groups/notification_settings_controller.rb +++ b/app/controllers/groups/notification_settings_controller.rb @@ -1,6 +1,6 @@ class Groups::NotificationSettingsController < Groups::ApplicationController def update - notification_setting = group.notification_settings.where(user_id: current_user).find(params[:id]) + notification_setting = group.notification_settings.find_by(user_id: current_user) saved = notification_setting.update_attributes(notification_setting_params) render json: { saved: saved } diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb index 3ecf63d107f..da9034380af 100644 --- a/app/controllers/projects/notification_settings_controller.rb +++ b/app/controllers/projects/notification_settings_controller.rb @@ -8,7 +8,7 @@ class Projects::NotificationSettingsController < Projects::ApplicationController end def update - notification_setting = project.notification_settings.where(user_id: current_user).find(params[:id]) + notification_setting = project.notification_settings.find_by(user_id: current_user) saved = notification_setting.update_attributes(notification_setting_params) render json: { saved: saved } -- cgit v1.2.1 From 069724cef5873b83720004772d1e874030cc9fff Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 17:11:13 -0300 Subject: Use singular resource for NotificationSetting Since a user cannot have multiple NotificationSettings records for one group/project we can use singular resource. --- config/routes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index fade07c0500..552385110dd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -406,7 +406,7 @@ Rails.application.routes.draw do resource :avatar, only: [:destroy] resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] - resources :notification_settings, only: [:update] + resource :notification_setting, only: [:update] end end @@ -608,7 +608,7 @@ Rails.application.routes.draw do resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] - resources :notification_settings, only: [:create, :update] + resource :notification_setting, only: [:create, :update] resources :refs, only: [] do collection do -- cgit v1.2.1 From ee497599cc69b126cc2e4a929f1799d3d3eb989d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 17:24:27 -0300 Subject: Use default_value_for to set default NotificationSetting#level --- app/controllers/projects_controller.rb | 1 - app/models/notification_setting.rb | 14 ++++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 41a4c41cf80..39f436f6f4e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -106,7 +106,6 @@ class ProjectsController < Projects::ApplicationController if @membership @notification_setting = current_user.notification_settings.find_or_initialize_by(source: @project) - @notification_setting.set_defaults unless @notification_setting.persisted? end end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 13a8995b036..d89194b5a12 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,4 +1,10 @@ class NotificationSetting < ActiveRecord::Base + # Notification level + # Note: When adding an option, it MUST go on the end of the array. + enum level: [:disabled, :participating, :watch, :global, :mention] + + default_value_for :level, NotificationSetting.levels[:global] + belongs_to :user belongs_to :source, polymorphic: true @@ -8,9 +14,6 @@ class NotificationSetting < ActiveRecord::Base validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source", allow_nil: true } - # Notification level - # Note: When adding an option, it MUST go on the end of the array. - enum level: [:disabled, :participating, :watch, :global, :mention] scope :for_groups, -> { where(source_type: 'Namespace') } scope :for_projects, -> { where(source_type: 'Project') } @@ -19,14 +22,9 @@ class NotificationSetting < ActiveRecord::Base setting = find_or_initialize_by(source: source) unless setting.persisted? - setting.set_defaults setting.save end setting end - - def set_defaults - self.level = :global - end end -- cgit v1.2.1 From 635b65d1206293d0b92322df18d458447ee73d5a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 17:35:21 -0300 Subject: Add method to return the user notification setting for a group, or a project --- app/controllers/projects_controller.rb | 2 +- app/models/user.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 39f436f6f4e..3768efe142a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -105,7 +105,7 @@ class ProjectsController < Projects::ApplicationController @membership = @project.team.find_member(current_user.id) if @membership - @notification_setting = current_user.notification_settings.find_or_initialize_by(source: @project) + @notification_setting = current_user.notification_settings_for(@project) end end diff --git a/app/models/user.rb b/app/models/user.rb index f68379fd050..031315debd7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -831,6 +831,10 @@ class User < ActiveRecord::Base end end + def notification_settings_for(source) + notification_settings.find_or_initialize_by(source: source) + end + private def projects_union -- cgit v1.2.1 From e3d4ebdd543f17d91e5c67be2f30bb97c687b448 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 7 Apr 2016 16:40:51 +0200 Subject: Update gitlab-shell to 2.7.2 --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 24ba9a38de6..37c2961c243 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.7.0 +2.7.2 -- cgit v1.2.1 From 47c8b7f3037c3e464e34d6f978a27e591f09e687 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 17:44:55 -0300 Subject: Fix CHANGELOG --- CHANGELOG | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c072cada732..eae6cb90700 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,10 +25,6 @@ v 8.7.0 (unreleased) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - -v 8.6.2 (unreleased) - - Comments on confidential issues don't show up in activity feed to non-members - - Fix NoMethodError when visiting CI root path at `/ci` - Fix creation of merge requests for orphaned branches (Stan Hu) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) -- cgit v1.2.1 From 99067a505ca62dc069189118d7d4c4e91de83917 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 8 Apr 2016 18:06:36 -0300 Subject: Fix schema.rb --- db/schema.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 4c7673511fb..a66274dc5a1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331133914) do +ActiveRecord::Schema.define(version: 20160331223143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -799,9 +799,9 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true -- cgit v1.2.1 From 3a196317abe1899b2b3c61fa97bed797c80456b9 Mon Sep 17 00:00:00 2001 From: connorshea Date: Thu, 7 Apr 2016 17:40:22 -0600 Subject: Add "sprite" parameter to emoji_icon helper The emoji_icon helper used to display the award emoji in Issue and Merge Request views. By default the spritesheet is used, but passing `sprite: false` to the `emoji_icon` helper makes the emoji render as separate images. For award emoji displayed by default (e.g. thumbs-up, thumbs-down, and any that have been awarded to the issue/MR) the independent images are used. Only when the full emoji menu is opened does the full spritesheet load. On a normal issue this change decreases the page load by 670KB or 250KB depending on pixel density. Resolves #14334. --- CHANGELOG | 1 + app/helpers/issues_helper.rb | 33 ++++++++++++++++++++++++--------- app/views/votes/_votes_block.html.haml | 2 +- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 54d79259b30..0997c15cfdc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) + - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Improved Markdown rendering performance !3389 (Yorick Peterse) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 24b90fef4fe..bcf8639c829 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -115,17 +115,32 @@ module IssuesHelper icon('eye-slash') if issue.confidential? end - def emoji_icon(name, unicode = nil, aliases = []) + def emoji_icon(name, unicode = nil, aliases = [], sprite: true) unicode ||= Emoji.emoji_filename(name) rescue "" - content_tag :div, "", - class: "icon emoji-icon emoji-#{unicode}", - title: name, - data: { - aliases: aliases.join(' '), - emoji: name, - unicode_name: unicode - } + data = { + aliases: aliases.join(" "), + emoji: name, + unicode_name: unicode + } + + if sprite + # Emoji icons for the emoji menu, these use a spritesheet. + content_tag :div, "", + class: "icon emoji-icon emoji-#{unicode}", + title: name, + data: data + else + # Emoji icons displayed separately, used for the awards already given + # to an issue or merge request. + content_tag :img, "", + class: "icon emoji", + title: name, + height: "20px", + width: "20px", + src: url_to_image("#{unicode}.png"), + data: data + end end def emoji_author_list(notes, current_user) diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 8ffcdc4a327..49cfcd53d74 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,7 +1,7 @@ .awards.votes-block - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}} - = emoji_icon(emoji) + = emoji_icon(emoji, sprite: false) %span.award-control-text.js-counter = notes.count -- cgit v1.2.1 From cedcc1453cbbd4a8ba43cf932b8765d891f19e0c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 01:19:21 -0400 Subject: Minor Testing guide copyedits [ci skip] --- doc/development/testing.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/development/testing.md b/doc/development/testing.md index 23417845f16..672e3fb4649 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -16,16 +16,18 @@ GitLab uses [factory_girl] as a test fixture replacement. - Factory definitions live in `spec/factories/`, named using the pluralization of their corresponding model (`User` factories are defined in `users.rb`). - There should be only one top-level factory definition per file. -- Make use of [Traits] to clean up definitions and usages. +- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and + should) call `create(...)` instead of `FactoryGirl.create(...)`. +- Make use of [traits] to clean up definitions and usages. - When defining a factory, don't define attributes that are not required for the resulting record to pass validation. -- When instantiating from a factory, don't supply extraneous attributes that - aren't required by the test. +- When instantiating from a factory, don't supply attributes that aren't + required by the test. - Factories don't have to be limited to `ActiveRecord` objects. [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). [factory_girl]: https://github.com/thoughtbot/factory_girl -[Traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits +[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits ## JavaScript @@ -40,9 +42,9 @@ the command line via `bundle exec teaspoon`, or via a web browser at `spec/javascripts/fixtures`. They should contain the bare minimum amount of markup necessary for the test. - > **Warning:** Keep in mind that a Rails view may change and - invalidate your test, but everything will still pass because your fixture - doesn't reflect the latest view. + > **Warning:** Keep in mind that a Rails view may change and + invalidate your test, but everything will still pass because your fixture + doesn't reflect the latest view. - Keep in mind that in a CI environment, these tests are run in a headless browser and you will not have access to certain APIs, such as @@ -102,7 +104,7 @@ Here are some things to keep in mind regarding test performance: - Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, `spy`, or `double` will do. Database persistence is slow! - Use `create(:empty_project)` instead of `create(:project)` when you don't need - the underlying repository. Filesystem operations are slow! + the underlying Git repository. Filesystem operations are slow! - Don't mark a feature as requiring JavaScript (through `@javascript` in Spinach or `js: true` in RSpec) unless it's _actually_ required for the test to be valid. Headless browser testing is slow! -- cgit v1.2.1 From 1d1ca8b9c257f79ae75740cacad7d361635312b6 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Sat, 9 Apr 2016 13:29:37 +0100 Subject: fix emoji aliases not showing in autocomplete --- fixtures/emojis/digests.json | 2487 +++++++++++++++++++++++++++++++++++++++++- lib/tasks/gemojione.rake | 15 +- 2 files changed, 2499 insertions(+), 3 deletions(-) diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json index 18d6e93e0f4..41ca617847e 100644 --- a/fixtures/emojis/digests.json +++ b/fixtures/emojis/digests.json @@ -64,21 +64,41 @@ "unicode": "1F6EA", "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4" }, + { + "name": "northeast_pointing_airplane", + "unicode": "1F6EA", + "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4" + }, { "name": "airplane_small", "unicode": "1F6E9", "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" }, + { + "name": "small_airplane", + "unicode": "1F6E9", + "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" + }, { "name": "airplane_small_up", "unicode": "1F6E8", "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a" }, + { + "name": "up_pointing_small_airplane", + "unicode": "1F6E8", + "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a" + }, { "name": "airplane_up", "unicode": "1F6E7", "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77" }, + { + "name": "up_pointing_airplane", + "unicode": "1F6E7", + "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77" + }, { "name": "alarm_clock", "unicode": "23F0", @@ -149,11 +169,21 @@ "unicode": "1F5EE", "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc" }, + { + "name": "left_anger_bubble", + "unicode": "1F5EE", + "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc" + }, { "name": "anger_right", "unicode": "1F5EF", "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" }, + { + "name": "right_anger_bubble", + "unicode": "1F5EF", + "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" + }, { "name": "angry", "unicode": "1F620", @@ -304,6 +334,11 @@ "unicode": "002A-20E3", "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" }, + { + "name": "keycap_asterisk", + "unicode": "002A-20E3", + "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" + }, { "name": "astonished", "unicode": "1F632", @@ -324,6 +359,11 @@ "unicode": "269B", "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" }, + { + "name": "atom_symbol", + "unicode": "269B", + "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" + }, { "name": "b", "unicode": "1F171", @@ -399,11 +439,21 @@ "unicode": "1F5F3", "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" }, + { + "name": "ballot_box_with_ballot", + "unicode": "1F5F3", + "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" + }, { "name": "ballot_box_check", "unicode": "1F5F9", "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15" }, + { + "name": "ballot_box_with_bold_check", + "unicode": "1F5F9", + "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15" + }, { "name": "ballot_box_with_check", "unicode": "2611", @@ -414,11 +464,21 @@ "unicode": "1F5F5", "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928" }, + { + "name": "ballot_box_with_script_x", + "unicode": "1F5F5", + "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928" + }, { "name": "ballot_x", "unicode": "1F5F4", "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1" }, + { + "name": "ballot_script_x", + "unicode": "1F5F4", + "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1" + }, { "name": "bamboo", "unicode": "1F38D", @@ -464,31 +524,61 @@ "unicode": "26F9", "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" }, + { + "name": "person_with_ball", + "unicode": "26F9", + "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" + }, { "name": "basketball_player_tone1", "unicode": "26F9-1F3FB", "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" }, + { + "name": "person_with_ball_tone1", + "unicode": "26F9-1F3FB", + "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" + }, { "name": "basketball_player_tone2", "unicode": "26F9-1F3FC", "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" }, + { + "name": "person_with_ball_tone2", + "unicode": "26F9-1F3FC", + "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" + }, { "name": "basketball_player_tone3", "unicode": "26F9-1F3FD", "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" }, + { + "name": "person_with_ball_tone3", + "unicode": "26F9-1F3FD", + "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" + }, { "name": "basketball_player_tone4", "unicode": "26F9-1F3FE", "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" }, + { + "name": "person_with_ball_tone4", + "unicode": "26F9-1F3FE", + "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" + }, { "name": "basketball_player_tone5", "unicode": "26F9-1F3FF", "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" }, + { + "name": "person_with_ball_tone5", + "unicode": "26F9-1F3FF", + "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" + }, { "name": "bath", "unicode": "1F6C0", @@ -534,11 +624,21 @@ "unicode": "1F3D6", "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" }, + { + "name": "beach_with_umbrella", + "unicode": "1F3D6", + "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" + }, { "name": "beach_umbrella", "unicode": "26F1", "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" }, + { + "name": "umbrella_on_ground", + "unicode": "26F1", + "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" + }, { "name": "bear", "unicode": "1F43B", @@ -584,6 +684,11 @@ "unicode": "1F6CE", "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" }, + { + "name": "bellhop_bell", + "unicode": "1F6CE", + "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" + }, { "name": "bento", "unicode": "1F371", @@ -634,6 +739,11 @@ "unicode": "2623", "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" }, + { + "name": "biohazard_sign", + "unicode": "2623", + "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" + }, { "name": "bird", "unicode": "1F426", @@ -769,6 +879,11 @@ "unicode": "1F395", "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d" }, + { + "name": "bouquet_of_flowers", + "unicode": "1F395", + "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d" + }, { "name": "bow", "unicode": "1F647", @@ -779,6 +894,11 @@ "unicode": "1F3F9", "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" }, + { + "name": "archery", + "unicode": "1F3F9", + "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" + }, { "name": "bow_tone1", "unicode": "1F647-1F3FB", @@ -924,6 +1044,11 @@ "unicode": "1F56C", "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c" }, + { + "name": "bullhorn_with_sound_waves", + "unicode": "1F56C", + "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c" + }, { "name": "burrito", "unicode": "1F32F", @@ -964,6 +1089,11 @@ "unicode": "1F5A9", "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c" }, + { + "name": "pocket calculator", + "unicode": "1F5A9", + "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c" + }, { "name": "calendar", "unicode": "1F4C6", @@ -974,6 +1104,11 @@ "unicode": "1F5D3", "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" }, + { + "name": "spiral_calendar_pad", + "unicode": "1F5D3", + "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" + }, { "name": "calling", "unicode": "1F4F2", @@ -1034,6 +1169,11 @@ "unicode": "1F5C3", "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" }, + { + "name": "card_file_box", + "unicode": "1F5C3", + "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" + }, { "name": "card_index", "unicode": "1F4C7", @@ -1049,6 +1189,11 @@ "unicode": "1F5AD", "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2" }, + { + "name": "tape_cartridge", + "unicode": "1F5AD", + "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2" + }, { "name": "cat", "unicode": "1F431", @@ -1079,6 +1224,11 @@ "unicode": "1F37E", "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" }, + { + "name": "bottle_with_popping_cork", + "unicode": "1F37E", + "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" + }, { "name": "chart", "unicode": "1F4B9", @@ -1104,6 +1254,11 @@ "unicode": "1F9C0", "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" }, + { + "name": "cheese_wedge", + "unicode": "1F9C0", + "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" + }, { "name": "cherries", "unicode": "1F352", @@ -1169,6 +1324,11 @@ "unicode": "1F307", "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" }, + { + "name": "city_sunrise", + "unicode": "1F307", + "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" + }, { "name": "cityscape", "unicode": "1F3D9", @@ -1229,6 +1389,11 @@ "unicode": "1F570", "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" }, + { + "name": "mantlepiece_clock", + "unicode": "1F570", + "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" + }, { "name": "clock1", "unicode": "1F550", @@ -1354,6 +1519,11 @@ "unicode": "1F5D8", "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622" }, + { + "name": "clockwise_right_and_left_semicircle_arrows", + "unicode": "1F5D8", + "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622" + }, { "name": "closed_book", "unicode": "1F4D5", @@ -1379,21 +1549,41 @@ "unicode": "1F329", "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" }, + { + "name": "cloud_with_lightning", + "unicode": "1F329", + "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" + }, { "name": "cloud_rain", "unicode": "1F327", "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" }, + { + "name": "cloud_with_rain", + "unicode": "1F327", + "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" + }, { "name": "cloud_snow", "unicode": "1F328", "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" }, + { + "name": "cloud_with_snow", + "unicode": "1F328", + "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" + }, { "name": "cloud_tornado", "unicode": "1F32A", "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" }, + { + "name": "cloud_with_tornado", + "unicode": "1F32A", + "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" + }, { "name": "clubs", "unicode": "2663", @@ -1439,6 +1629,11 @@ "unicode": "1F5B3", "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3" }, + { + "name": "old_personal_computer", + "unicode": "1F5B3", + "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3" + }, { "name": "confetti_ball", "unicode": "1F38A", @@ -1509,6 +1704,11 @@ "unicode": "1F3D7", "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" }, + { + "name": "building_construction", + "unicode": "1F3D7", + "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + }, { "name": "convenience_store", "unicode": "1F3EA", @@ -1569,6 +1769,11 @@ "unicode": "1F6CB", "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" }, + { + "name": "couch_and_lamp", + "unicode": "1F6CB", + "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" + }, { "name": "couple", "unicode": "1F46B", @@ -1579,6 +1784,11 @@ "unicode": "1F468-2764-1F468", "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" }, + { + "name": "couple_with_heart_mm", + "unicode": "1F468-2764-1F468", + "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" + }, { "name": "couple_with_heart", "unicode": "1F491", @@ -1589,6 +1799,11 @@ "unicode": "1F469-2764-1F469", "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" }, + { + "name": "couple_with_heart_ww", + "unicode": "1F469-2764-1F469", + "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" + }, { "name": "couplekiss", "unicode": "1F48F", @@ -1614,6 +1829,11 @@ "unicode": "1F58D", "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" }, + { + "name": "lower_left_crayon", + "unicode": "1F58D", + "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" + }, { "name": "credit_card", "unicode": "1F4B3", @@ -1629,6 +1849,11 @@ "unicode": "1F3CF", "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" }, + { + "name": "cricket_bat_ball", + "unicode": "1F3CF", + "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" + }, { "name": "crocodile", "unicode": "1F40A", @@ -1639,21 +1864,41 @@ "unicode": "271D", "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" }, + { + "name": "latin_cross", + "unicode": "271D", + "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" + }, { "name": "cross_heavy", "unicode": "1F547", "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70" }, + { + "name": "heavy_latin_cross", + "unicode": "1F547", + "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70" + }, { "name": "cross_white", "unicode": "1F546", "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6" }, + { + "name": "white_latin_cross", + "unicode": "1F546", + "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6" + }, { "name": "crossbones", "unicode": "1F571", "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584" }, + { + "name": "black_skull_and_crossbones", + "unicode": "1F571", + "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584" + }, { "name": "crossed_flags", "unicode": "1F38C", @@ -1674,6 +1919,11 @@ "unicode": "1F6F3", "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" }, + { + "name": "passenger_ship", + "unicode": "1F6F3", + "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" + }, { "name": "cry", "unicode": "1F622", @@ -1729,6 +1979,11 @@ "unicode": "1F5E1", "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" }, + { + "name": "dagger_knife", + "unicode": "1F5E1", + "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" + }, { "name": "dancer", "unicode": "1F483", @@ -1814,6 +2069,11 @@ "unicode": "1F5A5", "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" }, + { + "name": "desktop_computer", + "unicode": "1F5A5", + "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" + }, { "name": "desktop_window", "unicode": "1F5D4", @@ -1844,6 +2104,11 @@ "unicode": "1F5C2", "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" }, + { + "name": "card_index_dividers", + "unicode": "1F5C2", + "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" + }, { "name": "dizzy", "unicode": "1F4AB", @@ -1869,6 +2134,11 @@ "unicode": "1F5B9", "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72" }, + { + "name": "document_with_text", + "unicode": "1F5B9", + "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72" + }, { "name": "dog", "unicode": "1F436", @@ -1909,6 +2179,11 @@ "unicode": "1F54A", "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" }, + { + "name": "dove_of_peace", + "unicode": "1F54A", + "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" + }, { "name": "dragon", "unicode": "1F409", @@ -1944,6 +2219,11 @@ "unicode": "1F4E7", "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" }, + { + "name": "email", + "unicode": "1F4E7", + "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" + }, { "name": "ear", "unicode": "1F442", @@ -2044,21 +2324,41 @@ "unicode": "1F582", "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75" }, + { + "name": "back_of_envelope", + "unicode": "1F582", + "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75" + }, { "name": "envelope_flying", "unicode": "1F585", "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214" }, + { + "name": "flying_envelope", + "unicode": "1F585", + "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214" + }, { "name": "envelope_stamped", "unicode": "1F583", "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468" }, + { + "name": "stamped_envelope", + "unicode": "1F583", + "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468" + }, { "name": "envelope_stamped_pen", "unicode": "1F586", "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33" }, + { + "name": "pen_over_stamped_envelope", + "unicode": "1F586", + "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33" + }, { "name": "envelope_with_arrow", "unicode": "1F4E9", @@ -2254,31 +2554,61 @@ "unicode": "1F597", "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9" }, + { + "name": "white_down_pointing_left_hand_index", + "unicode": "1F597", + "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9" + }, { "name": "finger_pointing_down2", "unicode": "1F59F", "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee" }, + { + "name": "sideways_white_down_pointing_index", + "unicode": "1F59F", + "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee" + }, { "name": "finger_pointing_left", "unicode": "1F598", "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e" }, + { + "name": "sideways_white_left_pointing_index", + "unicode": "1F598", + "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e" + }, { "name": "finger_pointing_right", "unicode": "1F599", "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d" }, + { + "name": "sideways_white_right_pointing_index", + "unicode": "1F599", + "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d" + }, { "name": "finger_pointing_up", "unicode": "1F59E", "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d" }, + { + "name": "sideways_white_up_pointing_index", + "unicode": "1F59E", + "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d" + }, { "name": "fire", "unicode": "1F525", "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" }, + { + "name": "flame", + "unicode": "1F525", + "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" + }, { "name": "fire_engine", "unicode": "1F692", @@ -2289,6 +2619,11 @@ "unicode": "1F6F1", "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6" }, + { + "name": "oncoming_fire_engine", + "unicode": "1F6F1", + "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6" + }, { "name": "fireworks", "unicode": "1F386", @@ -2359,1188 +2694,2383 @@ "unicode": "1F1E6-1F1E8", "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" }, + { + "name": "ac", + "unicode": "1F1E6-1F1E8", + "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" + }, { "name": "flag_ad", "unicode": "1F1E6-1F1E9", "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" }, + { + "name": "ad", + "unicode": "1F1E6-1F1E9", + "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" + }, { "name": "flag_ae", "unicode": "1F1E6-1F1EA", "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" }, + { + "name": "ae", + "unicode": "1F1E6-1F1EA", + "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" + }, { "name": "flag_af", "unicode": "1F1E6-1F1EB", "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" }, + { + "name": "af", + "unicode": "1F1E6-1F1EB", + "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" + }, { "name": "flag_ag", "unicode": "1F1E6-1F1EC", "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" }, + { + "name": "ag", + "unicode": "1F1E6-1F1EC", + "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" + }, { "name": "flag_ai", "unicode": "1F1E6-1F1EE", "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" }, + { + "name": "ai", + "unicode": "1F1E6-1F1EE", + "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" + }, { "name": "flag_al", "unicode": "1F1E6-1F1F1", "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" }, + { + "name": "al", + "unicode": "1F1E6-1F1F1", + "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" + }, { "name": "flag_am", "unicode": "1F1E6-1F1F2", "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" }, + { + "name": "am", + "unicode": "1F1E6-1F1F2", + "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" + }, { "name": "flag_ao", "unicode": "1F1E6-1F1F4", "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" }, + { + "name": "ao", + "unicode": "1F1E6-1F1F4", + "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" + }, { "name": "flag_aq", "unicode": "1F1E6-1F1F6", "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" }, + { + "name": "aq", + "unicode": "1F1E6-1F1F6", + "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" + }, { "name": "flag_ar", "unicode": "1F1E6-1F1F7", "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" }, + { + "name": "ar", + "unicode": "1F1E6-1F1F7", + "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" + }, { "name": "flag_as", "unicode": "1F1E6-1F1F8", "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" }, + { + "name": "as", + "unicode": "1F1E6-1F1F8", + "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" + }, { "name": "flag_at", "unicode": "1F1E6-1F1F9", "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" }, + { + "name": "at", + "unicode": "1F1E6-1F1F9", + "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" + }, { "name": "flag_au", "unicode": "1F1E6-1F1FA", "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" }, + { + "name": "au", + "unicode": "1F1E6-1F1FA", + "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" + }, { "name": "flag_aw", "unicode": "1F1E6-1F1FC", "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" }, + { + "name": "aw", + "unicode": "1F1E6-1F1FC", + "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" + }, { "name": "flag_ax", "unicode": "1F1E6-1F1FD", "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" }, + { + "name": "ax", + "unicode": "1F1E6-1F1FD", + "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" + }, { "name": "flag_az", "unicode": "1F1E6-1F1FF", "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" }, + { + "name": "az", + "unicode": "1F1E6-1F1FF", + "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" + }, { "name": "flag_ba", "unicode": "1F1E7-1F1E6", "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" }, + { + "name": "ba", + "unicode": "1F1E7-1F1E6", + "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" + }, { "name": "flag_bb", "unicode": "1F1E7-1F1E7", "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" }, + { + "name": "bb", + "unicode": "1F1E7-1F1E7", + "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" + }, { "name": "flag_bd", "unicode": "1F1E7-1F1E9", "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" }, + { + "name": "bd", + "unicode": "1F1E7-1F1E9", + "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" + }, { "name": "flag_be", "unicode": "1F1E7-1F1EA", "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" }, + { + "name": "be", + "unicode": "1F1E7-1F1EA", + "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" + }, { "name": "flag_bf", "unicode": "1F1E7-1F1EB", "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" }, + { + "name": "bf", + "unicode": "1F1E7-1F1EB", + "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" + }, { "name": "flag_bg", "unicode": "1F1E7-1F1EC", "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" }, + { + "name": "bg", + "unicode": "1F1E7-1F1EC", + "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" + }, { "name": "flag_bh", "unicode": "1F1E7-1F1ED", "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" }, + { + "name": "bh", + "unicode": "1F1E7-1F1ED", + "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" + }, { "name": "flag_bi", "unicode": "1F1E7-1F1EE", "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" }, + { + "name": "bi", + "unicode": "1F1E7-1F1EE", + "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" + }, { "name": "flag_bj", "unicode": "1F1E7-1F1EF", "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" }, + { + "name": "bj", + "unicode": "1F1E7-1F1EF", + "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" + }, { "name": "flag_bl", "unicode": "1F1E7-1F1F1", "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" }, + { + "name": "bl", + "unicode": "1F1E7-1F1F1", + "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" + }, { "name": "flag_black", "unicode": "1F3F4", "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" }, + { + "name": "waving_black_flag", + "unicode": "1F3F4", + "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" + }, { "name": "flag_bm", "unicode": "1F1E7-1F1F2", "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" }, + { + "name": "bm", + "unicode": "1F1E7-1F1F2", + "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" + }, { "name": "flag_bn", "unicode": "1F1E7-1F1F3", "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" }, + { + "name": "bn", + "unicode": "1F1E7-1F1F3", + "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" + }, { "name": "flag_bo", "unicode": "1F1E7-1F1F4", "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" }, + { + "name": "bo", + "unicode": "1F1E7-1F1F4", + "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" + }, { "name": "flag_bq", "unicode": "1F1E7-1F1F6", "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" }, + { + "name": "bq", + "unicode": "1F1E7-1F1F6", + "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" + }, { "name": "flag_br", "unicode": "1F1E7-1F1F7", "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" }, + { + "name": "br", + "unicode": "1F1E7-1F1F7", + "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" + }, { "name": "flag_bs", "unicode": "1F1E7-1F1F8", "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" }, + { + "name": "bs", + "unicode": "1F1E7-1F1F8", + "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" + }, { "name": "flag_bt", "unicode": "1F1E7-1F1F9", "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" }, + { + "name": "bt", + "unicode": "1F1E7-1F1F9", + "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" + }, { "name": "flag_bv", "unicode": "1F1E7-1F1FB", "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" }, + { + "name": "bv", + "unicode": "1F1E7-1F1FB", + "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" + }, { "name": "flag_bw", "unicode": "1F1E7-1F1FC", "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" }, + { + "name": "bw", + "unicode": "1F1E7-1F1FC", + "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" + }, { "name": "flag_by", "unicode": "1F1E7-1F1FE", "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" }, + { + "name": "by", + "unicode": "1F1E7-1F1FE", + "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" + }, { "name": "flag_bz", "unicode": "1F1E7-1F1FF", "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" }, + { + "name": "bz", + "unicode": "1F1E7-1F1FF", + "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" + }, { "name": "flag_ca", "unicode": "1F1E8-1F1E6", "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" }, + { + "name": "ca", + "unicode": "1F1E8-1F1E6", + "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" + }, { "name": "flag_cc", "unicode": "1F1E8-1F1E8", "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" }, + { + "name": "cc", + "unicode": "1F1E8-1F1E8", + "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" + }, { "name": "flag_cd", "unicode": "1F1E8-1F1E9", "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" }, + { + "name": "congo", + "unicode": "1F1E8-1F1E9", + "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" + }, { "name": "flag_cf", "unicode": "1F1E8-1F1EB", "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" }, + { + "name": "cf", + "unicode": "1F1E8-1F1EB", + "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" + }, { "name": "flag_cg", "unicode": "1F1E8-1F1EC", "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" }, + { + "name": "cg", + "unicode": "1F1E8-1F1EC", + "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" + }, { "name": "flag_ch", "unicode": "1F1E8-1F1ED", "digest": "53d6d35aeeebb0b4b1ad858dc3691e649ac73d30b3be76f96d5fe9605fa99386" }, + { + "name": "ch", + "unicode": "1F1E8-1F1ED", + "digest": "53d6d35aeeebb0b4b1ad858dc3691e649ac73d30b3be76f96d5fe9605fa99386" + }, { "name": "flag_ci", "unicode": "1F1E8-1F1EE", "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" }, + { + "name": "ci", + "unicode": "1F1E8-1F1EE", + "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" + }, { "name": "flag_ck", "unicode": "1F1E8-1F1F0", "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" }, + { + "name": "ck", + "unicode": "1F1E8-1F1F0", + "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" + }, { "name": "flag_cl", "unicode": "1F1E8-1F1F1", "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" }, + { + "name": "chile", + "unicode": "1F1E8-1F1F1", + "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" + }, { "name": "flag_cm", "unicode": "1F1E8-1F1F2", "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" }, + { + "name": "cm", + "unicode": "1F1E8-1F1F2", + "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" + }, { "name": "flag_cn", "unicode": "1F1E8-1F1F3", "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" }, + { + "name": "cn", + "unicode": "1F1E8-1F1F3", + "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" + }, { "name": "flag_co", "unicode": "1F1E8-1F1F4", "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" }, + { + "name": "co", + "unicode": "1F1E8-1F1F4", + "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" + }, { "name": "flag_cp", "unicode": "1F1E8-1F1F5", "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" }, + { + "name": "cp", + "unicode": "1F1E8-1F1F5", + "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + }, { "name": "flag_cr", "unicode": "1F1E8-1F1F7", "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" }, + { + "name": "cr", + "unicode": "1F1E8-1F1F7", + "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" + }, { "name": "flag_cu", "unicode": "1F1E8-1F1FA", "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" }, + { + "name": "cu", + "unicode": "1F1E8-1F1FA", + "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" + }, { "name": "flag_cv", "unicode": "1F1E8-1F1FB", "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" }, + { + "name": "cv", + "unicode": "1F1E8-1F1FB", + "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" + }, { "name": "flag_cw", "unicode": "1F1E8-1F1FC", "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" }, + { + "name": "cw", + "unicode": "1F1E8-1F1FC", + "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" + }, { "name": "flag_cx", "unicode": "1F1E8-1F1FD", "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" }, + { + "name": "cx", + "unicode": "1F1E8-1F1FD", + "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" + }, { "name": "flag_cy", "unicode": "1F1E8-1F1FE", "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" }, + { + "name": "cy", + "unicode": "1F1E8-1F1FE", + "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" + }, { "name": "flag_cz", "unicode": "1F1E8-1F1FF", "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" }, + { + "name": "cz", + "unicode": "1F1E8-1F1FF", + "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" + }, { "name": "flag_de", "unicode": "1F1E9-1F1EA", "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" }, + { + "name": "de", + "unicode": "1F1E9-1F1EA", + "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" + }, { "name": "flag_dg", "unicode": "1F1E9-1F1EC", "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" }, + { + "name": "dg", + "unicode": "1F1E9-1F1EC", + "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" + }, { "name": "flag_dj", "unicode": "1F1E9-1F1EF", "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" }, + { + "name": "dj", + "unicode": "1F1E9-1F1EF", + "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" + }, { "name": "flag_dk", "unicode": "1F1E9-1F1F0", "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" }, + { + "name": "dk", + "unicode": "1F1E9-1F1F0", + "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" + }, { "name": "flag_dm", "unicode": "1F1E9-1F1F2", "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" }, + { + "name": "dm", + "unicode": "1F1E9-1F1F2", + "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" + }, { "name": "flag_do", "unicode": "1F1E9-1F1F4", "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" }, + { + "name": "do", + "unicode": "1F1E9-1F1F4", + "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" + }, { "name": "flag_dz", "unicode": "1F1E9-1F1FF", "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" }, + { + "name": "dz", + "unicode": "1F1E9-1F1FF", + "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" + }, { "name": "flag_ea", "unicode": "1F1EA-1F1E6", "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" }, + { + "name": "ea", + "unicode": "1F1EA-1F1E6", + "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" + }, { "name": "flag_ec", "unicode": "1F1EA-1F1E8", "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" }, + { + "name": "ec", + "unicode": "1F1EA-1F1E8", + "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" + }, { "name": "flag_ee", "unicode": "1F1EA-1F1EA", "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" }, + { + "name": "ee", + "unicode": "1F1EA-1F1EA", + "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" + }, { "name": "flag_eg", "unicode": "1F1EA-1F1EC", "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" }, + { + "name": "eg", + "unicode": "1F1EA-1F1EC", + "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" + }, { "name": "flag_eh", "unicode": "1F1EA-1F1ED", "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" }, + { + "name": "eh", + "unicode": "1F1EA-1F1ED", + "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" + }, { "name": "flag_er", "unicode": "1F1EA-1F1F7", "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" }, + { + "name": "er", + "unicode": "1F1EA-1F1F7", + "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" + }, { "name": "flag_es", "unicode": "1F1EA-1F1F8", "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" }, + { + "name": "es", + "unicode": "1F1EA-1F1F8", + "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" + }, { "name": "flag_et", "unicode": "1F1EA-1F1F9", "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" }, + { + "name": "et", + "unicode": "1F1EA-1F1F9", + "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" + }, { "name": "flag_eu", "unicode": "1F1EA-1F1FA", "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" }, + { + "name": "eu", + "unicode": "1F1EA-1F1FA", + "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" + }, { "name": "flag_fi", "unicode": "1F1EB-1F1EE", "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" }, + { + "name": "fi", + "unicode": "1F1EB-1F1EE", + "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" + }, { "name": "flag_fj", "unicode": "1F1EB-1F1EF", "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" }, + { + "name": "fj", + "unicode": "1F1EB-1F1EF", + "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" + }, { "name": "flag_fk", "unicode": "1F1EB-1F1F0", "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" }, + { + "name": "fk", + "unicode": "1F1EB-1F1F0", + "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" + }, { "name": "flag_fm", "unicode": "1F1EB-1F1F2", "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" }, + { + "name": "fm", + "unicode": "1F1EB-1F1F2", + "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" + }, { "name": "flag_fo", "unicode": "1F1EB-1F1F4", "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" }, + { + "name": "fo", + "unicode": "1F1EB-1F1F4", + "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" + }, { "name": "flag_fr", "unicode": "1F1EB-1F1F7", "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" }, + { + "name": "fr", + "unicode": "1F1EB-1F1F7", + "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" + }, { "name": "flag_ga", "unicode": "1F1EC-1F1E6", "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" }, + { + "name": "ga", + "unicode": "1F1EC-1F1E6", + "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" + }, { "name": "flag_gb", "unicode": "1F1EC-1F1E7", "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" }, + { + "name": "gb", + "unicode": "1F1EC-1F1E7", + "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" + }, { "name": "flag_gd", "unicode": "1F1EC-1F1E9", "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" }, + { + "name": "gd", + "unicode": "1F1EC-1F1E9", + "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" + }, { "name": "flag_ge", "unicode": "1F1EC-1F1EA", "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" }, + { + "name": "ge", + "unicode": "1F1EC-1F1EA", + "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" + }, { "name": "flag_gf", "unicode": "1F1EC-1F1EB", "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" }, + { + "name": "gf", + "unicode": "1F1EC-1F1EB", + "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" + }, { "name": "flag_gg", "unicode": "1F1EC-1F1EC", "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" }, + { + "name": "gg", + "unicode": "1F1EC-1F1EC", + "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" + }, { "name": "flag_gh", "unicode": "1F1EC-1F1ED", "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" }, + { + "name": "gh", + "unicode": "1F1EC-1F1ED", + "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" + }, { "name": "flag_gi", "unicode": "1F1EC-1F1EE", "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" }, + { + "name": "gi", + "unicode": "1F1EC-1F1EE", + "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" + }, { "name": "flag_gl", "unicode": "1F1EC-1F1F1", "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" }, + { + "name": "gl", + "unicode": "1F1EC-1F1F1", + "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" + }, { "name": "flag_gm", "unicode": "1F1EC-1F1F2", "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" }, + { + "name": "gm", + "unicode": "1F1EC-1F1F2", + "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" + }, { "name": "flag_gn", "unicode": "1F1EC-1F1F3", "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" }, + { + "name": "gn", + "unicode": "1F1EC-1F1F3", + "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" + }, { "name": "flag_gp", "unicode": "1F1EC-1F1F5", "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" }, + { + "name": "gp", + "unicode": "1F1EC-1F1F5", + "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" + }, { "name": "flag_gq", "unicode": "1F1EC-1F1F6", "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" }, + { + "name": "gq", + "unicode": "1F1EC-1F1F6", + "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" + }, { "name": "flag_gr", "unicode": "1F1EC-1F1F7", "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" }, + { + "name": "gr", + "unicode": "1F1EC-1F1F7", + "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" + }, { "name": "flag_gs", "unicode": "1F1EC-1F1F8", "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" }, + { + "name": "gs", + "unicode": "1F1EC-1F1F8", + "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" + }, { "name": "flag_gt", "unicode": "1F1EC-1F1F9", "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" }, + { + "name": "gt", + "unicode": "1F1EC-1F1F9", + "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" + }, { "name": "flag_gu", "unicode": "1F1EC-1F1FA", "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" }, + { + "name": "gu", + "unicode": "1F1EC-1F1FA", + "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" + }, { "name": "flag_gw", "unicode": "1F1EC-1F1FC", "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" }, + { + "name": "gw", + "unicode": "1F1EC-1F1FC", + "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" + }, { "name": "flag_gy", "unicode": "1F1EC-1F1FE", "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" }, + { + "name": "gy", + "unicode": "1F1EC-1F1FE", + "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" + }, { "name": "flag_hk", "unicode": "1F1ED-1F1F0", "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" }, + { + "name": "hk", + "unicode": "1F1ED-1F1F0", + "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" + }, { "name": "flag_hm", "unicode": "1F1ED-1F1F2", "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" }, + { + "name": "hm", + "unicode": "1F1ED-1F1F2", + "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" + }, { "name": "flag_hn", "unicode": "1F1ED-1F1F3", "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" }, + { + "name": "hn", + "unicode": "1F1ED-1F1F3", + "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" + }, { "name": "flag_hr", "unicode": "1F1ED-1F1F7", "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" }, + { + "name": "hr", + "unicode": "1F1ED-1F1F7", + "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" + }, { "name": "flag_ht", "unicode": "1F1ED-1F1F9", "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" }, + { + "name": "ht", + "unicode": "1F1ED-1F1F9", + "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" + }, { "name": "flag_hu", "unicode": "1F1ED-1F1FA", "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" }, + { + "name": "hu", + "unicode": "1F1ED-1F1FA", + "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" + }, { "name": "flag_ic", "unicode": "1F1EE-1F1E8", "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" }, + { + "name": "ic", + "unicode": "1F1EE-1F1E8", + "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" + }, { "name": "flag_id", "unicode": "1F1EE-1F1E9", "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" }, + { + "name": "indonesia", + "unicode": "1F1EE-1F1E9", + "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" + }, { "name": "flag_ie", "unicode": "1F1EE-1F1EA", "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" }, + { + "name": "ie", + "unicode": "1F1EE-1F1EA", + "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" + }, { "name": "flag_il", "unicode": "1F1EE-1F1F1", "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" }, + { + "name": "il", + "unicode": "1F1EE-1F1F1", + "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" + }, { "name": "flag_im", "unicode": "1F1EE-1F1F2", "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" }, + { + "name": "im", + "unicode": "1F1EE-1F1F2", + "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" + }, { "name": "flag_in", "unicode": "1F1EE-1F1F3", "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" }, + { + "name": "in", + "unicode": "1F1EE-1F1F3", + "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" + }, { "name": "flag_io", "unicode": "1F1EE-1F1F4", "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" }, + { + "name": "io", + "unicode": "1F1EE-1F1F4", + "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" + }, { "name": "flag_iq", "unicode": "1F1EE-1F1F6", "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" }, + { + "name": "iq", + "unicode": "1F1EE-1F1F6", + "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" + }, { "name": "flag_ir", "unicode": "1F1EE-1F1F7", "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" }, + { + "name": "ir", + "unicode": "1F1EE-1F1F7", + "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" + }, { "name": "flag_is", "unicode": "1F1EE-1F1F8", "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" }, + { + "name": "is", + "unicode": "1F1EE-1F1F8", + "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" + }, { "name": "flag_it", "unicode": "1F1EE-1F1F9", "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" }, + { + "name": "it", + "unicode": "1F1EE-1F1F9", + "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" + }, { "name": "flag_je", "unicode": "1F1EF-1F1EA", "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" }, + { + "name": "je", + "unicode": "1F1EF-1F1EA", + "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" + }, { "name": "flag_jm", "unicode": "1F1EF-1F1F2", "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" }, + { + "name": "jm", + "unicode": "1F1EF-1F1F2", + "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" + }, { "name": "flag_jo", "unicode": "1F1EF-1F1F4", "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" }, + { + "name": "jo", + "unicode": "1F1EF-1F1F4", + "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" + }, { "name": "flag_jp", "unicode": "1F1EF-1F1F5", "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" }, + { + "name": "jp", + "unicode": "1F1EF-1F1F5", + "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" + }, { "name": "flag_ke", "unicode": "1F1F0-1F1EA", "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" }, + { + "name": "ke", + "unicode": "1F1F0-1F1EA", + "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" + }, { "name": "flag_kg", "unicode": "1F1F0-1F1EC", "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" }, + { + "name": "kg", + "unicode": "1F1F0-1F1EC", + "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" + }, { "name": "flag_kh", "unicode": "1F1F0-1F1ED", "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" }, + { + "name": "kh", + "unicode": "1F1F0-1F1ED", + "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" + }, { "name": "flag_ki", "unicode": "1F1F0-1F1EE", "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" }, + { + "name": "ki", + "unicode": "1F1F0-1F1EE", + "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" + }, { "name": "flag_km", "unicode": "1F1F0-1F1F2", "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" }, + { + "name": "km", + "unicode": "1F1F0-1F1F2", + "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" + }, { "name": "flag_kn", "unicode": "1F1F0-1F1F3", "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" }, + { + "name": "kn", + "unicode": "1F1F0-1F1F3", + "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" + }, { "name": "flag_kp", "unicode": "1F1F0-1F1F5", "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" }, + { + "name": "kp", + "unicode": "1F1F0-1F1F5", + "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" + }, { "name": "flag_kr", "unicode": "1F1F0-1F1F7", "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" }, + { + "name": "kr", + "unicode": "1F1F0-1F1F7", + "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" + }, { "name": "flag_kw", "unicode": "1F1F0-1F1FC", "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" }, + { + "name": "kw", + "unicode": "1F1F0-1F1FC", + "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" + }, { "name": "flag_ky", "unicode": "1F1F0-1F1FE", "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" }, + { + "name": "ky", + "unicode": "1F1F0-1F1FE", + "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" + }, { "name": "flag_kz", "unicode": "1F1F0-1F1FF", "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" }, + { + "name": "kz", + "unicode": "1F1F0-1F1FF", + "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" + }, { "name": "flag_la", "unicode": "1F1F1-1F1E6", "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" }, + { + "name": "la", + "unicode": "1F1F1-1F1E6", + "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" + }, { "name": "flag_lb", "unicode": "1F1F1-1F1E7", "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" }, + { + "name": "lb", + "unicode": "1F1F1-1F1E7", + "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" + }, { "name": "flag_lc", "unicode": "1F1F1-1F1E8", "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" }, + { + "name": "lc", + "unicode": "1F1F1-1F1E8", + "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" + }, { "name": "flag_li", "unicode": "1F1F1-1F1EE", "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" }, + { + "name": "li", + "unicode": "1F1F1-1F1EE", + "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" + }, { "name": "flag_lk", "unicode": "1F1F1-1F1F0", "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" }, + { + "name": "lk", + "unicode": "1F1F1-1F1F0", + "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" + }, { "name": "flag_lr", "unicode": "1F1F1-1F1F7", "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" }, + { + "name": "lr", + "unicode": "1F1F1-1F1F7", + "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" + }, { "name": "flag_ls", "unicode": "1F1F1-1F1F8", "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" }, + { + "name": "ls", + "unicode": "1F1F1-1F1F8", + "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" + }, { "name": "flag_lt", "unicode": "1F1F1-1F1F9", "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" }, + { + "name": "lt", + "unicode": "1F1F1-1F1F9", + "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" + }, { "name": "flag_lu", "unicode": "1F1F1-1F1FA", "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" }, + { + "name": "lu", + "unicode": "1F1F1-1F1FA", + "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" + }, { "name": "flag_lv", "unicode": "1F1F1-1F1FB", "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" }, + { + "name": "lv", + "unicode": "1F1F1-1F1FB", + "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" + }, { "name": "flag_ly", "unicode": "1F1F1-1F1FE", "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" }, + { + "name": "ly", + "unicode": "1F1F1-1F1FE", + "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" + }, { "name": "flag_ma", "unicode": "1F1F2-1F1E6", "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" }, + { + "name": "ma", + "unicode": "1F1F2-1F1E6", + "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" + }, { "name": "flag_mc", "unicode": "1F1F2-1F1E8", "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" }, + { + "name": "mc", + "unicode": "1F1F2-1F1E8", + "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" + }, { "name": "flag_md", "unicode": "1F1F2-1F1E9", "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" }, + { + "name": "md", + "unicode": "1F1F2-1F1E9", + "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" + }, { "name": "flag_me", "unicode": "1F1F2-1F1EA", "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" }, + { + "name": "me", + "unicode": "1F1F2-1F1EA", + "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" + }, { "name": "flag_mf", "unicode": "1F1F2-1F1EB", "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" }, + { + "name": "mf", + "unicode": "1F1F2-1F1EB", + "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + }, { "name": "flag_mg", "unicode": "1F1F2-1F1EC", "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" }, + { + "name": "mg", + "unicode": "1F1F2-1F1EC", + "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" + }, { "name": "flag_mh", "unicode": "1F1F2-1F1ED", "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" }, + { + "name": "mh", + "unicode": "1F1F2-1F1ED", + "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" + }, { "name": "flag_mk", "unicode": "1F1F2-1F1F0", "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" }, + { + "name": "mk", + "unicode": "1F1F2-1F1F0", + "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" + }, { "name": "flag_ml", "unicode": "1F1F2-1F1F1", "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" }, + { + "name": "ml", + "unicode": "1F1F2-1F1F1", + "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" + }, { "name": "flag_mm", "unicode": "1F1F2-1F1F2", "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" }, + { + "name": "mm", + "unicode": "1F1F2-1F1F2", + "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" + }, { "name": "flag_mn", "unicode": "1F1F2-1F1F3", "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" }, + { + "name": "mn", + "unicode": "1F1F2-1F1F3", + "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" + }, { "name": "flag_mo", "unicode": "1F1F2-1F1F4", "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" }, + { + "name": "mo", + "unicode": "1F1F2-1F1F4", + "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" + }, { "name": "flag_mp", "unicode": "1F1F2-1F1F5", "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" }, + { + "name": "mp", + "unicode": "1F1F2-1F1F5", + "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" + }, { "name": "flag_mq", "unicode": "1F1F2-1F1F6", "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" }, + { + "name": "mq", + "unicode": "1F1F2-1F1F6", + "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" + }, { "name": "flag_mr", "unicode": "1F1F2-1F1F7", "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" }, + { + "name": "mr", + "unicode": "1F1F2-1F1F7", + "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" + }, { "name": "flag_ms", "unicode": "1F1F2-1F1F8", "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" }, + { + "name": "ms", + "unicode": "1F1F2-1F1F8", + "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" + }, { "name": "flag_mt", "unicode": "1F1F2-1F1F9", "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" }, + { + "name": "mt", + "unicode": "1F1F2-1F1F9", + "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" + }, { "name": "flag_mu", "unicode": "1F1F2-1F1FA", "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" }, + { + "name": "mu", + "unicode": "1F1F2-1F1FA", + "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" + }, { "name": "flag_mv", "unicode": "1F1F2-1F1FB", "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" }, + { + "name": "mv", + "unicode": "1F1F2-1F1FB", + "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" + }, { "name": "flag_mw", "unicode": "1F1F2-1F1FC", "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" }, + { + "name": "mw", + "unicode": "1F1F2-1F1FC", + "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" + }, { "name": "flag_mx", "unicode": "1F1F2-1F1FD", "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" }, + { + "name": "mx", + "unicode": "1F1F2-1F1FD", + "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" + }, { "name": "flag_my", "unicode": "1F1F2-1F1FE", "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" }, + { + "name": "my", + "unicode": "1F1F2-1F1FE", + "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" + }, { "name": "flag_mz", "unicode": "1F1F2-1F1FF", "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" }, + { + "name": "mz", + "unicode": "1F1F2-1F1FF", + "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" + }, { "name": "flag_na", "unicode": "1F1F3-1F1E6", "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" }, + { + "name": "na", + "unicode": "1F1F3-1F1E6", + "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" + }, { "name": "flag_nc", "unicode": "1F1F3-1F1E8", "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" }, + { + "name": "nc", + "unicode": "1F1F3-1F1E8", + "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" + }, { "name": "flag_ne", "unicode": "1F1F3-1F1EA", "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" }, + { + "name": "ne", + "unicode": "1F1F3-1F1EA", + "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" + }, { "name": "flag_nf", "unicode": "1F1F3-1F1EB", "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" }, + { + "name": "nf", + "unicode": "1F1F3-1F1EB", + "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" + }, { "name": "flag_ng", "unicode": "1F1F3-1F1EC", "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" }, + { + "name": "nigeria", + "unicode": "1F1F3-1F1EC", + "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" + }, { "name": "flag_ni", "unicode": "1F1F3-1F1EE", "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" }, + { + "name": "ni", + "unicode": "1F1F3-1F1EE", + "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" + }, { "name": "flag_nl", "unicode": "1F1F3-1F1F1", "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" }, + { + "name": "nl", + "unicode": "1F1F3-1F1F1", + "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" + }, { "name": "flag_no", "unicode": "1F1F3-1F1F4", "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" }, + { + "name": "no", + "unicode": "1F1F3-1F1F4", + "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" + }, { "name": "flag_np", "unicode": "1F1F3-1F1F5", "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" }, + { + "name": "np", + "unicode": "1F1F3-1F1F5", + "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" + }, { "name": "flag_nr", "unicode": "1F1F3-1F1F7", "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" }, + { + "name": "nr", + "unicode": "1F1F3-1F1F7", + "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" + }, { "name": "flag_nu", "unicode": "1F1F3-1F1FA", "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" }, + { + "name": "nu", + "unicode": "1F1F3-1F1FA", + "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" + }, { "name": "flag_nz", "unicode": "1F1F3-1F1FF", "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" }, + { + "name": "nz", + "unicode": "1F1F3-1F1FF", + "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" + }, { "name": "flag_om", "unicode": "1F1F4-1F1F2", "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" }, + { + "name": "om", + "unicode": "1F1F4-1F1F2", + "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" + }, { "name": "flag_pa", "unicode": "1F1F5-1F1E6", "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" }, + { + "name": "pa", + "unicode": "1F1F5-1F1E6", + "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" + }, { "name": "flag_pe", "unicode": "1F1F5-1F1EA", "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" }, + { + "name": "pe", + "unicode": "1F1F5-1F1EA", + "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" + }, { "name": "flag_pf", "unicode": "1F1F5-1F1EB", "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" }, + { + "name": "pf", + "unicode": "1F1F5-1F1EB", + "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" + }, { "name": "flag_pg", "unicode": "1F1F5-1F1EC", "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" }, + { + "name": "pg", + "unicode": "1F1F5-1F1EC", + "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" + }, { "name": "flag_ph", "unicode": "1F1F5-1F1ED", "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" }, + { + "name": "ph", + "unicode": "1F1F5-1F1ED", + "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" + }, { "name": "flag_pk", "unicode": "1F1F5-1F1F0", "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" }, + { + "name": "pk", + "unicode": "1F1F5-1F1F0", + "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" + }, { "name": "flag_pl", "unicode": "1F1F5-1F1F1", "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" }, + { + "name": "pl", + "unicode": "1F1F5-1F1F1", + "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" + }, { "name": "flag_pm", "unicode": "1F1F5-1F1F2", "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" }, + { + "name": "pm", + "unicode": "1F1F5-1F1F2", + "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" + }, { "name": "flag_pn", "unicode": "1F1F5-1F1F3", "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" }, + { + "name": "pn", + "unicode": "1F1F5-1F1F3", + "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" + }, { "name": "flag_pr", "unicode": "1F1F5-1F1F7", "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" }, + { + "name": "pr", + "unicode": "1F1F5-1F1F7", + "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" + }, { "name": "flag_ps", "unicode": "1F1F5-1F1F8", "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" }, + { + "name": "ps", + "unicode": "1F1F5-1F1F8", + "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" + }, { "name": "flag_pt", "unicode": "1F1F5-1F1F9", "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" }, + { + "name": "pt", + "unicode": "1F1F5-1F1F9", + "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" + }, { "name": "flag_pw", "unicode": "1F1F5-1F1FC", "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" }, + { + "name": "pw", + "unicode": "1F1F5-1F1FC", + "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" + }, { "name": "flag_py", "unicode": "1F1F5-1F1FE", "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" }, + { + "name": "py", + "unicode": "1F1F5-1F1FE", + "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" + }, { "name": "flag_qa", "unicode": "1F1F6-1F1E6", "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" }, + { + "name": "qa", + "unicode": "1F1F6-1F1E6", + "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" + }, { "name": "flag_re", "unicode": "1F1F7-1F1EA", "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" }, + { + "name": "re", + "unicode": "1F1F7-1F1EA", + "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" + }, { "name": "flag_ro", "unicode": "1F1F7-1F1F4", "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" }, + { + "name": "ro", + "unicode": "1F1F7-1F1F4", + "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" + }, { "name": "flag_rs", "unicode": "1F1F7-1F1F8", "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" }, + { + "name": "rs", + "unicode": "1F1F7-1F1F8", + "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" + }, { "name": "flag_ru", "unicode": "1F1F7-1F1FA", "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" }, + { + "name": "ru", + "unicode": "1F1F7-1F1FA", + "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" + }, { "name": "flag_rw", "unicode": "1F1F7-1F1FC", "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" }, + { + "name": "rw", + "unicode": "1F1F7-1F1FC", + "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" + }, { "name": "flag_sa", "unicode": "1F1F8-1F1E6", "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" }, + { + "name": "saudiarabia", + "unicode": "1F1F8-1F1E6", + "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + }, + { + "name": "saudi", + "unicode": "1F1F8-1F1E6", + "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + }, { "name": "flag_sb", "unicode": "1F1F8-1F1E7", "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" }, + { + "name": "sb", + "unicode": "1F1F8-1F1E7", + "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" + }, { "name": "flag_sc", "unicode": "1F1F8-1F1E8", "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" }, + { + "name": "sc", + "unicode": "1F1F8-1F1E8", + "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" + }, { "name": "flag_sd", "unicode": "1F1F8-1F1E9", "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" }, + { + "name": "sd", + "unicode": "1F1F8-1F1E9", + "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" + }, { "name": "flag_se", "unicode": "1F1F8-1F1EA", "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" }, + { + "name": "se", + "unicode": "1F1F8-1F1EA", + "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" + }, { "name": "flag_sg", "unicode": "1F1F8-1F1EC", "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" }, + { + "name": "sg", + "unicode": "1F1F8-1F1EC", + "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" + }, { "name": "flag_sh", "unicode": "1F1F8-1F1ED", "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" }, + { + "name": "sh", + "unicode": "1F1F8-1F1ED", + "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" + }, { "name": "flag_si", "unicode": "1F1F8-1F1EE", "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" }, + { + "name": "si", + "unicode": "1F1F8-1F1EE", + "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" + }, { "name": "flag_sj", "unicode": "1F1F8-1F1EF", "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" }, + { + "name": "sj", + "unicode": "1F1F8-1F1EF", + "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" + }, { "name": "flag_sk", "unicode": "1F1F8-1F1F0", "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" }, + { + "name": "sk", + "unicode": "1F1F8-1F1F0", + "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" + }, { "name": "flag_sl", "unicode": "1F1F8-1F1F1", "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" }, + { + "name": "sl", + "unicode": "1F1F8-1F1F1", + "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" + }, { "name": "flag_sm", "unicode": "1F1F8-1F1F2", "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" }, + { + "name": "sm", + "unicode": "1F1F8-1F1F2", + "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" + }, { "name": "flag_sn", "unicode": "1F1F8-1F1F3", "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" }, + { + "name": "sn", + "unicode": "1F1F8-1F1F3", + "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" + }, { "name": "flag_so", "unicode": "1F1F8-1F1F4", "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" }, + { + "name": "so", + "unicode": "1F1F8-1F1F4", + "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" + }, { "name": "flag_sr", "unicode": "1F1F8-1F1F7", "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" }, + { + "name": "sr", + "unicode": "1F1F8-1F1F7", + "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" + }, { "name": "flag_ss", "unicode": "1F1F8-1F1F8", "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" }, + { + "name": "ss", + "unicode": "1F1F8-1F1F8", + "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" + }, { "name": "flag_st", "unicode": "1F1F8-1F1F9", "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" }, + { + "name": "st", + "unicode": "1F1F8-1F1F9", + "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" + }, { "name": "flag_sv", "unicode": "1F1F8-1F1FB", "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" }, + { + "name": "sv", + "unicode": "1F1F8-1F1FB", + "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" + }, { "name": "flag_sx", "unicode": "1F1F8-1F1FD", "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" }, + { + "name": "sx", + "unicode": "1F1F8-1F1FD", + "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" + }, { "name": "flag_sy", "unicode": "1F1F8-1F1FE", "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" }, + { + "name": "sy", + "unicode": "1F1F8-1F1FE", + "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" + }, { "name": "flag_sz", "unicode": "1F1F8-1F1FF", "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" }, + { + "name": "sz", + "unicode": "1F1F8-1F1FF", + "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" + }, { "name": "flag_ta", "unicode": "1F1F9-1F1E6", "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" }, + { + "name": "ta", + "unicode": "1F1F9-1F1E6", + "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" + }, { "name": "flag_tc", "unicode": "1F1F9-1F1E8", "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" }, + { + "name": "tc", + "unicode": "1F1F9-1F1E8", + "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" + }, { "name": "flag_td", "unicode": "1F1F9-1F1E9", "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" }, + { + "name": "td", + "unicode": "1F1F9-1F1E9", + "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" + }, { "name": "flag_tf", "unicode": "1F1F9-1F1EB", "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" }, + { + "name": "tf", + "unicode": "1F1F9-1F1EB", + "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" + }, { "name": "flag_tg", "unicode": "1F1F9-1F1EC", "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" }, + { + "name": "tg", + "unicode": "1F1F9-1F1EC", + "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" + }, { "name": "flag_th", "unicode": "1F1F9-1F1ED", "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" }, + { + "name": "th", + "unicode": "1F1F9-1F1ED", + "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" + }, { "name": "flag_tj", "unicode": "1F1F9-1F1EF", "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" }, + { + "name": "tj", + "unicode": "1F1F9-1F1EF", + "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" + }, { "name": "flag_tk", "unicode": "1F1F9-1F1F0", "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" }, + { + "name": "tk", + "unicode": "1F1F9-1F1F0", + "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" + }, { "name": "flag_tl", "unicode": "1F1F9-1F1F1", "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" }, + { + "name": "tl", + "unicode": "1F1F9-1F1F1", + "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" + }, { "name": "flag_tm", "unicode": "1F1F9-1F1F2", "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" }, + { + "name": "turkmenistan", + "unicode": "1F1F9-1F1F2", + "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" + }, { "name": "flag_tn", "unicode": "1F1F9-1F1F3", "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" }, + { + "name": "tn", + "unicode": "1F1F9-1F1F3", + "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" + }, { "name": "flag_to", "unicode": "1F1F9-1F1F4", "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" }, + { + "name": "to", + "unicode": "1F1F9-1F1F4", + "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" + }, { "name": "flag_tr", "unicode": "1F1F9-1F1F7", "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" }, + { + "name": "tr", + "unicode": "1F1F9-1F1F7", + "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" + }, { "name": "flag_tt", "unicode": "1F1F9-1F1F9", "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" }, + { + "name": "tt", + "unicode": "1F1F9-1F1F9", + "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" + }, { "name": "flag_tv", "unicode": "1F1F9-1F1FB", "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" }, + { + "name": "tuvalu", + "unicode": "1F1F9-1F1FB", + "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" + }, { "name": "flag_tw", "unicode": "1F1F9-1F1FC", "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" }, + { + "name": "tw", + "unicode": "1F1F9-1F1FC", + "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" + }, { "name": "flag_tz", "unicode": "1F1F9-1F1FF", "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" }, { - "name": "flag_ua", + "name": "tz", + "unicode": "1F1F9-1F1FF", + "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" + }, + { + "name": "flag_ua", + "unicode": "1F1FA-1F1E6", + "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" + }, + { + "name": "ua", "unicode": "1F1FA-1F1E6", "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" }, @@ -3549,106 +5079,211 @@ "unicode": "1F1FA-1F1EC", "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" }, + { + "name": "ug", + "unicode": "1F1FA-1F1EC", + "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" + }, { "name": "flag_um", "unicode": "1F1FA-1F1F2", "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" }, + { + "name": "um", + "unicode": "1F1FA-1F1F2", + "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" + }, { "name": "flag_us", "unicode": "1F1FA-1F1F8", "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" }, + { + "name": "us", + "unicode": "1F1FA-1F1F8", + "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" + }, { "name": "flag_uy", "unicode": "1F1FA-1F1FE", "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" }, + { + "name": "uy", + "unicode": "1F1FA-1F1FE", + "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" + }, { "name": "flag_uz", "unicode": "1F1FA-1F1FF", "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" }, + { + "name": "uz", + "unicode": "1F1FA-1F1FF", + "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" + }, { "name": "flag_va", "unicode": "1F1FB-1F1E6", "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" }, + { + "name": "va", + "unicode": "1F1FB-1F1E6", + "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" + }, { "name": "flag_vc", "unicode": "1F1FB-1F1E8", "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" }, + { + "name": "vc", + "unicode": "1F1FB-1F1E8", + "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" + }, { "name": "flag_ve", "unicode": "1F1FB-1F1EA", "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" }, + { + "name": "ve", + "unicode": "1F1FB-1F1EA", + "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" + }, { "name": "flag_vg", "unicode": "1F1FB-1F1EC", "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" }, + { + "name": "vg", + "unicode": "1F1FB-1F1EC", + "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" + }, { "name": "flag_vi", "unicode": "1F1FB-1F1EE", "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" }, + { + "name": "vi", + "unicode": "1F1FB-1F1EE", + "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" + }, { "name": "flag_vn", "unicode": "1F1FB-1F1F3", "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" }, + { + "name": "vn", + "unicode": "1F1FB-1F1F3", + "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" + }, { "name": "flag_vu", "unicode": "1F1FB-1F1FA", "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" }, + { + "name": "vu", + "unicode": "1F1FB-1F1FA", + "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" + }, { "name": "flag_wf", "unicode": "1F1FC-1F1EB", "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" }, + { + "name": "wf", + "unicode": "1F1FC-1F1EB", + "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + }, { "name": "flag_white", "unicode": "1F3F3", "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" }, + { + "name": "waving_white_flag", + "unicode": "1F3F3", + "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" + }, { "name": "flag_ws", "unicode": "1F1FC-1F1F8", "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" }, + { + "name": "ws", + "unicode": "1F1FC-1F1F8", + "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" + }, { "name": "flag_xk", "unicode": "1F1FD-1F1F0", "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" }, + { + "name": "xk", + "unicode": "1F1FD-1F1F0", + "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" + }, { "name": "flag_ye", "unicode": "1F1FE-1F1EA", "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" }, + { + "name": "ye", + "unicode": "1F1FE-1F1EA", + "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" + }, { "name": "flag_yt", "unicode": "1F1FE-1F1F9", "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" }, + { + "name": "yt", + "unicode": "1F1FE-1F1F9", + "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" + }, { "name": "flag_za", "unicode": "1F1FF-1F1E6", "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" }, + { + "name": "za", + "unicode": "1F1FF-1F1E6", + "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" + }, { "name": "flag_zm", "unicode": "1F1FF-1F1F2", "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" }, + { + "name": "zm", + "unicode": "1F1FF-1F1F2", + "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" + }, { "name": "flag_zw", "unicode": "1F1FF-1F1FC", "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" }, + { + "name": "zw", + "unicode": "1F1FF-1F1FC", + "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" + }, { "name": "flags", "unicode": "1F38F", @@ -3669,11 +5304,21 @@ "unicode": "1F581", "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697" }, + { + "name": "clamshell_mobile_phone", + "unicode": "1F581", + "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697" + }, { "name": "floppy_black", "unicode": "1F5AA", "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea" }, + { + "name": "black_hard_shell_floppy_disk", + "unicode": "1F5AA", + "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea" + }, { "name": "floppy_disk", "unicode": "1F4BE", @@ -3684,6 +5329,11 @@ "unicode": "1F5AB", "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0" }, + { + "name": "white_hard_shell_floppy_disk", + "unicode": "1F5AB", + "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0" + }, { "name": "flower_playing_cards", "unicode": "1F3B4", @@ -3714,6 +5364,11 @@ "unicode": "1F5C1", "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685" }, + { + "name": "open_folder", + "unicode": "1F5C1", + "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685" + }, { "name": "football", "unicode": "1F3C8", @@ -3734,6 +5389,11 @@ "unicode": "1F37D", "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" }, + { + "name": "fork_and_knife_with_plate", + "unicode": "1F37D", + "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" + }, { "name": "fountain", "unicode": "26F2", @@ -3754,16 +5414,31 @@ "unicode": "1F5BC", "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" }, + { + "name": "frame_with_picture", + "unicode": "1F5BC", + "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" + }, { "name": "frame_tiles", "unicode": "1F5BD", "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0" }, + { + "name": "frame_with_tiles", + "unicode": "1F5BD", + "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0" + }, { "name": "frame_x", "unicode": "1F5BE", "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15" }, + { + "name": "frame_with_an_x", + "unicode": "1F5BE", + "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15" + }, { "name": "free", "unicode": "1F193", @@ -3789,11 +5464,21 @@ "unicode": "1F626", "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" }, + { + "name": "anguished", + "unicode": "1F626", + "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" + }, { "name": "frowning2", "unicode": "2639", "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" }, + { + "name": "white_frowning_face", + "unicode": "2639", + "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" + }, { "name": "fuelpump", "unicode": "26FD", @@ -4029,6 +5714,11 @@ "unicode": "2692", "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" }, + { + "name": "hammer_and_pick", + "unicode": "2692", + "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" + }, { "name": "hamster", "unicode": "1F439", @@ -4039,41 +5729,81 @@ "unicode": "1F590", "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" }, + { + "name": "raised_hand_with_fingers_splayed", + "unicode": "1F590", + "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" + }, { "name": "hand_splayed_reverse", "unicode": "1F591", "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db" }, + { + "name": "reversed_raised_hand_with_fingers_splayed", + "unicode": "1F591", + "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db" + }, { "name": "hand_splayed_tone1", "unicode": "1F590-1F3FB", "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" }, + { + "name": "raised_hand_with_fingers_splayed_tone1", + "unicode": "1F590-1F3FB", + "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" + }, { "name": "hand_splayed_tone2", "unicode": "1F590-1F3FC", "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" }, + { + "name": "raised_hand_with_fingers_splayed_tone2", + "unicode": "1F590-1F3FC", + "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" + }, { "name": "hand_splayed_tone3", "unicode": "1F590-1F3FD", "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" }, + { + "name": "raised_hand_with_fingers_splayed_tone3", + "unicode": "1F590-1F3FD", + "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" + }, { "name": "hand_splayed_tone4", "unicode": "1F590-1F3FE", "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" }, + { + "name": "raised_hand_with_fingers_splayed_tone4", + "unicode": "1F590-1F3FE", + "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" + }, { "name": "hand_splayed_tone5", "unicode": "1F590-1F3FF", "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" }, + { + "name": "raised_hand_with_fingers_splayed_tone5", + "unicode": "1F590-1F3FF", + "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" + }, { "name": "hand_victory", "unicode": "1F594", "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735" }, + { + "name": "reversed_victory_hand", + "unicode": "1F594", + "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735" + }, { "name": "handbag", "unicode": "1F45C", @@ -4104,6 +5834,11 @@ "unicode": "1F915", "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" }, + { + "name": "face_with_head_bandage", + "unicode": "1F915", + "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" + }, { "name": "headphones", "unicode": "1F3A7", @@ -4129,6 +5864,11 @@ "unicode": "2763", "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" }, + { + "name": "heavy_heart_exclamation_mark_ornament", + "unicode": "2763", + "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" + }, { "name": "heart_eyes", "unicode": "1F60D", @@ -4144,6 +5884,11 @@ "unicode": "1F394", "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204" }, + { + "name": "heart_with_tip_on_the_left", + "unicode": "1F394", + "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204" + }, { "name": "heartbeat", "unicode": "1F493", @@ -4199,6 +5944,11 @@ "unicode": "26D1", "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" }, + { + "name": "helmet_with_white_cross", + "unicode": "26D1", + "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" + }, { "name": "herb", "unicode": "1F33F", @@ -4234,6 +5984,11 @@ "unicode": "1F3D8", "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" }, + { + "name": "house_buildings", + "unicode": "1F3D8", + "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" + }, { "name": "honey_pot", "unicode": "1F36F", @@ -4289,6 +6044,11 @@ "unicode": "1F32D", "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" }, + { + "name": "hot_dog", + "unicode": "1F32D", + "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" + }, { "name": "hotel", "unicode": "1F3E8", @@ -4319,6 +6079,11 @@ "unicode": "1F3DA", "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" }, + { + "name": "derelict_house_building", + "unicode": "1F3DA", + "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" + }, { "name": "house_with_garden", "unicode": "1F3E1", @@ -4329,6 +6094,11 @@ "unicode": "1F917", "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" }, + { + "name": "hugging_face", + "unicode": "1F917", + "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" + }, { "name": "hushed", "unicode": "1F62F", @@ -4379,6 +6149,11 @@ "unicode": "1F6C8", "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca" }, + { + "name": "circled_information_source", + "unicode": "1F6C8", + "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca" + }, { "name": "information_desk_person", "unicode": "1F481", @@ -4434,6 +6209,11 @@ "unicode": "1F3DD", "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" }, + { + "name": "desert_island", + "unicode": "1F3DD", + "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" + }, { "name": "izakaya_lantern", "unicode": "1F3EE", @@ -4474,6 +6254,11 @@ "unicode": "1F6E6", "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948" }, + { + "name": "up_pointing_military_airplane", + "unicode": "1F6E6", + "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948" + }, { "name": "joy", "unicode": "1F602", @@ -4504,21 +6289,41 @@ "unicode": "1F5DD", "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" }, + { + "name": "old_key", + "unicode": "1F5DD", + "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" + }, { "name": "keyboard", "unicode": "1F5AE", "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16" }, + { + "name": "wired_keyboard", + "unicode": "1F5AE", + "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16" + }, { "name": "keyboard_mouse", "unicode": "1F5A6", "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3" }, + { + "name": "keyboard_and_mouse", + "unicode": "1F5A6", + "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3" + }, { "name": "keyboard_with_jacks", "unicode": "1F398", "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee" }, + { + "name": "musical_keyboard_with_jacks", + "unicode": "1F398", + "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee" + }, { "name": "keycap_ten", "unicode": "1F51F", @@ -4539,11 +6344,21 @@ "unicode": "1F468-2764-1F48B-1F468", "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" }, + { + "name": "couplekiss_mm", + "unicode": "1F468-2764-1F48B-1F468", + "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" + }, { "name": "kiss_ww", "unicode": "1F469-2764-1F48B-1F469", "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" }, + { + "name": "couplekiss_ww", + "unicode": "1F469-2764-1F48B-1F469", + "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" + }, { "name": "kissing", "unicode": "1F617", @@ -4619,6 +6434,11 @@ "unicode": "1F606", "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" }, + { + "name": "satisfied", + "unicode": "1F606", + "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" + }, { "name": "leaves", "unicode": "1F343", @@ -4639,6 +6459,11 @@ "unicode": "1F57B", "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3" }, + { + "name": "left_hand_telephone_receiver", + "unicode": "1F57B", + "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3" + }, { "name": "left_right_arrow", "unicode": "2194", @@ -4674,6 +6499,11 @@ "unicode": "1F574", "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" }, + { + "name": "man_in_business_suit_levitating", + "unicode": "1F574", + "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" + }, { "name": "libra", "unicode": "264E", @@ -4684,36 +6514,71 @@ "unicode": "1F3CB", "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" }, + { + "name": "weight_lifter", + "unicode": "1F3CB", + "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" + }, { "name": "lifter_tone1", "unicode": "1F3CB-1F3FB", "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" }, + { + "name": "weight_lifter_tone1", + "unicode": "1F3CB-1F3FB", + "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" + }, { "name": "lifter_tone2", "unicode": "1F3CB-1F3FC", "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" }, + { + "name": "weight_lifter_tone2", + "unicode": "1F3CB-1F3FC", + "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" + }, { "name": "lifter_tone3", "unicode": "1F3CB-1F3FD", "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" }, + { + "name": "weight_lifter_tone3", + "unicode": "1F3CB-1F3FD", + "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" + }, { "name": "lifter_tone4", "unicode": "1F3CB-1F3FE", "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" }, + { + "name": "weight_lifter_tone4", + "unicode": "1F3CB-1F3FE", + "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" + }, { "name": "lifter_tone5", "unicode": "1F3CB-1F3FF", "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" }, + { + "name": "weight_lifter_tone5", + "unicode": "1F3CB-1F3FF", + "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" + }, { "name": "light_check_mark", "unicode": "1F5F8", "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b" }, + { + "name": "light_mark", + "unicode": "1F5F8", + "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b" + }, { "name": "light_rail", "unicode": "1F688", @@ -4729,6 +6594,11 @@ "unicode": "1F981", "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" }, + { + "name": "lion", + "unicode": "1F981", + "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" + }, { "name": "lips", "unicode": "1F444", @@ -4929,6 +6799,11 @@ "unicode": "1F5FA", "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" }, + { + "name": "world_map", + "unicode": "1F5FA", + "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" + }, { "name": "maple_leaf", "unicode": "1F341", @@ -4979,6 +6854,11 @@ "unicode": "1F3C5", "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" }, + { + "name": "sports_medal", + "unicode": "1F3C5", + "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" + }, { "name": "mega", "unicode": "1F4E3", @@ -5004,31 +6884,61 @@ "unicode": "1F918", "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" }, + { + "name": "sign_of_the_horns", + "unicode": "1F918", + "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" + }, { "name": "metal_tone1", "unicode": "1F918-1F3FB", "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" }, + { + "name": "sign_of_the_horns_tone1", + "unicode": "1F918-1F3FB", + "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" + }, { "name": "metal_tone2", "unicode": "1F918-1F3FC", "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" }, + { + "name": "sign_of_the_horns_tone2", + "unicode": "1F918-1F3FC", + "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" + }, { "name": "metal_tone3", "unicode": "1F918-1F3FD", "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" }, + { + "name": "sign_of_the_horns_tone3", + "unicode": "1F918-1F3FD", + "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" + }, { "name": "metal_tone4", "unicode": "1F918-1F3FE", "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" }, + { + "name": "sign_of_the_horns_tone4", + "unicode": "1F918-1F3FE", + "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" + }, { "name": "metal_tone5", "unicode": "1F918-1F3FF", "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" }, + { + "name": "sign_of_the_horns_tone5", + "unicode": "1F918-1F3FF", + "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" + }, { "name": "metro", "unicode": "1F687", @@ -5044,6 +6954,11 @@ "unicode": "1F399", "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" }, + { + "name": "studio_microphone", + "unicode": "1F399", + "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" + }, { "name": "microscope", "unicode": "1F52C", @@ -5054,31 +6969,61 @@ "unicode": "1F595", "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" }, + { + "name": "reversed_hand_with_middle_finger_extended", + "unicode": "1F595", + "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" + }, { "name": "middle_finger_tone1", "unicode": "1F595-1F3FB", "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" }, + { + "name": "reversed_hand_with_middle_finger_extended_tone1", + "unicode": "1F595-1F3FB", + "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" + }, { "name": "middle_finger_tone2", "unicode": "1F595-1F3FC", "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" }, + { + "name": "reversed_hand_with_middle_finger_extended_tone2", + "unicode": "1F595-1F3FC", + "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" + }, { "name": "middle_finger_tone3", "unicode": "1F595-1F3FD", "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" }, + { + "name": "reversed_hand_with_middle_finger_extended_tone3", + "unicode": "1F595-1F3FD", + "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" + }, { "name": "middle_finger_tone4", "unicode": "1F595-1F3FE", "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" }, + { + "name": "reversed_hand_with_middle_finger_extended_tone4", + "unicode": "1F595-1F3FE", + "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" + }, { "name": "middle_finger_tone5", "unicode": "1F595-1F3FF", "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" }, + { + "name": "reversed_hand_with_middle_finger_extended_tone5", + "unicode": "1F595-1F3FF", + "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" + }, { "name": "military_medal", "unicode": "1F396", @@ -5109,6 +7054,11 @@ "unicode": "1F911", "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" }, + { + "name": "money_mouth_face", + "unicode": "1F911", + "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" + }, { "name": "money_with_wings", "unicode": "1F4B8", @@ -5144,11 +7094,21 @@ "unicode": "1F5F1", "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9" }, + { + "name": "lightning_mood_bubble", + "unicode": "1F5F1", + "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9" + }, { "name": "mood_lightning", "unicode": "1F5F2", "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f" }, + { + "name": "lightning_mood", + "unicode": "1F5F2", + "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f" + }, { "name": "mortar_board", "unicode": "1F393", @@ -5169,6 +7129,11 @@ "unicode": "1F3CD", "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" }, + { + "name": "racing_motorcycle", + "unicode": "1F3CD", + "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" + }, { "name": "motorway", "unicode": "1F6E3", @@ -5229,6 +7194,11 @@ "unicode": "1F3D4", "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" }, + { + "name": "snow_capped_mountain", + "unicode": "1F3D4", + "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" + }, { "name": "mouse", "unicode": "1F42D", @@ -5244,11 +7214,21 @@ "unicode": "1F5AF", "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0" }, + { + "name": "one_button_mouse", + "unicode": "1F5AF", + "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0" + }, { "name": "mouse_three_button", "unicode": "1F5B1", "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" }, + { + "name": "three_button_mouse", + "unicode": "1F5B1", + "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" + }, { "name": "movie_camera", "unicode": "1F3A5", @@ -5364,11 +7344,21 @@ "unicode": "1F913", "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" }, + { + "name": "nerd_face", + "unicode": "1F913", + "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" + }, { "name": "network", "unicode": "1F5A7", "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06" }, + { + "name": "three_networked_computers", + "unicode": "1F5A7", + "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06" + }, { "name": "neutral_face", "unicode": "1F610", @@ -5399,6 +7389,11 @@ "unicode": "1F5DE", "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" }, + { + "name": "rolled_up_newspaper", + "unicode": "1F5DE", + "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" + }, { "name": "ng", "unicode": "1F196", @@ -5524,11 +7519,21 @@ "unicode": "1F5C9", "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af" }, + { + "name": "note_page", + "unicode": "1F5C9", + "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af" + }, { "name": "note_empty", "unicode": "1F5C6", "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be" }, + { + "name": "empty_note_page", + "unicode": "1F5C6", + "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be" + }, { "name": "notebook", "unicode": "1F4D3", @@ -5544,16 +7549,31 @@ "unicode": "1F5CA", "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e" }, + { + "name": "note_pad", + "unicode": "1F5CA", + "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e" + }, { "name": "notepad_empty", "unicode": "1F5C7", "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af" }, + { + "name": "empty_note_pad", + "unicode": "1F5C7", + "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af" + }, { "name": "notepad_spiral", "unicode": "1F5D2", "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" }, + { + "name": "spiral_note_pad", + "unicode": "1F5D2", + "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" + }, { "name": "notes", "unicode": "1F3B6", @@ -5599,6 +7619,11 @@ "unicode": "1F6E2", "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" }, + { + "name": "oil_drum", + "unicode": "1F6E2", + "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" + }, { "name": "ok", "unicode": "1F197", @@ -5699,31 +7724,61 @@ "unicode": "1F475", "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" }, + { + "name": "grandma", + "unicode": "1F475", + "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" + }, { "name": "older_woman_tone1", "unicode": "1F475-1F3FB", "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" }, + { + "name": "grandma_tone1", + "unicode": "1F475-1F3FB", + "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" + }, { "name": "older_woman_tone2", "unicode": "1F475-1F3FC", "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" }, + { + "name": "grandma_tone2", + "unicode": "1F475-1F3FC", + "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" + }, { "name": "older_woman_tone3", "unicode": "1F475-1F3FD", "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" }, + { + "name": "grandma_tone3", + "unicode": "1F475-1F3FD", + "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" + }, { "name": "older_woman_tone4", "unicode": "1F475-1F3FE", "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" }, + { + "name": "grandma_tone4", + "unicode": "1F475-1F3FE", + "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" + }, { "name": "older_woman_tone5", "unicode": "1F475-1F3FF", "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" }, + { + "name": "grandma_tone5", + "unicode": "1F475-1F3FF", + "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" + }, { "name": "om_symbol", "unicode": "1F549", @@ -5809,6 +7864,11 @@ "unicode": "1F5B8", "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d" }, + { + "name": "optical_disc_icon", + "unicode": "1F5B8", + "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d" + }, { "name": "orange_book", "unicode": "1F4D9", @@ -5864,6 +7924,11 @@ "unicode": "1F58C", "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" }, + { + "name": "lower_left_paintbrush", + "unicode": "1F58C", + "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" + }, { "name": "palm_tree", "unicode": "1F334", @@ -5884,11 +7949,21 @@ "unicode": "1F587", "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" }, + { + "name": "linked_paperclips", + "unicode": "1F587", + "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" + }, { "name": "park", "unicode": "1F3DE", "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" }, + { + "name": "national_park", + "unicode": "1F3DE", + "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" + }, { "name": "parking", "unicode": "1F17F", @@ -5914,11 +7989,21 @@ "unicode": "23F8", "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" }, + { + "name": "double_vertical_bar", + "unicode": "23F8", + "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" + }, { "name": "peace", "unicode": "262E", "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" }, + { + "name": "peace_symbol", + "unicode": "262E", + "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" + }, { "name": "peach", "unicode": "1F351", @@ -5934,16 +8019,31 @@ "unicode": "1F58A", "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" }, + { + "name": "lower_left_ballpoint_pen", + "unicode": "1F58A", + "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" + }, { "name": "pen_fountain", "unicode": "1F58B", "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" }, + { + "name": "lower_left_fountain_pen", + "unicode": "1F58B", + "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" + }, { "name": "pencil", "unicode": "1F4DD", "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" }, + { + "name": "memo", + "unicode": "1F4DD", + "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" + }, { "name": "pencil2", "unicode": "270F", @@ -5954,6 +8054,11 @@ "unicode": "1F589", "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c" }, + { + "name": "lower_left_pencil", + "unicode": "1F589", + "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c" + }, { "name": "penguin", "unicode": "1F427", @@ -5964,11 +8069,21 @@ "unicode": "1F3F2", "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269" }, + { + "name": "black_pennant", + "unicode": "1F3F2", + "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269" + }, { "name": "pennant_white", "unicode": "1F3F1", "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0" }, + { + "name": "white_pennant", + "unicode": "1F3F1", + "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0" + }, { "name": "pensive", "unicode": "1F614", @@ -6109,11 +8224,21 @@ "unicode": "1F3D3", "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" }, + { + "name": "table_tennis", + "unicode": "1F3D3", + "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" + }, { "name": "piracy", "unicode": "1F572", "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9" }, + { + "name": "no_piracy", + "unicode": "1F572", + "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9" + }, { "name": "pisces", "unicode": "2653", @@ -6129,6 +8254,11 @@ "unicode": "1F6D0", "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" }, + { + "name": "worship_symbol", + "unicode": "1F6D0", + "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" + }, { "name": "play_pause", "unicode": "23EF", @@ -6299,6 +8429,21 @@ "unicode": "1F4A9", "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" }, + { + "name": "shit", + "unicode": "1F4A9", + "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + }, + { + "name": "hankey", + "unicode": "1F4A9", + "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + }, + { + "name": "poo", + "unicode": "1F4A9", + "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + }, { "name": "popcorn", "unicode": "1F37F", @@ -6419,11 +8564,21 @@ "unicode": "1F6C7", "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f" }, + { + "name": "prohibited_sign", + "unicode": "1F6C7", + "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f" + }, { "name": "projector", "unicode": "1F4FD", "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" }, + { + "name": "film_projector", + "unicode": "1F4FD", + "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" + }, { "name": "punch", "unicode": "1F44A", @@ -6499,6 +8654,11 @@ "unicode": "1F3CE", "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" }, + { + "name": "racing_car", + "unicode": "1F3CE", + "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" + }, { "name": "racehorse", "unicode": "1F40E", @@ -6519,6 +8679,11 @@ "unicode": "2622", "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" }, + { + "name": "radioactive_sign", + "unicode": "2622", + "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" + }, { "name": "rage", "unicode": "1F621", @@ -6534,6 +8699,11 @@ "unicode": "1F6E4", "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" }, + { + "name": "railroad_track", + "unicode": "1F6E4", + "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" + }, { "name": "rainbow", "unicode": "1F308", @@ -6744,11 +8914,21 @@ "unicode": "1F569", "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375" }, + { + "name": "right_speaker_with_one_sound_wave", + "unicode": "1F569", + "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375" + }, { "name": "right_speaker_three", "unicode": "1F56A", "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80" }, + { + "name": "right_speaker_with_three_sound_waves", + "unicode": "1F56A", + "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80" + }, { "name": "ring", "unicode": "1F48D", @@ -6764,6 +8944,11 @@ "unicode": "1F916", "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" }, + { + "name": "robot_face", + "unicode": "1F916", + "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" + }, { "name": "rocket", "unicode": "1F680", @@ -6779,6 +8964,11 @@ "unicode": "1F644", "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" }, + { + "name": "face_with_rolling_eyes", + "unicode": "1F644", + "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" + }, { "name": "rooster", "unicode": "1F413", @@ -7099,11 +9289,21 @@ "unicode": "1F480", "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" }, + { + "name": "skeleton", + "unicode": "1F480", + "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" + }, { "name": "skull_crossbones", "unicode": "2620", "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" }, + { + "name": "skull_and_crossbones", + "unicode": "2620", + "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" + }, { "name": "sleeping", "unicode": "1F634", @@ -7124,11 +9324,21 @@ "unicode": "1F641", "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" }, + { + "name": "slightly_frowning_face", + "unicode": "1F641", + "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" + }, { "name": "slight_smile", "unicode": "1F642", "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" }, + { + "name": "slightly_smiling_face", + "unicode": "1F642", + "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" + }, { "name": "slot_machine", "unicode": "1F3B0", @@ -7299,6 +9509,11 @@ "unicode": "1F5E3", "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" }, + { + "name": "speaking_head_in_silhouette", + "unicode": "1F5E3", + "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" + }, { "name": "speech_balloon", "unicode": "1F4AC", @@ -7309,21 +9524,41 @@ "unicode": "1F5E8", "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" }, + { + "name": "left_speech_bubble", + "unicode": "1F5E8", + "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" + }, { "name": "speech_right", "unicode": "1F5E9", "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a" }, + { + "name": "right_speech_bubble", + "unicode": "1F5E9", + "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a" + }, { "name": "speech_three", "unicode": "1F5EB", "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9" }, + { + "name": "three_speech_bubbles", + "unicode": "1F5EB", + "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9" + }, { "name": "speech_two", "unicode": "1F5EA", "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983" }, + { + "name": "two_speech_bubbles", + "unicode": "1F5EA", + "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983" + }, { "name": "speedboat", "unicode": "1F6A4", @@ -7344,31 +9579,61 @@ "unicode": "1F575", "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" }, + { + "name": "sleuth_or_spy", + "unicode": "1F575", + "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" + }, { "name": "spy_tone1", "unicode": "1F575-1F3FB", "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" }, + { + "name": "sleuth_or_spy_tone1", + "unicode": "1F575-1F3FB", + "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" + }, { "name": "spy_tone2", "unicode": "1F575-1F3FC", "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" }, + { + "name": "sleuth_or_spy_tone2", + "unicode": "1F575-1F3FC", + "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" + }, { "name": "spy_tone3", "unicode": "1F575-1F3FD", "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" }, + { + "name": "sleuth_or_spy_tone3", + "unicode": "1F575-1F3FD", + "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" + }, { "name": "spy_tone4", "unicode": "1F575-1F3FE", "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" }, + { + "name": "sleuth_or_spy_tone4", + "unicode": "1F575-1F3FE", + "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" + }, { "name": "spy_tone5", "unicode": "1F575-1F3FF", "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" }, + { + "name": "sleuth_or_spy_tone5", + "unicode": "1F575-1F3FF", + "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" + }, { "name": "stadium", "unicode": "1F3DF", @@ -7419,6 +9684,11 @@ "unicode": "1F4FE", "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5" }, + { + "name": "portable_stereo", + "unicode": "1F4FE", + "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5" + }, { "name": "stew", "unicode": "1F372", @@ -7644,6 +9914,11 @@ "unicode": "1F57F", "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db" }, + { + "name": "black_touchtone_telephone", + "unicode": "1F57F", + "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db" + }, { "name": "telephone_receiver", "unicode": "1F4DE", @@ -7654,6 +9929,11 @@ "unicode": "1F57E", "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581" }, + { + "name": "white_touchtone_telephone", + "unicode": "1F57E", + "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581" + }, { "name": "telescope", "unicode": "1F52D", @@ -7684,11 +9964,21 @@ "unicode": "1F912", "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" }, + { + "name": "face_with_thermometer", + "unicode": "1F912", + "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" + }, { "name": "thinking", "unicode": "1F914", "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" }, + { + "name": "thinking_face", + "unicode": "1F914", + "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" + }, { "name": "thought_balloon", "unicode": "1F4AD", @@ -7699,11 +9989,21 @@ "unicode": "1F5EC", "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46" }, + { + "name": "left_thought_bubble", + "unicode": "1F5EC", + "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46" + }, { "name": "thought_right", "unicode": "1F5ED", "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae" }, + { + "name": "right_thought_bubble", + "unicode": "1F5ED", + "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae" + }, { "name": "three", "unicode": "0033-20E3", @@ -7714,76 +10014,151 @@ "unicode": "1F593", "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958" }, + { + "name": "reversed_thumbs_down_sign", + "unicode": "1F593", + "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958" + }, { "name": "thumbs_up_reverse", "unicode": "1F592", "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837" }, + { + "name": "reversed_thumbs_up_sign", + "unicode": "1F592", + "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837" + }, { "name": "thumbsdown", "unicode": "1F44E", "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" }, + { + "name": "-1", + "unicode": "1F44E", + "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" + }, { "name": "thumbsdown_tone1", "unicode": "1F44E-1F3FB", "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" }, + { + "name": "-1_tone1", + "unicode": "1F44E-1F3FB", + "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" + }, { "name": "thumbsdown_tone2", "unicode": "1F44E-1F3FC", "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" }, + { + "name": "-1_tone2", + "unicode": "1F44E-1F3FC", + "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" + }, { "name": "thumbsdown_tone3", "unicode": "1F44E-1F3FD", "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" }, + { + "name": "-1_tone3", + "unicode": "1F44E-1F3FD", + "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" + }, { "name": "thumbsdown_tone4", "unicode": "1F44E-1F3FE", "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" }, + { + "name": "-1_tone4", + "unicode": "1F44E-1F3FE", + "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" + }, { "name": "thumbsdown_tone5", "unicode": "1F44E-1F3FF", "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" }, + { + "name": "-1_tone5", + "unicode": "1F44E-1F3FF", + "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" + }, { "name": "thumbsup", "unicode": "1F44D", "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" }, + { + "name": "+1", + "unicode": "1F44D", + "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" + }, { "name": "thumbsup_tone1", "unicode": "1F44D-1F3FB", "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" }, + { + "name": "+1_tone1", + "unicode": "1F44D-1F3FB", + "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" + }, { "name": "thumbsup_tone2", "unicode": "1F44D-1F3FC", "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" }, + { + "name": "+1_tone2", + "unicode": "1F44D-1F3FC", + "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" + }, { "name": "thumbsup_tone3", "unicode": "1F44D-1F3FD", "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" }, + { + "name": "+1_tone3", + "unicode": "1F44D-1F3FD", + "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" + }, { "name": "thumbsup_tone4", "unicode": "1F44D-1F3FE", "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" }, + { + "name": "+1_tone4", + "unicode": "1F44D-1F3FE", + "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" + }, { "name": "thumbsup_tone5", "unicode": "1F44D-1F3FF", "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" }, + { + "name": "+1_tone5", + "unicode": "1F44D-1F3FF", + "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" + }, { "name": "thunder_cloud_rain", "unicode": "26C8", "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" }, + { + "name": "thunder_cloud_and_rain", + "unicode": "26C8", + "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" + }, { "name": "ticket", "unicode": "1F3AB", @@ -7794,6 +10169,11 @@ "unicode": "1F39F", "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" }, + { + "name": "admission_tickets", + "unicode": "1F39F", + "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" + }, { "name": "tiger", "unicode": "1F42F", @@ -7809,6 +10189,11 @@ "unicode": "23F2", "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" }, + { + "name": "timer_clock", + "unicode": "23F2", + "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" + }, { "name": "tired_face", "unicode": "1F62B", @@ -7869,6 +10254,11 @@ "unicode": "1F6E0", "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" }, + { + "name": "hammer_and_wrench", + "unicode": "1F6E0", + "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" + }, { "name": "top", "unicode": "1F51D", @@ -7884,11 +10274,21 @@ "unicode": "23ED", "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" }, + { + "name": "next_track", + "unicode": "23ED", + "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" + }, { "name": "track_previous", "unicode": "23EE", "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" }, + { + "name": "previous_track", + "unicode": "23EE", + "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" + }, { "name": "trackball", "unicode": "1F5B2", @@ -7919,6 +10319,11 @@ "unicode": "1F6F2", "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c" }, + { + "name": "diesel_locomotive", + "unicode": "1F6F2", + "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c" + }, { "name": "tram", "unicode": "1F68A", @@ -7929,6 +10334,11 @@ "unicode": "1F6C6", "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5" }, + { + "name": "triangle_with_rounded_corners", + "unicode": "1F6C6", + "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5" + }, { "name": "triangular_flag_on_post", "unicode": "1F6A9", @@ -7994,6 +10404,11 @@ "unicode": "1F58F", "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246" }, + { + "name": "turned_ok_hand_sign", + "unicode": "1F58F", + "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246" + }, { "name": "turtle", "unicode": "1F422", @@ -8109,6 +10524,11 @@ "unicode": "1F984", "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" }, + { + "name": "unicorn_face", + "unicode": "1F984", + "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" + }, { "name": "unlock", "unicode": "1F513", @@ -8124,11 +10544,21 @@ "unicode": "1F643", "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" }, + { + "name": "upside_down_face", + "unicode": "1F643", + "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" + }, { "name": "urn", "unicode": "26B1", "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" }, + { + "name": "funeral_urn", + "unicode": "26B1", + "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" + }, { "name": "v", "unicode": "270C", @@ -8214,31 +10644,61 @@ "unicode": "1F596", "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers", + "unicode": "1F596", + "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" + }, { "name": "vulcan_tone1", "unicode": "1F596-1F3FB", "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone1", + "unicode": "1F596-1F3FB", + "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" + }, { "name": "vulcan_tone2", "unicode": "1F596-1F3FC", "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone2", + "unicode": "1F596-1F3FC", + "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" + }, { "name": "vulcan_tone3", "unicode": "1F596-1F3FD", "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone3", + "unicode": "1F596-1F3FD", + "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" + }, { "name": "vulcan_tone4", "unicode": "1F596-1F3FE", "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone4", + "unicode": "1F596-1F3FE", + "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" + }, { "name": "vulcan_tone5", "unicode": "1F596-1F3FF", "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" }, + { + "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone5", + "unicode": "1F596-1F3FF", + "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" + }, { "name": "walking", "unicode": "1F6B6", @@ -8429,16 +10889,31 @@ "unicode": "1F325", "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" }, + { + "name": "white_sun_behind_cloud", + "unicode": "1F325", + "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" + }, { "name": "white_sun_rain_cloud", "unicode": "1F326", "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" }, + { + "name": "white_sun_behind_cloud_with_rain", + "unicode": "1F326", + "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" + }, { "name": "white_sun_small_cloud", "unicode": "1F324", "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" }, + { + "name": "white_sun_with_small_cloud", + "unicode": "1F324", + "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" + }, { "name": "wind_blowing_face", "unicode": "1F32C", @@ -8524,6 +10999,11 @@ "unicode": "1F58E", "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb" }, + { + "name": "left_writing_hand", + "unicode": "1F58E", + "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb" + }, { "name": "writing_hand_tone1", "unicode": "270D-1F3FB", @@ -8589,6 +11069,11 @@ "unicode": "1F910", "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" }, + { + "name": "zipper_mouth_face", + "unicode": "1F910", + "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" + }, { "name": "zzz", "unicode": "1F4A4", diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index 7ec00a898fd..030ee8bafcb 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -5,12 +5,23 @@ namespace :gemojione do require 'json' dir = Gemojione.index.images_path + digests = [] + aliases = Hash.new { |hash, key| hash[key] = [] } + aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json') - digests = AwardEmoji.emojis.map do |name, emoji_hash| + JSON.parse(File.read(aliases_path)).each do |alias_name, real_name| + aliases[real_name] << alias_name + end + + AwardEmoji.emojis.map do |name, emoji_hash| fpath = File.join(dir, "#{emoji_hash['unicode']}.png") digest = Digest::SHA256.file(fpath).hexdigest - { name: name, unicode: emoji_hash['unicode'], digest: digest } + digests << { name: name, unicode: emoji_hash['unicode'], digest: digest } + + aliases[name].each do |alias_name| + digests << { name: alias_name, unicode: emoji_hash['unicode'], digest: digest } + end end out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') -- cgit v1.2.1 From 1647e3e8849991629f5ebf1a11f9ca06e8f741ea Mon Sep 17 00:00:00 2001 From: connorshea Date: Sat, 9 Apr 2016 08:29:57 -0600 Subject: Upgrade Sprockets from 3.3.5 to 3.6.0. Sprockets 3.5.0 reintroduces GZIP-ing and 3.6.0 includes performance improvements. Changelog: https://github.com/rails/sprockets/blob/3.x/CHANGELOG.md Resolves #14344. --- Gemfile | 2 +- Gemfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 3f29df0d81d..298cfd260ba 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'responders', '~> 2.0' # Specify a sprockets version due to increased performance # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 -gem 'sprockets', '~> 3.3.5' +gem 'sprockets', '~> 3.6.0' # Default values for AR models gem "default_value_for", "~> 3.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index 7eb4080183d..28b71ac7bc8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -776,7 +776,8 @@ GEM spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (3.3.5) + sprockets (3.6.0) + concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.0.4) actionpack (>= 4.0) @@ -1033,7 +1034,7 @@ DEPENDENCIES spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.0.0) spring-commands-teaspoon (~> 0.0.2) - sprockets (~> 3.3.5) + sprockets (~> 3.6.0) state_machines-activerecord (~> 0.3.0) task_list (~> 1.0.2) teaspoon (~> 1.1.0) -- cgit v1.2.1 From fb8afae1863c5daa6b1fbce04755fa573302b607 Mon Sep 17 00:00:00 2001 From: connorshea Date: Sat, 9 Apr 2016 11:28:47 -0600 Subject: Apparently I missed updating the schema.rb when removing the Twitter "Congrats" button From commit 85cc1729596ac1e5b31d8cfa1daa07477db6033d --- db/schema.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index e63e22ce864..ec235c19131 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331133914) do +ActiveRecord::Schema.define(version: 20160331223143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -44,7 +44,6 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.datetime "updated_at" t.string "home_page_url" t.integer "default_branch_protection", default: 2 - t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" t.boolean "version_check_enabled", default: true t.integer "max_attachment_size", default: 10, null: false -- cgit v1.2.1 From ca823065a905f8c19b2003a67e79831ba1ceff2e Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Sat, 9 Apr 2016 20:51:15 +0000 Subject: actually enable artifacts --- doc/ci/build_artifacts/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md index 71db5aa5dc8..bd26da2af6d 100644 --- a/doc/ci/build_artifacts/README.md +++ b/doc/ci/build_artifacts/README.md @@ -31,7 +31,7 @@ To disable it site-wide, follow the steps below. 1. Edit `/etc/gitlab/gitlab.rb` and add the following line: ```ruby - gitlab_rails['artifacts_enabled'] = false + gitlab_rails['artifacts_enabled'] = true ``` 1. Save the file and [reconfigure GitLab][] for the changes to take effect. @@ -44,7 +44,7 @@ To disable it site-wide, follow the steps below. ```yaml artifacts: - enabled: false + enabled: true ``` 1. Save the file and [restart GitLab][] for the changes to take effect. -- cgit v1.2.1 From 44d9f52fd9dba09c352f64bb742291e5adab423a Mon Sep 17 00:00:00 2001 From: connorshea Date: Sat, 9 Apr 2016 16:31:41 -0600 Subject: Enable asset compression in production. `config.assets.compress` needed to be explicitly enabled. Follow-up to !3544. Resolves #14344. --- CHANGELOG | 1 + config/environments/production.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c25ee3c71d3..b5dcc8bc8a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) + - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) diff --git a/config/environments/production.rb b/config/environments/production.rb index 909526605a1..a9d8ac4b6d4 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -21,6 +21,9 @@ Rails.application.configure do # Generate digests for assets URLs config.assets.digest = true + # Enable compression of compiled assets using gzip. + config.assets.compress = true + # Defaults to nil and saved in location specified by config.assets.prefix # config.assets.manifest = YOUR_PATH -- cgit v1.2.1 From b95c9217b75d84412d9a3e4f52f31158329755f4 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Fri, 1 Apr 2016 17:27:28 +0100 Subject: prevent users from being able to both upvote and downvote --- app/assets/javascripts/awards_handler.coffee | 15 +++++++++++++-- app/views/votes/_votes_block.html.haml | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 6a670d5e887..af4462ece38 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -22,8 +22,19 @@ class @AwardsHandler emoji = $(this) .find(".icon") .data "emoji" + + if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" + awards_handler.addAward "thumbsdown" + + else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" + awards_handler.addAward "thumbsup" + awards_handler.addAward emoji + didUserClickEmoji: (that, emoji) -> + if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title") + $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1 + showEmojiMenu: -> if $(".emoji-menu").length if $(".emoji-menu").is ".is-visible" @@ -105,7 +116,7 @@ class @AwardsHandler if origTitle authors = origTitle.split(', ') authors.push("me") - award_block.attr("title", authors.join(", ")) + award_block.attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) resetTooltip: (award) -> @@ -122,7 +133,7 @@ class @AwardsHandler nodes = [] nodes.push( - "" diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 49cfcd53d74..dc249155b92 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,6 +1,6 @@ .awards.votes-block - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}} + %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}} = emoji_icon(emoji, sprite: false) %span.award-control-text.js-counter = notes.count -- cgit v1.2.1 From 91905ae3f5a9f755258b770482cd45916ee47fe4 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Tue, 5 Apr 2016 23:26:17 +0100 Subject: write test to prevent reoccurence of issue --- spec/features/issues/award_emoji_spec.rb | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 spec/features/issues/award_emoji_spec.rb diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb new file mode 100644 index 00000000000..41af789aae2 --- /dev/null +++ b/spec/features/issues/award_emoji_spec.rb @@ -0,0 +1,64 @@ +require 'rails_helper' + +describe 'Awards Emoji', feature: true do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as(user) + end + + describe 'Click award emoji from issue#show' do + let!(:issue) do + create(:issue, + author: @user, + assignee: @user, + project: project) + end + + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'should increment the thumbsdown emoji', js: true do + find('[data-emoji="thumbsdown"]').click + sleep 2 + expect(thumbsdown_emoji).to have_text("1") + end + + context 'click the thumbsup emoji' do + + it 'should increment the thumbsup emoji', js: true do + find('[data-emoji="thumbsup"]').click + sleep 2 + expect(thumbsup_emoji).to have_text("1") + end + + it 'should decrement the thumbsdown emoji', js: true do + expect(thumbsdown_emoji).to have_text("0") + end + end + + context 'click the thumbsdown emoji' do + + it 'should increment the thumbsdown emoji', js: true do + find('[data-emoji="thumbsdown"]').click + sleep 2 + expect(thumbsdown_emoji).to have_text("1") + end + + it 'should decrement the thumbsup emoji', js: true do + expect(thumbsup_emoji).to have_text("0") + end + end + end + + def thumbsup_emoji + page.all('span.js-counter').first + end + + def thumbsdown_emoji + page.all('span.js-counter').last + end +end -- cgit v1.2.1 From 2378ec0d635a4dbe0d1716a86bb3e3d038518e94 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 19:32:22 -0400 Subject: Add a `program` tag to Sentry Raven context This will let us filter errors by the program environment in which they were encountered. Source: http://stackoverflow.com/a/28370539/223897 Closes #15092 --- app/controllers/application_controller.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c81cb85dc1b..97d53acde94 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -47,6 +47,16 @@ class ApplicationController < ActionController::Base email: current_user.email, username: current_user.username, ) + + Raven.tags_context(program: sentry_program_context) + end + end + + def sentry_program_context + if Sidekiq.server? + 'sidekiq' + else + 'rails' end end -- cgit v1.2.1 From f03b5c4fde1bd6eb415457e60d3c79fbd9c2e4ee Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 20:02:36 -0400 Subject: Remove tab stop from "Write", "Preview", "Go full screen" links Closes #15089 --- app/views/projects/_md_preview.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 4920910fee1..7a78d61a611 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -2,13 +2,13 @@ .md-header %ul.nav-links %li.active - %a.js-md-write-button{ href: "#md-write-holder" } + %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } Write %li - %a.js-md-preview-button{ href: "#md-preview-holder" } + %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } Preview %li.pull-right - %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button' } + %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } Go full screen .md-write-holder -- cgit v1.2.1 From 98f536f1693d59942d91e18ece499bb35ec55259 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sat, 9 Apr 2016 20:09:07 -0400 Subject: Everything on one page. --- doc/integration/README.md | 17 +++-------------- doc/project_services/project_services.md | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/integration/README.md b/doc/integration/README.md index 7c8f785a61f..6fe04aa2a06 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -19,26 +19,15 @@ See the documentation below for details on how to configure these services. GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. +[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html + + ## Project services Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Pivotal Tracker, and Slack are available in the form of a [Project Service][]. -You can find these within GitLab in the Services page under Project Settings if -you are at least a master on the project. -Project Services are a bit like plugins in that they allow a lot of freedom in -adding functionality to GitLab. For example there is also a service that can -send an email every time someone pushes new commits. -Because GitLab is open source we can ship with the code and tests for all -plugins. This allows the community to keep the plugins up to date so that they -always work in newer GitLab versions. - -For an overview of what projects services are available without logging in, -please see the [project_services directory][projects-code]. - -[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html [Project Service]: ../project_services/project_services.md -[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services ## SSL certificate errors diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index 3fea2cff0b9..a5af620d9be 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -1,7 +1,24 @@ # Project Services Project services allow you to integrate GitLab with other applications. Below -is list of the currently supported ones. Click on the service links to see +is list of the currently supported ones. + +You can find these within GitLab in the Services page under Project Settings if +you are at least a master on the project. +Project Services are a bit like plugins in that they allow a lot of freedom in +adding functionality to GitLab. For example there is also a service that can +send an email every time someone pushes new commits. + +Because GitLab is open source we can ship with the code and tests for all +plugins. This allows the community to keep the plugins up to date so that they +always work in newer GitLab versions. + +For an overview of what projects services are available without logging in, +please see the [project_services directory][projects-code]. + +[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services + +Click on the service links to see further configuration instructions and details. Contributions are welcome. ## Services -- cgit v1.2.1 From f833726ec048b0ddf85fa8d194956f71c6334785 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 20:43:59 -0400 Subject: Remove `random_markdown_tip` helper Only usage got removed in !3442 --- app/helpers/gitlab_markdown_helper.rb | 23 ----------------------- spec/helpers/gitlab_markdown_helper_spec.rb | 7 ------- 2 files changed, 30 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 2f760af02fd..3a45205563e 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -116,29 +116,6 @@ module GitlabMarkdownHelper end end - MARKDOWN_TIPS = [ - "End a line with two or more spaces for a line-break, or soft-return", - "Inline code can be denoted by `surrounding it with backticks`", - "Blocks of code can be denoted by three backticks ``` or four leading spaces", - "Emoji can be added by :emoji_name:, for example :thumbsup:", - "Notify other participants using @user_name", - "Notify a specific group using @group_name", - "Notify the entire team using @all", - "Reference an issue using a hash, for example issue #123", - "Reference a merge request using an exclamation point, for example MR !123", - "Italicize words or phrases using *asterisks* or _underscores_", - "Bold words or phrases using **double asterisks** or __double underscores__", - "Strikethrough words or phrases using ~~two tildes~~", - "Make a bulleted list using + pluses, - minuses, or * asterisks", - "Denote blockquotes using > at the beginning of a line", - "Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___" - ].freeze - - # Returns a random markdown tip for use as a textarea placeholder - def random_markdown_tip - MARKDOWN_TIPS.sample - end - private # Return +text+, truncated to +max_chars+ characters, excluding any HTML diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 9adcd916ced..13de88e2f21 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -150,13 +150,6 @@ describe GitlabMarkdownHelper do end end - describe 'random_markdown_tip' do - it 'returns a random Markdown tip' do - stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip']) - expect(random_markdown_tip).to eq 'Random tip' - end - end - describe '#first_line_in_markdown' do let(:text) { "@#{user.username}, can you look at this?\nHello world\n"} -- cgit v1.2.1 From 66102e5017c2962d1fd1ffcbef09e605033643ba Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 9 Apr 2016 18:34:00 -0700 Subject: Use raw SQL instead of Rails models in 20130403003950 migration Closes gitlab-org/gitlab-development-kit#109 Closes https://github.com/gitlabhq/gitlabhq/issues/10123 --- ...130403003950_add_last_activity_column_into_project.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/db/migrate/20130403003950_add_last_activity_column_into_project.rb b/db/migrate/20130403003950_add_last_activity_column_into_project.rb index 2a036bd9993..85e31608d79 100644 --- a/db/migrate/20130403003950_add_last_activity_column_into_project.rb +++ b/db/migrate/20130403003950_add_last_activity_column_into_project.rb @@ -3,14 +3,16 @@ class AddLastActivityColumnIntoProject < ActiveRecord::Migration add_column :projects, :last_activity_at, :datetime add_index :projects, :last_activity_at - Project.find_each do |project| - last_activity_date = if project.last_activity - project.last_activity.created_at - else - project.updated_at - end + select_all('SELECT id, updated_at FROM projects').each do |project| + project_id = project['id'] + update_date = project['updated_at'] + event = select_one("SELECT created_at FROM events WHERE project_id = #{project_id} ORDER BY created_at DESC LIMIT 1") - project.update_attribute(:last_activity_at, last_activity_date) + if event && event['created_at'] + update_date = event['created_at'] + end + + execute("UPDATE projects SET last_activity_at = '#{update_date}' WHERE id = #{project_id}") end end -- cgit v1.2.1 From c7ec5929b1f580e7078d11774df2e8c0d1abbe03 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 21:49:47 -0400 Subject: First pass at a Code Review guide Largely borrowed from thoughtbot's code review guide, so attribution is included. [ci skip] --- doc/development/README.md | 2 ++ doc/development/code_review.md | 75 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 doc/development/code_review.md diff --git a/doc/development/README.md b/doc/development/README.md index 2f4e7845ccc..3f3ef068f96 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -2,6 +2,8 @@ - [Architecture](architecture.md) of GitLab - [CI setup](ci_setup.md) for testing GitLab +- [Code review guidelines](code_review.md) for reviewing code and having code + reviewed. - [Gotchas](gotchas.md) to avoid - [How to dump production data to staging](db_dump.md) - [Instrumentation](instrumentation.md) diff --git a/doc/development/code_review.md b/doc/development/code_review.md new file mode 100644 index 00000000000..ab4236404c6 --- /dev/null +++ b/doc/development/code_review.md @@ -0,0 +1,75 @@ +# Code Review Guidelines + +This guide contains advice and best practices for performing code review, and +having your code reviewed. + +All merge requests for GitLab CE and EE, whether written by a GitLab team member +or a volunteer contributor, must go through a code review process to ensure the +code is effective, understandable, and maintainable. + +Any developer can, and is encouraged to, perform code review on merge requests +of colleagues and contributors. However, the final decision to accept a merge +request is up to one of our merge request "endbosses", denoted on the +[team page](https://about.gitlab.com/team). + +## Everyone + +- Accept that many programming decisions are opinions. Discuss tradeoffs, which + you prefer, and reach a resolution quickly. +- Ask questions; don't make demands. ("What do you think about naming this + `:user_id`?") +- Ask for clarification. ("I didn't understand. Can you clarify?") +- Avoid selective ownership of code. ("mine", "not mine", "yours") +- Avoid using terms that could be seen as referring to personal traits. ("dumb", + "stupid"). Assume everyone is attractive, intelligent, and well-meaning. +- Be explicit. Remember people don't always understand your intentions online. +- Be humble. ("I'm not sure - let's look it up.") +- Don't use hyperbole. ("always", "never", "endlessly", "nothing") +- Consider one-on-one chats or video calls if there are too many "I didn't + understand" or "Alternative solution:" comments. Post a follow-up comment + summarizing one-on-one discussion. + +## Having your code reviewed + +- The first reviewer of your code is _you_. Before you perform that first push + of your shiny new branch, read through the entire diff. Does it make sense? + Did you include something unrelated to the overall purpose of the changes? Did + you forget to remove any debugging code? +- Be grateful for the reviewer's suggestions. ("Good call. I'll make that + change.") +- Don't take it personally. The review is of the code, not of you. +- Explain why the code exists. ("It's like that because of these reasons. Would + it be more clear if I rename this class/file/method/variable?") +- Extract unrelated changes and refactorings into future merge requests/issues. +- Seek to understand the reviewer's perspective. +- Try to respond to every comment. +- Push commits based on earlier rounds of feedback as isolated commits to the + branch. Do not squash until the branch is ready to merge. Reviewers should be + able to read individual updates based on their earlier feedback. + +## Reviewing code + +Understand why the change is necessary (fixes a bug, improves the user +experience, refactors the existing code). Then: + +- Communicate which ideas you feel strongly about and those you don't. +- Identify ways to simplify the code while still solving the problem. +- Offer alternative implementations, but assume the author already considered + them. ("What do you think about using a custom validator here?") +- Seek to understand the author's perspective. +- If you don't understand a piece of code, _say so_. There's a good chance + someone else would be confused by it as well. +- After a round of line notes, it can be helpful to post a summary note such as + "LGTM :thumbsup:", or "Just a couple things to address." +- Avoid accepting a merge request before the build succeeds ("Merge when build + succeeds" is fine). + +## Credits + +Largely based on the [thoughtbot code review guide]. + +[thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review + +--- + +[Return to Development documentation](README.md) -- cgit v1.2.1 From 8536dedbb1fd9154485a001d16e1414c6e3e8e36 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 10 Apr 2016 06:36:14 -0700 Subject: Fix seed_fu failure with inserting milestones into test DB Milestones were not being saved due to "invalid state" validation errors --- db/fixtures/development/07_milestones.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb index e028ac82ba3..540e4e68259 100644 --- a/db/fixtures/development/07_milestones.rb +++ b/db/fixtures/development/07_milestones.rb @@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do milestone_params = { title: "v#{i}.0", description: FFaker::Lorem.sentence, - state: ['opened', 'closed'].sample, + state: [:active, :closed].sample, } milestone = Milestones::CreateService.new( -- cgit v1.2.1 From 94b48d95570cd7c0b8dbd11eb4edc957ad5c7443 Mon Sep 17 00:00:00 2001 From: connorshea Date: Sun, 10 Apr 2016 14:36:31 -0600 Subject: Upgrade premailer-rails from 1.9.0 to 1.9.2 Includes compatibility with Rails 5. Changelog: https://github.com/fphilipe/premailer-rails/blob/master/CHANGELOG.md#v19 2 Working towards #14286. --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 28b71ac7bc8..df0d848af4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,7 +145,7 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) creole (0.5.0) - css_parser (1.3.7) + css_parser (1.4.1) addressable d3_rails (3.5.11) railties (>= 3.1.0) @@ -559,8 +559,8 @@ GEM premailer (1.8.6) css_parser (>= 1.3.6) htmlentities (>= 4.0.0) - premailer-rails (1.9.0) - actionmailer (>= 3, < 5) + premailer-rails (1.9.2) + actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) pry (0.10.3) coderay (~> 1.1.0) -- cgit v1.2.1 From 1e99e56202c0c96d4a70fba95e04b4f41d24327a Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Sun, 10 Apr 2016 21:14:40 +0000 Subject: Make the purpose of artifact config options clearer --- doc/ci/build_artifacts/README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md index bd26da2af6d..a21cd129862 100644 --- a/doc/ci/build_artifacts/README.md +++ b/doc/ci/build_artifacts/README.md @@ -1,7 +1,8 @@ # Introduction to build artifacts Artifacts is a list of files and directories which are attached to a build -after it completes successfully. +after it completes successfully. Artificats is enabled by default. _If you are searching for ways to use artifacts, jump to +[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._ Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by GitLab Runner are uploaded to GitLab and are downloadable as a single archive @@ -16,13 +17,9 @@ The artifacts browser will be available only for new artifacts that are sent to GitLab using GitLab Runner version 1.0 and up. It will not be possible to browse old artifacts already uploaded to GitLab. -## Enabling build artifacts - -_If you are searching for ways to use artifacts, jump to -[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._ +## Disabling build artifacts -The artifacts feature is enabled by default in all GitLab installations. -To disable it site-wide, follow the steps below. +To disable artifacts site-wide, follow the steps below. --- @@ -31,7 +28,7 @@ To disable it site-wide, follow the steps below. 1. Edit `/etc/gitlab/gitlab.rb` and add the following line: ```ruby - gitlab_rails['artifacts_enabled'] = true + gitlab_rails['artifacts_enabled'] = false ``` 1. Save the file and [reconfigure GitLab][] for the changes to take effect. @@ -44,7 +41,7 @@ To disable it site-wide, follow the steps below. ```yaml artifacts: - enabled: true + enabled: false ``` 1. Save the file and [restart GitLab][] for the changes to take effect. -- cgit v1.2.1 From a7e1638b430627133a33986b8a8283891431567a Mon Sep 17 00:00:00 2001 From: connorshea Date: Sun, 10 Apr 2016 15:14:57 -0600 Subject: Upgrade annotate from 2.6.10 to 2.7.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Includes compatibility with Rails 5. We don’t have any tests for this, so no need to run them. [ci skip] Changelog: https://github.com/ctran/annotate_models/releases/tag/v2.7.0 Working towards #14286. --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 298cfd260ba..258b5612cd5 100644 --- a/Gemfile +++ b/Gemfile @@ -239,7 +239,7 @@ group :development do gem "foreman" gem 'brakeman', '~> 3.2.0', require: false - gem "annotate", "~> 2.6.0" + gem "annotate", "~> 2.7.0" gem "letter_opener", '~> 1.1.2' gem 'quiet_assets', '~> 1.0.2' gem 'rerun', '~> 0.11.0' diff --git a/Gemfile.lock b/Gemfile.lock index 28b71ac7bc8..bd11489ee5f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,8 +51,8 @@ GEM activerecord (>= 3.0) akismet (2.0.0) allocations (1.0.4) - annotate (2.6.10) - activerecord (>= 3.2, <= 4.3) + annotate (2.7.0) + activerecord (>= 3.2, < 6.0) rake (~> 10.4) arel (6.0.3) asana (0.4.0) @@ -888,7 +888,7 @@ DEPENDENCIES after_commit_queue akismet (~> 2.0) allocations (~> 1.0) - annotate (~> 2.6.0) + annotate (~> 2.7.0) asana (~> 0.4.0) asciidoctor (~> 1.5.2) attr_encrypted (~> 1.3.4) -- cgit v1.2.1 From 84ff30a6b2b522e35d608df311c58c180acb114e Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Sun, 10 Apr 2016 21:16:06 +0000 Subject: fix typo --- doc/ci/build_artifacts/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md index a21cd129862..9553bb11e9d 100644 --- a/doc/ci/build_artifacts/README.md +++ b/doc/ci/build_artifacts/README.md @@ -1,7 +1,9 @@ # Introduction to build artifacts Artifacts is a list of files and directories which are attached to a build -after it completes successfully. Artificats is enabled by default. _If you are searching for ways to use artifacts, jump to +after it completes successfully. This feature is enabled by default in all GitLab installations. + +_If you are searching for ways to use artifacts, jump to [Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._ Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by -- cgit v1.2.1 From 3b77a07856dc172959980918f19710e1b550b7cc Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 10 Apr 2016 06:22:58 -0700 Subject: Fix more SQL migrations to use raw commands gitlab-org/gitlab-development-kit#109 --- db/migrate/20130315124931_user_color_scheme.rb | 4 +++- .../20131112220935_add_visibility_level_to_projects.rb | 6 ++++-- .../20140313092127_migrate_already_imported_projects.rb | 8 +++++--- db/migrate/20141007100818_add_visibility_level_to_snippet.rb | 12 ++++++------ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/db/migrate/20130315124931_user_color_scheme.rb b/db/migrate/20130315124931_user_color_scheme.rb index fe139e32ea7..56c9a31ee3c 100644 --- a/db/migrate/20130315124931_user_color_scheme.rb +++ b/db/migrate/20130315124931_user_color_scheme.rb @@ -1,7 +1,9 @@ class UserColorScheme < ActiveRecord::Migration + include Gitlab::Database + def up add_column :users, :color_scheme_id, :integer, null: false, default: 1 - User.where(dark_scheme: true).update_all(color_scheme_id: 2) + execute("UPDATE users SET color_scheme_id = 2 WHERE dark_scheme = #{true_value}") remove_column :users, :dark_scheme end diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb index cf1e9f912a0..89421cbedad 100644 --- a/db/migrate/20131112220935_add_visibility_level_to_projects.rb +++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb @@ -1,13 +1,15 @@ class AddVisibilityLevelToProjects < ActiveRecord::Migration + include Gitlab::Database + def self.up add_column :projects, :visibility_level, :integer, :default => 0, :null => false - Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + execute("UPDATE projects SET visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} WHERE public = #{true_value}") remove_column :projects, :public end def self.down add_column :projects, :public, :boolean, :default => false, :null => false - Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) + execute("UPDATE projects SET public = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}") remove_column :projects, :visibility_level end end diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb index f4392c0f05e..0a9f73a5758 100644 --- a/db/migrate/20140313092127_migrate_already_imported_projects.rb +++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb @@ -1,12 +1,14 @@ class MigrateAlreadyImportedProjects < ActiveRecord::Migration + include Gitlab::Database + def up - Project.where(imported: true).update_all(import_status: "finished") - Project.where(imported: false).update_all(import_status: "none") + execute("UPDATE projects SET import_status = 'finished' WHERE imported = #{true_value}") + execute("UPDATE projects SET import_status = 'none' WHERE imported = #{false_value}") remove_column :projects, :imported end def down add_column :projects, :imported, :boolean, default: false - Project.where(import_status: 'finished').update_all(imported: true) + execute("UPDATE projects SET imported = #{true_value} WHERE import_status = 'finished'") end end diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb index 7f125acb5d1..bb3501f41db 100644 --- a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb +++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb @@ -2,8 +2,8 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration def up add_column :snippets, :visibility_level, :integer, :default => 0, :null => false - Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::PRIVATE} WHERE private = true") + execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::INTERNAL} WHERE private = false") add_index :snippets, :visibility_level @@ -12,10 +12,10 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration def down add_column :snippets, :private, :boolean, :default => false, :null => false - - Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false) - Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true) - + + execute("UPDATE snippets SET private = false WHERE visibility_level = #{Gitlab::VisibilityLevel::INTERNAL}") + execute("UPDATE snippets SET private = true WHERE visibility_level = #{Gitlab::VisibilityLevel::PRIVATE}") + remove_column :snippets, :visibility_level end end -- cgit v1.2.1 From a7c11fe47361527798b41d0ee9dc344b144a3b50 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 13:25:32 -0500 Subject: Ignore toggling sidebar for above 1200px resolution --- app/assets/javascripts/merge_request_tabs.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 839e6ec2c08..e45f91c2221 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -73,7 +73,8 @@ class @MergeRequestTabs @expandView() else if action == 'diffs' @loadDiff($target.attr('href')) - @shrinkView() + if bp.getBreakpointSize() isnt 'lg' + @shrinkView() else if action == 'builds' @loadBuilds($target.attr('href')) @expandView() -- cgit v1.2.1 From fe0aa823252eb468f468c9d813fdc28687c4a4b6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 14:37:15 -0500 Subject: Check existence of variable method This happens because tab's `shown` event is triggered first if we enter directly to the diff page therefore Breakpoints class is not initialized yet. --- app/assets/javascripts/merge_request_tabs.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index e45f91c2221..3500d796d2d 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -73,7 +73,7 @@ class @MergeRequestTabs @expandView() else if action == 'diffs' @loadDiff($target.attr('href')) - if bp.getBreakpointSize() isnt 'lg' + if window.bp isnt undefined and window.bp.getBreakpointSize() isnt 'lg' @shrinkView() else if action == 'builds' @loadBuilds($target.attr('href')) -- cgit v1.2.1 From 836301676adba7a91e4539f3d0e554873f06fc99 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 5 Apr 2016 17:12:15 -0500 Subject: Use existence operator instead --- app/assets/javascripts/merge_request_tabs.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 3500d796d2d..9946249adbf 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -73,7 +73,7 @@ class @MergeRequestTabs @expandView() else if action == 'diffs' @loadDiff($target.attr('href')) - if window.bp isnt undefined and window.bp.getBreakpointSize() isnt 'lg' + if bp? and bp.getBreakpointSize() isnt 'lg' @shrinkView() else if action == 'builds' @loadBuilds($target.attr('href')) -- cgit v1.2.1 From 0c6923e2d13b6648c8e08017450a01e7068edfb9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 10 Apr 2016 22:54:32 -0400 Subject: Re-add a note about sarcasm to the Code Review guide [ci skip] --- doc/development/code_review.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/development/code_review.md b/doc/development/code_review.md index ab4236404c6..40ae55ab905 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -25,6 +25,9 @@ request is up to one of our merge request "endbosses", denoted on the - Be explicit. Remember people don't always understand your intentions online. - Be humble. ("I'm not sure - let's look it up.") - Don't use hyperbole. ("always", "never", "endlessly", "nothing") +- Be careful about the use of sarcasm. Everything we do is public; what seems + like good-natured ribbing to you and a long-time colleague might come off as + mean and unwelcoming to a person new to the project. - Consider one-on-one chats or video calls if there are too many "I didn't understand" or "Alternative solution:" comments. Post a follow-up comment summarizing one-on-one discussion. -- cgit v1.2.1 From 13c00262f74d463e6adbc008aac00a4c63faac11 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 10 Apr 2016 21:19:09 -0700 Subject: Use false/true mixin in migration from !3640 --- db/migrate/20141007100818_add_visibility_level_to_snippet.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb index bb3501f41db..93826185e8b 100644 --- a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb +++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb @@ -1,9 +1,11 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration + include Gitlab::Database + def up add_column :snippets, :visibility_level, :integer, :default => 0, :null => false - execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::PRIVATE} WHERE private = true") - execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::INTERNAL} WHERE private = false") + execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::PRIVATE} WHERE private = #{true_value}") + execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::INTERNAL} WHERE private = #{false_value}") add_index :snippets, :visibility_level @@ -13,8 +15,8 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration def down add_column :snippets, :private, :boolean, :default => false, :null => false - execute("UPDATE snippets SET private = false WHERE visibility_level = #{Gitlab::VisibilityLevel::INTERNAL}") - execute("UPDATE snippets SET private = true WHERE visibility_level = #{Gitlab::VisibilityLevel::PRIVATE}") + execute("UPDATE snippets SET private = #{false_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::INTERNAL}") + execute("UPDATE snippets SET private = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PRIVATE}") remove_column :snippets, :visibility_level end -- cgit v1.2.1 From 0e8aaad51044bbbd5fc544c322d2a17d1c1ce22a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Apr 2016 17:13:57 +0200 Subject: Decrease threshold for ABC Size metric in Rubocop To 60. --- .rubocop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 71273ce6098..2fda0b03119 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -691,7 +691,7 @@ Style/ZeroLengthPredicate: # branches, and conditions. Metrics/AbcSize: Enabled: true - Max: 70 + Max: 60 # Avoid excessive block nesting. Metrics/BlockNesting: -- cgit v1.2.1 From 643fe43d78044d200c863f34d05c210c3e45b5ce Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 09:43:59 +0100 Subject: Addressed feedback Removed important from css --- app/assets/javascripts/gl_dropdown.js.coffee | 10 +++++----- app/assets/stylesheets/pages/merge_requests.scss | 5 +++-- app/views/projects/merge_requests/_new_compare.html.haml | 5 +++-- app/views/shared/issuable/_form.html.haml | 14 ++++++++------ features/steps/project/source/browse_files.rb | 1 - 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 82a82a27ccf..4be4ab60839 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -61,7 +61,7 @@ class GitLabDropdownFilter if data? results = data - if search_text isnt "" + if search_text isnt '' results = fuzzaldrinPlus.filter(data, search_text, key: @options.keys ) @@ -139,7 +139,7 @@ class GitLabDropdown if _.isString(@filterInput) @filterInput = @getElement(@filterInput) - search_fields = if @options.search then @options.search.fields else []; + searchFields = if @options.search then @options.search.fields else []; if @options.data # If data is an array @@ -163,11 +163,11 @@ class GitLabDropdown filterInputBlur: @filterInputBlur remote: @options.filterRemote query: @options.data - keys: search_fields + keys: searchFields elements: => - selector = ".dropdown-content li:not(.divider)" + selector = '.dropdown-content li:not(.divider)' - if @dropdown.find(".dropdown-toggle-page").length + if @dropdown.find('.dropdown-toggle-page').length selector = ".dropdown-page-one #{selector}" return $(selector) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 4269afe4c50..3c955faf2ad 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -258,6 +258,7 @@ } } -.merge-request-form .select2-container { - width: 250px!important; +.issuable-form-select-holder { + display: inline-block; + width: 250px; } diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 036b1935361..cae5d452317 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -16,9 +16,10 @@ = dropdown_title("Select source project") = dropdown_filter("Search projects") = dropdown_content do + - is_active = f.object.source_project_id == @merge_request.source_project.id %ul %li - %a{ href: "#", class: "#{("is-active" if f.object.source_project_id == @merge_request.source_project.id)}", data: { id: @merge_request.source_project.id } } + %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } } = @merge_request.source_project_path .merge-request-select.dropdown = f.hidden_field :source_branch @@ -33,7 +34,7 @@ %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } } = branch .panel-footer - = icon('spinner spin', class: "js-source-loading") + = icon('spinner spin', class: 'js-source-loading') %ul.list-unstyled.mr_source_commit .col-md-6 diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index e2a9e5bfb92..5244e7f6ccb 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -53,10 +53,11 @@ .issue-assignee = f.label :assignee_id, "Assignee", class: 'control-label' .col-sm-10 - = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", - placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id, project: @target_project || @project, - first_user: true, current_user: true, include_blank: true) + .issuable-form-select-holder + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id, project: @target_project || @project, + first_user: true, current_user: true, include_blank: true)   = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group @@ -64,8 +65,9 @@ = f.label :milestone_id, "Milestone", class: 'control-label' .col-sm-10 - if milestone_options(issuable).present? - = f.select(:milestone_id, milestone_options(issuable), - { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) + .issuable-form-select-holder + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) - else .prepend-top-10 %span.light No open milestones available. diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 37958a924bf..e072505e5d7 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -219,7 +219,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I see Browse code link' do expect(page).to have_link 'Browse Files' - # expect(page).not_to have_link 'Browse File' expect(page).not_to have_link 'Browse Directory »' end -- cgit v1.2.1 From a4fff0e03368bf57081ae80d727bfc38ab165cd5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 09:52:12 +0100 Subject: Fixed dropdown title overlap --- app/assets/stylesheets/framework/dropdowns.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 82dc1acbd01..ba6c7930cdc 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -248,7 +248,7 @@ .dropdown-title { position: relative; - padding: 0 0 15px; + padding: 0 25px 15px; margin: 0 10px 10px; font-weight: 600; line-height: 1; @@ -275,7 +275,7 @@ } .dropdown-menu-close { - right: 7px; + right: 5px; width: 20px; height: 20px; top: -1px; -- cgit v1.2.1 From 12ff7ee93348239d545e0553a49881cfe490420e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 09:54:32 +0100 Subject: Merge request mobile spacing --- app/assets/stylesheets/pages/merge_requests.scss | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 3c955faf2ad..b79335eab91 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -242,10 +242,19 @@ } .merge-request-select { - float: left; - width: 50%; padding-left: 5px; padding-right: 5px; + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + + @media (min-width: $screen-sm-min) { + float: left; + width: 50%; + margin-bottom: 0; + } .dropdown-menu-toggle { width: 100%; -- cgit v1.2.1 From 2f4dc45da2fbe6ab4469f1c836683bec9c8f0dd9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 11:38:24 +0100 Subject: Fixed issue with dashboard/issues not filtering by milestone Closes #15128 --- app/assets/javascripts/milestone_select.js.coffee | 8 +++- spec/features/dashboard_issues_spec.rb | 54 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spec/features/dashboard_issues_spec.rb diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index f73127f49f0..6bd4e885a03 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -85,15 +85,21 @@ class @MilestoneSelect # display:block overrides the hide-collapse rule $value.removeAttr('style') clicked: (selected) -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + if $dropdown.hasClass 'js-filter-bulk-update' return - if $dropdown.hasClass('js-filter-submit') + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if selected.name? selectedMilestone = selected.name else selectedMilestone = '' Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass('js-filter-submit') + $dropdown.closest('form').submit() else selected = $selectbox .find('input[type="hidden"]') diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb new file mode 100644 index 00000000000..39805da9d0b --- /dev/null +++ b/spec/features/dashboard_issues_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe "Dashboard Issues filtering", feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, project: project) } + + context 'filtering by milestone' do + before do + project.team << [user, :master] + login_as(user) + + create(:issue, project: project, author: user, assignee: user) + create(:issue, project: project, author: user, assignee: user, milestone: milestone) + + visit_issues + end + + it 'should show all issues with no milestone' do + show_milestone_dropdown + + click_link 'No Milestone' + + expect(page).to have_selector('.issue', count: 1) + end + + it 'should show all issues with any milestone' do + show_milestone_dropdown + + click_link 'Any Milestone' + + expect(page).to have_selector('.issue', count: 2) + end + + it 'should show all issues with the selected milestone' do + show_milestone_dropdown + + page.within '.dropdown-content' do + click_link milestone.title + end + + expect(page).to have_selector('.issue', count: 1) + end + end + + def show_milestone_dropdown + click_button 'Milestone' + expect(page).to have_selector('.dropdown-content', visible: true) + end + + def visit_issues + visit issues_dashboard_path + end +end -- cgit v1.2.1 From 667d44c25ccefb511fc0d206eaa5990117032236 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 11 Apr 2016 12:46:19 +0200 Subject: Fix high CPU usage when PostReceive receives refs/merge-requests/ --- CHANGELOG | 1 + app/workers/post_receive.rb | 2 +- spec/workers/post_receive_spec.rb | 43 +++++++++++++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df0..6a196dd9dce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.7.0 (unreleased) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups + - Fix high CPU usage when PostReceive receives refs/merge-requests/ - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 3cc232ef1ae..9e1215b21a6 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -40,7 +40,7 @@ class PostReceive if Gitlab::Git.tag_ref?(ref) GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) - else + elsif Gitlab::Git.branch_ref?(ref) GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 0265dbe9c66..94ff3457902 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -4,6 +4,9 @@ describe PostReceive do let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } + let(:project) { create(:project) } + let(:key) { create(:key, user: project.owner) } + let(:key_id) { key.shell_id } context "as a resque worker" do it "reponds to #perform" do @@ -11,11 +14,43 @@ describe PostReceive do end end - context "webhook" do - let(:project) { create(:project) } - let(:key) { create(:key, user: project.owner) } - let(:key_id) { key.shell_id } + describe "#process_project_changes" do + before do + allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner) + end + context "branches" do + let(:changes) { "123456 789012 refs/heads/tést" } + + it "should call GitTagPushService" do + expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) + expect_any_instance_of(GitTagPushService).not_to receive(:execute) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + + context "tags" do + let(:changes) { "123456 789012 refs/tags/tag" } + + it "should call GitTagPushService" do + expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + + context "merge-requests" do + let(:changes) { "123456 789012 refs/merge-requests/123" } + + it "should not call any of the services" do + expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(GitTagPushService).not_to receive(:execute) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + end + + context "webhook" do it "fetches the correct project" do expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) PostReceive.new.perform(pwd(project), key_id, base64_changes) -- cgit v1.2.1 From 16926a676bdea4bfbbbaf9d390373073d2ff8bbd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Apr 2016 12:23:37 +0200 Subject: Store block timings as transaction values This makes it easier to query, simplifies the code, and makes it possible to figure out what transaction the data belongs to (simply because it's now stored _in_ the transaction). This new setup keeps track of both the real/wall time _and_ CPU time spent in a block, both measured using milliseconds (to keep all units the same). --- doc/development/instrumentation.md | 36 +++++++++++++++++------------------- lib/gitlab/metrics.rb | 23 ++++++++++++++--------- lib/gitlab/metrics/system.rb | 11 +++++++++++ spec/lib/gitlab/metrics_spec.rb | 16 +++++----------- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c0192bd6709..f7e148fb3e4 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -2,36 +2,34 @@ GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby code. This can be used to measure the time spent in a specific part of a larger -chunk of code. The resulting data is written to a separate series. +chunk of code. The resulting data is stored as a field in the transaction that +executed the block. -To start measuring a block of Ruby code you should use -`Gitlab::Metrics.measure` and give it a name for the series to store the data -in: +To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure` +and give it a name: ```ruby -Gitlab::Metrics.measure(:user_logins) do +Gitlab::Metrics.measure(:foo) do ... end ``` -The first argument of this method is the series name and should be plural. This -name will be prefixed with `rails_` or `sidekiq_` depending on whether the code -was run in the Rails application or one of the Sidekiq workers. In the -above example the final series names would be as follows: +Two values are measured for a block: -- rails_user_logins -- sidekiq_user_logins +1. The real time elapsed, stored in NAME_real_time +2. The CPU time elapsed, stored in NAME_cpu_time -Series names should be plural as this keeps the naming style in line with the -other series names. +Both the real and CPU timings are measured in milliseconds. -By default metrics measured using a block contain a single value, "duration", -which contains the number of milliseconds it took to execute the block. Custom -values can be added by passing a Hash as the 2nd argument. Custom tags can be -added by passing a Hash as the 3rd argument. A simple example is as follows: +Multiple calls to the same block will result in the final values being the sum +of all individual values. Take this code for example: ```ruby -Gitlab::Metrics.measure(:example_series, { number: 10 }, { class: self.class.to_s }) do - ... +3.times do + Gitlab::Metrics.measure(:sleep) do + sleep 1 + end end ``` + +Here the final value of `sleep_real_time` will be `3`, _not_ `1`. diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 4a3f47b5a95..4d64555027e 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -74,24 +74,29 @@ module Gitlab # # Example: # - # Gitlab::Metrics.measure(:find_by_username_timings) do + # Gitlab::Metrics.measure(:find_by_username_duration) do # User.find_by_username(some_username) # end # - # series - The name of the series to store the data in. - # values - A Hash containing extra values to add to the metric. - # tags - A Hash containing extra tags to add to the metric. + # name - The name of the field to store the execution time in. # # Returns the value yielded by the supplied block. - def self.measure(series, values = {}, tags = {}) + def self.measure(name) return yield unless Transaction.current - start = Time.now.to_f + real_start = Time.now.to_f + cpu_start = System.cpu_time + retval = yield - duration = (Time.now.to_f - start) * 1000.0 - values = values.merge(duration: duration) - Transaction.current.add_metric(series, values, tags) + cpu_stop = System.cpu_time + real_stop = Time.now.to_f + + real_time = (real_stop - real_start) * 1000.0 + cpu_time = cpu_stop - cpu_start + + Transaction.current.increment("#{name}_real_time", real_time) + Transaction.current.increment("#{name}_cpu_time", cpu_time) retval end diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb index 83371265278..a7d183b2f94 100644 --- a/lib/gitlab/metrics/system.rb +++ b/lib/gitlab/metrics/system.rb @@ -30,6 +30,17 @@ module Gitlab 0 end end + + # THREAD_CPUTIME is not supported on OS X + if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) + def self.cpu_time + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond) + end + else + def self.cpu_time + Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond) + end + end end end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 8f63a5f2043..edefec909c9 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -79,19 +79,13 @@ describe Gitlab::Metrics do end it 'adds a metric to the current transaction' do - expect(transaction).to receive(:add_metric). - with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' }) + expect(transaction).to receive(:increment). + with('foo_real_time', a_kind_of(Numeric)) - Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 } - end - - it 'supports adding of custom values' do - values = { duration: a_kind_of(Numeric), number: 10 } - - expect(transaction).to receive(:add_metric). - with(:foo, values, { tag: 'value' }) + expect(transaction).to receive(:increment). + with('foo_cpu_time', a_kind_of(Numeric)) - Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 } + Gitlab::Metrics.measure(:foo) { 10 } end it 'returns the return value of the block' do -- cgit v1.2.1 From 185d78bcb3064c25b764937949d2bb66ec66901a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Apr 2016 13:11:13 +0200 Subject: Added specs for Gitlab::Metrics::System.cpu_time --- spec/lib/gitlab/metrics/system_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index f8c1d956ca1..d6ae54e25e8 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do end end end + + describe '.cpu_time' do + it 'returns a Fixnum' do + expect(described_class.cpu_time).to be_an_instance_of(Fixnum) + end + end end -- cgit v1.2.1 From d9110a7ecab52ab0716a42c2075cebdf8028d5e7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Apr 2016 13:27:24 +0200 Subject: Track call counts in Gitlab::Metrics.measure_block --- doc/development/instrumentation.md | 7 ++++--- lib/gitlab/metrics.rb | 1 + spec/lib/gitlab/metrics_spec.rb | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index f7e148fb3e4..c1cf2e77c26 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -14,10 +14,11 @@ Gitlab::Metrics.measure(:foo) do end ``` -Two values are measured for a block: +3 values are measured for a block: -1. The real time elapsed, stored in NAME_real_time -2. The CPU time elapsed, stored in NAME_cpu_time +1. The real time elapsed, stored in NAME_real_time. +2. The CPU time elapsed, stored in NAME_cpu_time. +3. The call count, stored in NAME_call_count. Both the real and CPU timings are measured in milliseconds. diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 4d64555027e..33dd3e39f4d 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -97,6 +97,7 @@ module Gitlab Transaction.current.increment("#{name}_real_time", real_time) Transaction.current.increment("#{name}_cpu_time", cpu_time) + Transaction.current.increment("#{name}_call_count", 1) retval end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index edefec909c9..a3b68455260 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -85,6 +85,9 @@ describe Gitlab::Metrics do expect(transaction).to receive(:increment). with('foo_cpu_time', a_kind_of(Numeric)) + expect(transaction).to receive(:increment). + with('foo_call_count', 1) + Gitlab::Metrics.measure(:foo) { 10 } end -- cgit v1.2.1 From 7eed4608fe5adf65d6a29ef50c93485ca2e6806f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Apr 2016 14:29:38 +0200 Subject: Fixed stubbing for Gitlab::Metrics specs If the measure method uses Transaction.current directly the SQL subscriber (Subscribers::ActiveRecord) will add timings of queries triggered by DB cleaner. --- lib/gitlab/metrics.rb | 16 ++++++++++++---- spec/lib/gitlab/metrics_spec.rb | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 33dd3e39f4d..2a0a5629be5 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -82,7 +82,9 @@ module Gitlab # # Returns the value yielded by the supplied block. def self.measure(name) - return yield unless Transaction.current + trans = current_transaction + + return yield unless trans real_start = Time.now.to_f cpu_start = System.cpu_time @@ -95,9 +97,9 @@ module Gitlab real_time = (real_stop - real_start) * 1000.0 cpu_time = cpu_stop - cpu_start - Transaction.current.increment("#{name}_real_time", real_time) - Transaction.current.increment("#{name}_cpu_time", cpu_time) - Transaction.current.increment("#{name}_call_count", 1) + trans.increment("#{name}_real_time", real_time) + trans.increment("#{name}_cpu_time", cpu_time) + trans.increment("#{name}_call_count", 1) retval end @@ -113,5 +115,11 @@ module Gitlab new(udp: { host: host, port: port }) end end + + private + + def self.current_transaction + Transaction.current + end end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index a3b68455260..3dee13e27f4 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -74,7 +74,7 @@ describe Gitlab::Metrics do let(:transaction) { Gitlab::Metrics::Transaction.new } before do - allow(Gitlab::Metrics::Transaction).to receive(:current). + allow(Gitlab::Metrics).to receive(:current_transaction). and_return(transaction) end -- cgit v1.2.1 From 08c3936b83f3f67cbabf74289805a3938e2b7564 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 11 Apr 2016 15:10:14 +0200 Subject: Check head unborn in rugged --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 8dead3a5884..3e52ee5d0a2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -896,7 +896,7 @@ class Repository end def main_language - unless empty? + unless empty? or rugged.head_unborn? Linguist::Repository.new(rugged, rugged.head.target_id).language end end -- cgit v1.2.1 From 73fdd4b83d76998fef9770dbeaf05981d4500b8c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 10:23:40 -0300 Subject: Use Hash instead of Array on NotificationSetting#level enum --- app/models/notification_setting.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index d89194b5a12..5001738f411 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,7 +1,5 @@ class NotificationSetting < ActiveRecord::Base - # Notification level - # Note: When adding an option, it MUST go on the end of the array. - enum level: [:disabled, :participating, :watch, :global, :mention] + enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 } default_value_for :level, NotificationSetting.levels[:global] -- cgit v1.2.1 From de4d98fd120fd43bd744abc116c62708577b5673 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 11 Apr 2016 15:19:11 +0100 Subject: fix bug causing comment form in issue to submit twice when CTRL+Enter is pressed twice --- app/assets/javascripts/behaviors/quick_submit.js.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee index 6e29d374267..3cb96bacaa7 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js.coffee +++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee @@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> e.preventDefault() $form = $(e.target).closest('form') - $form.find('input[type=submit], button[type=submit]').disable() + $submit_button = $form.find('input[type=submit], button[type=submit]') + + return if $submit_button.attr('disabled') + + $submit_button.disable() $form.submit() # If the user tabs to a submit button on a `js-quick-submit` form, display a -- cgit v1.2.1 From ca0b3ea1f107562492babbaf5ce5fec43897bd88 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 11 Apr 2016 17:03:27 +0200 Subject: Go back to gitlab-workhorse 0.7.1 We found out that 0.7.2 breaks shallow `git clone`. GitLab 8.7 will work fine with gitlab-workhorse 0.7.1 too. --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.6-to-8.7.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 7486fdbc50b..39e898a4f95 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.2 +0.7.1 diff --git a/doc/install/installation.md b/doc/install/installation.md index d97dc7d1311..f8f7d6a9ebe 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -352,7 +352,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.2 + sudo -u git -H git checkout v0.7.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index 57847d2d9fd..8599133a726 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -58,7 +58,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.2 +sudo -u git -H git checkout v0.7.1 sudo -u git -H make ``` -- cgit v1.2.1 From 12e6084667f8750c263b4a2e324e9a283697b52e Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 10:16:15 -0500 Subject: Allow `external_providers` for Omniauth to be defined to mark these users as external --- config/initializers/1_settings.rb | 1 + lib/gitlab/o_auth/user.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 72c4d8d61ce..94612997ead 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -129,6 +129,7 @@ Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil? +Settings.omniauth['external_providers'] = [] if Settings.omniauth['external_providers'].nil? Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil? Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil? diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 832fb08a526..6e099c26d8c 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -54,6 +54,14 @@ module Gitlab @user ||= build_new_user end + unless @user.nil? + if external_provider? + @user.external = true + else + @user.external = false + end + end + @user end @@ -113,6 +121,10 @@ module Gitlab end end + def external_provider? + Gitlab.config.omniauth.external_providers.include?(auth_hash.provider) + end + def block_after_signup? if creating_linked_ldap_user? ldap_config.block_auto_created_users -- cgit v1.2.1 From ea04b0191d624fdc3e6f82840825bd265a4c3f59 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 10:16:42 -0500 Subject: Added default setting for `external_providers` --- spec/lib/gitlab/o_auth/user_spec.rb | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 3a769acfdc0..6727a83e58a 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -15,20 +15,20 @@ describe Gitlab::OAuth::User, lib: true do end let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } - describe :persisted? do + describe '#persisted?' do let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } it "finds an existing user based on uid and provider (facebook)" do expect( oauth_user.persisted? ).to be_truthy end - it "returns false if use is not found in database" do + it 'returns false if user is not found in database' do allow(auth_hash).to receive(:uid).and_return('non-existing') expect( oauth_user.persisted? ).to be_falsey end end - describe :save do + describe '#save' do def stub_omniauth_config(messages) allow(Gitlab.config.omniauth).to receive_messages(messages) end @@ -40,8 +40,27 @@ describe Gitlab::OAuth::User, lib: true do let(:provider) { 'twitter' } describe 'signup' do - shared_examples "to verify compliance with allow_single_sign_on" do - context "with new allow_single_sign_on enabled syntax" do + shared_examples 'to verify compliance with allow_single_sign_on' do + context 'provider is marked as external' do + it 'should mark user as external' do + stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter']) + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + context 'provider was external, now has been removed' do + it 'should mark existing user internal' do + create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true) + stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook']) + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + + context 'with new allow_single_sign_on enabled syntax' do before { stub_omniauth_config(allow_single_sign_on: ['twitter']) } it "creates a user from Omniauth" do @@ -67,16 +86,16 @@ describe Gitlab::OAuth::User, lib: true do end end - context "with new allow_single_sign_on disabled syntax" do + context 'with new allow_single_sign_on disabled syntax' do before { stub_omniauth_config(allow_single_sign_on: []) } - it "throws an error" do + it 'throws an error' do expect{ oauth_user.save }.to raise_error StandardError end end - context "with old allow_single_sign_on disabled (Default)" do + context 'with old allow_single_sign_on disabled (Default)' do before { stub_omniauth_config(allow_single_sign_on: false) } - it "throws an error" do + it 'throws an error' do expect{ oauth_user.save }.to raise_error StandardError end end -- cgit v1.2.1 From cedfe9d22851d6765b9737c2489e8ff166a7d238 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 10:16:56 -0500 Subject: Documentation of feature --- config/gitlab.yml.example | 7 +++++++ doc/integration/omniauth.md | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 35c7c425a5a..75aba2544b5 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -304,6 +304,13 @@ production: &base # (default: false) auto_link_saml_user: false + # Set different Omniauth providers as external so that all users creating accounts + # via these providers will not be able to have access to internal projects. You + # will need to use the full name of the provider, like `google_oauth2` for Google. + # Refer to the examples below for the full names of the supported providers. + # (default: []) + external_providers: [] + ## Auth providers # Uncomment the following lines and fill in the data of the auth provider you want to use # If your favorite auth provider is not listed you can use others: diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 25f35988305..cab329c0dec 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -120,6 +120,29 @@ OmniAuth provider for an existing user. The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on. +## Configure OmniAuth Providers as External + +>**Note:** +This setting was introduced with version 8.7 of GitLab + +You can define which OmniAuth providers you want to be `external` so that all users +creating accounts via these providers will not be able to have access to internal +projects. You will need to use the full name of the provider, like `google_oauth2` +for Google. Refer to the examples for the full names of the supported providers. + +**For Omnibus installations** + +```ruby + gitlab_rails['omniauth_external_providers'] = ['twitter', 'google_oauth2'] +``` + +**For installations from source** + +```yaml + omniauth: + external_providers: ['twitter', 'google_oauth2'] +``` + ## Using Custom Omniauth Providers >**Note:** -- cgit v1.2.1 From 979dedba8a68f33b8e2078f7e2980bf048a8a25a Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 11 Apr 2016 16:24:49 +0100 Subject: make milestone labels in labels tab similar to that of the labels page --- app/assets/stylesheets/pages/labels.scss | 25 ++++++++++++++++------- app/views/shared/milestones/_labels_tab.html.haml | 13 ++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 3e0a3140be7..da20fa28802 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -79,19 +79,30 @@ color: $white-light; } +@mixin labels-mobile { + @media (max-width: $screen-xs-min) { + display: block; + width: 100%; + margin-left: 0; + padding: 10px 0; + } +} + + .manage-labels-list { - .prepend-left-10 { + .prepend-left-10, .prepend-description-left { display: inline-block; width: 40%; vertical-align: middle; - @media (max-width: $screen-xs-min) { - display: block; - width: 100%; - margin-left: 0; - padding: 10px 0; - } + @include labels-mobile; + } + + .prepend-description-left { + width: 57%; + + @include labels-mobile; } .pull-info-right { diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index 868b2357003..b15e8ea73fe 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -4,15 +4,16 @@ %li %span.label-row - = link_to milestones_label_path(options) do - - render_colored_label(label, tooltip: false) - %span.prepend-left-10 + %span.label-name + = link_to milestones_label_path(options) do + - render_colored_label(label, tooltip: false) + %span.prepend-description-left = markdown(label.description, pipeline: :single_line) - .pull-right - %strong.issues-count + .pull-info-right + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'opened')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' - %strong.issues-count + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'closed')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' -- cgit v1.2.1 From 31bcd9f8793d972bd59fc75c686ab03974a1d631 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 10:25:53 -0500 Subject: Added CHANGELOG item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df0..08ee7ec89ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.7.0 (unreleased) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 + - Allow Omniauth providers to be marked as `external` !3657 - Add endpoints to archive or unarchive a project !3372 - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) -- cgit v1.2.1 From 935f913165b91467a70d9ba2b0ea29fad467db9d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Apr 2016 17:42:12 +0200 Subject: Instrument Banzai code --- config/initializers/metrics.rb | 23 +++++++++++++++++++++++ lib/banzai/renderer.rb | 20 ++++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index a9fc38fb04a..1b445bbbd10 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -75,6 +75,29 @@ if Gitlab::Metrics.enabled? config.instrument_methods(const) config.instrument_instance_methods(const) end + + # Instruments all Banzai filters + Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file| + klass = File.basename(file, File.extname(file)).camelize + const = Banzai::Filter.const_get(klass) + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end + + config.instrument_methods(Banzai::ReferenceExtractor) + config.instrument_instance_methods(Banzai::ReferenceExtractor) + + config.instrument_methods(Banzai::Renderer) + config.instrument_methods(Banzai::Querying) + + [Issuable, Mentionable, Participable].each do |klass| + config.instrument_instance_methods(klass) + config.instrument_instance_methods(klass::ClassMethods) + end + + config.instrument_methods(Gitlab::ReferenceExtractor) + config.instrument_instance_methods(Gitlab::ReferenceExtractor) end GC::Profiler.enable diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index ae714c87dc5..c14a9c4c722 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -19,8 +19,10 @@ module Banzai cache_key = full_cache_key(cache_key, context[:pipeline]) if cache_key - Rails.cache.fetch(cache_key) do - cacheless_render(text, context) + Gitlab::Metrics.measure(:banzai_cached_render) do + Rails.cache.fetch(cache_key) do + cacheless_render(text, context) + end end else cacheless_render(text, context) @@ -64,13 +66,15 @@ module Banzai private def self.cacheless_render(text, context = {}) - result = render_result(text, context) + Gitlab::Metrics.measure(:banzai_cacheless_render) do + result = render_result(text, context) - output = result[:output] - if output.respond_to?(:to_html) - output.to_html - else - output.to_s + output = result[:output] + if output.respond_to?(:to_html) + output.to_html + else + output.to_s + end end end -- cgit v1.2.1 From 450a39ededbf93d0bfcec1d4774c3562b87fc190 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 17:04:01 +0100 Subject: Fixed alignment on issuable new form Fixes #13802 --- app/assets/stylesheets/pages/issuable.scss | 6 ++++++ app/views/shared/issuable/_form.html.haml | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88c1b614c74..999b9a2e79a 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -316,3 +316,9 @@ color: #8c8c8c; } } + +.issuable-form-padding-top { + @media (min-width: $screen-sm-min) { + padding-top: 7px; + } +} diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 757a3812deb..1c89a2ee7f3 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -70,13 +70,13 @@ - if can? current_user, :admin_milestone, issuable.project = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank .form-group + - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: 'control-label' - .col-sm-10 - - if issuable.project.labels.any? + .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) } + - if has_labels = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } - else - .prepend-top-10 %span.light No labels yet.   - if can? current_user, :admin_label, issuable.project -- cgit v1.2.1 From 27d2b355358b52d840153cbb100c2820fab29afe Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 11 Apr 2016 18:11:49 +0200 Subject: Add changelog, change code to guard clause --- CHANGELOG | 3 +++ app/models/repository.rb | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df0..a53a0615ca7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,9 @@ v 8.7.0 (unreleased) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 +v 8.6.6 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 + v 8.6.5 - Fix importing from GitHub Enterprise. !3529 - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 diff --git a/app/models/repository.rb b/app/models/repository.rb index 3e52ee5d0a2..a24ce45a38b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -896,9 +896,9 @@ class Repository end def main_language - unless empty? or rugged.head_unborn? - Linguist::Repository.new(rugged, rugged.head.target_id).language - end + return nil if empty? || rugged.head_unborn? + + Linguist::Repository.new(rugged, rugged.head.target_id).language end def avatar -- cgit v1.2.1 From f36655dc8b796aac1a75cc28d4768280a9e2860b Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 11 Apr 2016 18:14:01 +0200 Subject: Dont return null --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index a24ce45a38b..462b48118ef 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -896,7 +896,7 @@ class Repository end def main_language - return nil if empty? || rugged.head_unborn? + return if empty? || rugged.head_unborn? Linguist::Repository.new(rugged, rugged.head.target_id).language end -- cgit v1.2.1 From 60fd4188a540368776738896b1e10683b7fa9e65 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 16:18:25 +0100 Subject: Build notification null check Cancels build notification interval on page change --- app/assets/javascripts/merge_request_widget.js.coffee | 15 ++++++++++++++- app/views/projects/merge_requests/widget/_show.html.haml | 7 +------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 84a8887fbce..1e3a5847521 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -12,10 +12,19 @@ class @MergeRequestWidget @readyForCICheck = true clearInterval @fetchBuildStatusInterval + @clearEventListeners() + @addEventListeners() @pollCIStatus() notifyPermissions() - setOpts: (@opts) -> + clearEventListeners: -> + $(document).off 'page:change.merge_request' + + addEventListeners: -> + $(document).on 'page:change.merge_request', => + if $('body').data('page') isnt 'projects:merge_requests:show' + clearInterval @fetchBuildStatusInterval + @clearEventListeners() mergeInProgress: (deleteSourceBranch = false)-> $.ajax @@ -63,11 +72,15 @@ class @MergeRequestWidget @firstCICheck = false @opts.ci_status = data.status +<<<<<<< a918e8bf277418048776a5d9c34a64b39f4e56f3 if @opts.ci_status is '' @opts.ci_status = data.status return if data.status isnt @opts.ci_status +======= + if data.status isnt @opts.ci_status and data.status? +>>>>>>> Build notification null check @showCIStatus data.status if data.coverage @showCICoverage data.coverage diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 92d95358937..003477dda1a 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -8,7 +8,6 @@ = render 'projects/merge_requests/widget/locked' :javascript - var merge_request_widget; var opts = { merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, @@ -20,8 +19,4 @@ builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" }; - if(typeof merge_request_widget === 'undefined') { - merge_request_widget = new MergeRequestWidget(opts); - } else { - merge_request_widget.setOpts(opts); - } + new MergeRequestWidget(opts); -- cgit v1.2.1 From b87cc5f224428a332b63f3ac7832ea7d882b37b3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 13:58:44 +0100 Subject: Preparing build status --- app/controllers/projects/merge_requests_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ae613f5e093..9addcdf3f91 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -237,6 +237,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + status = "preparing" if status == nil + response = { title: merge_request.title, sha: merge_request.last_commit_short_sha, -- cgit v1.2.1 From ce0678faa5a1e4688aa7491c49cebd4b172244af Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 14:50:34 +0100 Subject: Rubocop fix --- app/controllers/projects/merge_requests_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9addcdf3f91..3e0cfc6aa65 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -237,7 +237,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - status = "preparing" if status == nil + status = "preparing" if status.nil? response = { title: merge_request.title, -- cgit v1.2.1 From 99fe9c7b7703b1594c9cda571f43f20198f96b0f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 14:58:50 +0100 Subject: Corrects the button color on build status change --- app/assets/javascripts/merge_request_widget.js.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 1e3a5847521..63f75be2dad 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -111,6 +111,8 @@ class @MergeRequestWidget @setMergeButtonClass('btn-danger') when "running", "pending" @setMergeButtonClass('btn-warning') + when "success" + @setMergeButtonClass('btn-create') else $('.ci_widget.ci-error').show() @setMergeButtonClass('btn-danger') @@ -120,4 +122,6 @@ class @MergeRequestWidget $('.ci_widget:visible .ci-coverage').text(text) setMergeButtonClass: (css_class) -> - $('.accept_merge_request').removeClass("btn-create").addClass(css_class) + $('.accept_merge_request') + .removeClass('btn-danger btn-warning btn-create') + .addClass(css_class) -- cgit v1.2.1 From 552cde76fa96282dec4ba002cd1850d003a5b29f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 15:06:27 +0100 Subject: Hides notification after X amount of seconds --- app/assets/javascripts/lib/notify.js.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee index 3f9ca39912c..9e28353ac34 100644 --- a/app/assets/javascripts/lib/notify.js.coffee +++ b/app/assets/javascripts/lib/notify.js.coffee @@ -2,6 +2,11 @@ notificationGranted = (message, opts, onclick) -> notification = new Notification(message, opts) + # Hide the notification after X amount of seconds + setTimeout -> + notification.close() + , 8000 + if onclick notification.onclick = onclick -- cgit v1.2.1 From 843bc44aa9dea8a19af21dd13a82ecc02bf8840c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 17:04:22 +0100 Subject: Preparing build text --- app/assets/javascripts/merge_request_widget.js.coffee | 16 +++++++++++++--- app/views/projects/merge_requests/widget/_show.html.haml | 9 ++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 63f75be2dad..a522f9189ab 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -47,7 +47,7 @@ class @MergeRequestWidget $('.mr-state-widget').replaceWith(data) ciLabelForStatus: (status) -> - if status == 'success' + if status is 'success' 'passed' else status @@ -86,12 +86,22 @@ class @MergeRequestWidget @showCICoverage data.coverage if showNotification - message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status)) + status = @ciLabelForStatus(data.status) + + if status is "preparing" + title = @opts.ci_title.preparing + status = status.charAt(0).toUpperCase() + status.slice(1); + message = @opts.ci_message.preparing.replace('{{status}}', status) + else + title = @opts.ci_title.normal + message = @opts.ci_message.normal.replace('{{status}}', status) + + title = title.replace('{{status}}', status) message = message.replace('{{sha}}', data.sha) message = message.replace('{{title}}', data.title) notify( - "Build #{@ciLabelForStatus(data.status)}", + title, message, @opts.gitlab_icon, -> diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 003477dda1a..804ca3783e3 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,8 +14,15 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "", - ci_message: "Build {{status}} for \"{{title}}\"", + ci_message: { + normal: "Build {{status}} for \"{{title}}\"", + preparing: "{{status}} build for \"{{title}}\"" + }, ci_enable: #{@project.ci_service ? "true" : "false"}, + ci_title: { + preparing: "{{status}} build", + normal: "Build {{status}}" + }, builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" }; -- cgit v1.2.1 From 6a13b372224a5f7ae03dde92f455645441b62f84 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 15:32:46 +0100 Subject: Fixed missed conflict --- app/assets/javascripts/merge_request_widget.js.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index a522f9189ab..065626beeb8 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -72,15 +72,11 @@ class @MergeRequestWidget @firstCICheck = false @opts.ci_status = data.status -<<<<<<< a918e8bf277418048776a5d9c34a64b39f4e56f3 if @opts.ci_status is '' @opts.ci_status = data.status return - if data.status isnt @opts.ci_status -======= if data.status isnt @opts.ci_status and data.status? ->>>>>>> Build notification null check @showCIStatus data.status if data.coverage @showCICoverage data.coverage -- cgit v1.2.1 From 1b4a56bdb05afdb68b99067c60b9b453b2ecdc24 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 17:28:54 +0100 Subject: Fixed failing tests --- app/views/projects/merge_requests/widget/_show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 804ca3783e3..3c68d61c4b5 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -26,4 +26,4 @@ builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" }; - new MergeRequestWidget(opts); + merge_request_widget = new MergeRequestWidget(opts); -- cgit v1.2.1 From 6333ebe619848aaec3811c9e8908c017317c019f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 10:16:39 +0100 Subject: Prefills commit message in edit file See #14488 --- app/views/shared/_commit_message_container.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 7afbaeddee8..eeb4e2f5563 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -6,7 +6,7 @@ .commit-message-container .max-width-marker = text_area_tag 'commit_message', - (params[:commit_message] || local_assigns[:text]), + (params[:commit_message] || local_assigns[:placeholder]), class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder], required: true, rows: (local_assigns[:rows] || 3), id: "commit_message-#{nonce}" -- cgit v1.2.1 From 44a95c5dea278d9d835030e3cab8e9e7bead0b44 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 17:41:47 +0100 Subject: Added back text used on merge commit message --- app/views/shared/_commit_message_container.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index eeb4e2f5563..0a38327baa2 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -6,7 +6,7 @@ .commit-message-container .max-width-marker = text_area_tag 'commit_message', - (params[:commit_message] || local_assigns[:placeholder]), + (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]), class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder], required: true, rows: (local_assigns[:rows] || 3), id: "commit_message-#{nonce}" -- cgit v1.2.1 From 38a4f5cec6ab85525ab9db1d7d2669a77171f768 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 16:45:03 -0500 Subject: Rename method and initialize .timeago inside --- app/assets/javascripts/application.js.coffee | 4 +--- app/assets/javascripts/lib/datetime_utility.js.coffee | 4 +++- app/assets/javascripts/merge_request_tabs.js.coffee | 12 +++--------- app/assets/javascripts/notes.js.coffee | 9 +++------ 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 922a28b4ef5..b05138ac1ac 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -164,9 +164,7 @@ $ -> $('.trigger-submit').on 'change', -> $(@).parents('form').submit() - $timeago = $('abbr.timeago, .js-timeago') - gl.utils.updateFormatDate($timeago) - $timeago.timeago() + gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false) # Flash if (flash = $(".flash-container")).length > 0 diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/datetime_utility.js.coffee index ef9406fc331..ad1d1c70481 100644 --- a/app/assets/javascripts/lib/datetime_utility.js.coffee +++ b/app/assets/javascripts/lib/datetime_utility.js.coffee @@ -6,10 +6,12 @@ w.gl.utils.formatDate = (datetime) -> dateFormat(datetime, 'mmm d, yyyy h:MMtt Z') - w.gl.utils.updateFormatDate = ($timeagoEls) -> + w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) -> $timeagoEls.each( -> $el = $(@) $el.attr('title', gl.utils.formatDate($el.attr('datetime'))) ) + $timeagoEls.timeago() if setTimeago + ) window diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index fdf084a8a82..0ae6e244602 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -141,9 +141,7 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#commits").innerHTML = data.html - $timeago = $('.js-timeago', 'div#commits') - gl.utils.updateFormatDate($timeago) - $timeago.timeago() + gl.utils.localTimeAgo($('.js-timeago', 'div#commits')) @commitsLoaded = true @scrollToElement("#commits") @@ -154,9 +152,7 @@ class @MergeRequestTabs url: "#{source}.json" + @_location.search success: (data) => document.querySelector("div#diffs").innerHTML = data.html - $timeago = $('.js-timeago', 'div#diffs') - gl.utils.updateFormatDate($timeago) - $timeago.timeago() + gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) $('div#diffs .js-syntax-highlight').syntaxHighlight() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @@ -169,9 +165,7 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#builds").innerHTML = data.html - $timeago = $('.js-timeago', 'div#builds') - gl.utils.updateFormatDate($timeago) - $timeago.timeago() + gl.utils.localTimeAgo($('.js-timeago', 'div#builds')) @buildsLoaded = true @scrollToElement("#builds") diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 02e52040e3c..a67890200dd 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -170,8 +170,7 @@ class @Notes .syntaxHighlight() # Update datetime format on the recent note - $timeago = $notesList.find("#note_#{note.id} .js-timeago") - gl.utils.updateFormatDate($timeago) + gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false) @initTaskList() @updateNotesCount(1) @@ -224,7 +223,7 @@ class @Notes # append new note to all matching discussions discussionContainer.append note_html - gl.utils.updateFormatDate($('.js-timeago', note_html)) + gl.utils.localTimeAgo($('.js-timeago', note_html), false) @updateNotesCount(1) @@ -355,9 +354,7 @@ class @Notes # Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html) - $timeago = $('.js-timeago', $html) - gl.utils.updateFormatDate($timeago) - $timeago.timeago() + gl.utils.localTimeAgo($('.js-timeago', $html)) $html.syntaxHighlight() $html.find('.js-task-list-container').taskList('enable') -- cgit v1.2.1 From 476cf23fc37d6db8d3fb412ce0b646f228d9aac4 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 11 Apr 2016 16:21:32 -0300 Subject: Allow to close invalid merge request --- app/models/commit.rb | 8 +++--- app/models/merge_request.rb | 9 +++++-- .../projects/merge_requests_controller_spec.rb | 29 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index d09876a07d9..11ecfcace14 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -150,13 +150,11 @@ class Commit end def hook_attrs(with_changed_files: false) - path_with_namespace = project.path_with_namespace - data = { id: id, message: safe_message, timestamp: committed_date.xmlschema, - url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}", + url: commit_url, author: { name: author_name, email: author_email @@ -170,6 +168,10 @@ class Commit data end + def commit_url + project.present? ? "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{id}" : "" + end + # Discover issues should be closed when this commit is pushed to a project's # default branch. def closes_issues(current_user = self.committer) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bf185cb5dd8..8292445bcac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -213,6 +213,8 @@ class MergeRequest < ActiveRecord::Base end def validate_branches + return if allow_broken + if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" end @@ -344,9 +346,12 @@ class MergeRequest < ActiveRecord::Base end def hook_attrs + source_hook_attrs = source_project.hook_attrs if source_project.present? + target_hook_attrs = target_project.hook_attrs if target_project.present? + attrs = { - source: source_project.hook_attrs, - target: target_project.hook_attrs, + source: source_hook_attrs, + target: target_hook_attrs, last_commit: nil, work_in_progress: work_in_progress? } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 75e6b6f45a7..0f2cd34132a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -157,6 +157,35 @@ describe Projects::MergeRequestsController do end end + describe 'PUT #update' do + context 'there is no source project' do + let(:project) { create(:project) } + let(:fork_project) { create(:forked_project_with_submodules) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + + before do + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + merge_request.reload + end + + it 'closes MR without errors' do + fork_project.destroy + + post :update, + namespace_id: project.namespace.path, + project_id: project.path, + id: merge_request.iid, + merge_request: { + state_event: 'close' + } + + expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) + expect(merge_request.reload.closed?).to be_truthy + end + end + end + describe "DELETE #destroy" do it "denies access to users unless they're admin or project owner" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid -- cgit v1.2.1 From 72e2c1db19fe9a3f45e5df89c03e8077e064ec8b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 11 Apr 2016 17:20:38 -0500 Subject: Update delete button --- app/views/shared/issuable/_form.html.haml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 757a3812deb..4b4078cb8a0 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -128,8 +128,6 @@ - else .pull-right - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, - method: :delete, class: 'btn btn-grouped' do - = icon('trash-o') - Delete + = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, + method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' -- cgit v1.2.1 From 05a611a0918f9a39de4ea3a051c2192c327f778d Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 17:25:18 -0500 Subject: Better control flow and added guard clause. --- lib/gitlab/saml/user.rb | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index dd77216be48..dba4bbfc899 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -26,15 +26,13 @@ module Gitlab @user ||= build_new_user end - unless @user.nil? - if external_users_enabled? - # Check if there is overlap between the user's groups and the external groups - # setting then set user as external or internal. - if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? - @user.external = false - else - @user.external = true - end + if external_users_enabled? && @user + # Check if there is overlap between the user's groups and the external groups + # setting then set user as external or internal. + if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty? + @user.external = false + else + @user.external = true end end @@ -50,11 +48,8 @@ module Gitlab end def changed? - if gl_user - gl_user.changed? || gl_user.identities.any?(&:changed?) - else - true - end + return true unless gl_user + gl_user.changed? || gl_user.identities.any?(&:changed?) end protected -- cgit v1.2.1 From 158bba238f06f43f2988552f02b32feb2bc245e6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 17:42:51 -0500 Subject: Set tooltips for new added labels --- app/assets/javascripts/labels_select.js.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d1fe116397a..cf0d4f9aae3 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -34,7 +34,7 @@ class @LabelsSelect labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> issues?label_name=<%= label.title %>"> - + <%= label.title %> @@ -165,6 +165,8 @@ class @LabelsSelect .html(template) $sidebarCollapsedValue.text(labelCount) + $('.has-tooltip', $value).tooltip(container: 'body') + $value .find('a') .each((i) -> -- cgit v1.2.1 From 61fc9aa87ea3752f3c7b853ab1cb102e53d392f2 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 11 Apr 2016 17:26:01 -0500 Subject: Better control flow. --- lib/gitlab/o_auth/user.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 6e099c26d8c..356e96fcbab 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -54,12 +54,10 @@ module Gitlab @user ||= build_new_user end - unless @user.nil? - if external_provider? - @user.external = true - else - @user.external = false - end + if external_provider? && @user + @user.external = true + elsif @user + @user.external = false end @user -- cgit v1.2.1 From d88d6e7619c3f976361df638e765472b499f1986 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 18:04:42 -0500 Subject: Hide top search form on the search page --- app/views/layouts/header/_default.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 0f3b8119379..17502148dce 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -8,7 +8,7 @@ .navbar-collapse.collapse %ul.nav.navbar-nav %li.hidden-sm.hidden-xs - = render 'layouts/search' + = render 'layouts/search' unless current_controller?(:search) %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('search') -- cgit v1.2.1 From 93a10f17e0c84074580eaf1b101af2a0fffd19ed Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 18:23:12 -0300 Subject: Reuse `User#notification_settings_for` when it's possible --- app/controllers/groups/notification_settings_controller.rb | 2 +- app/controllers/projects/notification_settings_controller.rb | 7 +++---- app/models/member.rb | 2 +- app/services/notification_service.rb | 4 ++-- spec/services/notification_service_spec.rb | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb index 20405a05190..1b46f26a378 100644 --- a/app/controllers/groups/notification_settings_controller.rb +++ b/app/controllers/groups/notification_settings_controller.rb @@ -1,6 +1,6 @@ class Groups::NotificationSettingsController < Groups::ApplicationController def update - notification_setting = group.notification_settings.find_by(user_id: current_user) + notification_setting = current_user.notification_settings_for(group) saved = notification_setting.update_attributes(notification_setting_params) render json: { saved: saved } diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb index da9034380af..90d294a4624 100644 --- a/app/controllers/projects/notification_settings_controller.rb +++ b/app/controllers/projects/notification_settings_controller.rb @@ -1,14 +1,13 @@ class Projects::NotificationSettingsController < Projects::ApplicationController def create - notification_setting = project.notification_settings.new(notification_setting_params) - notification_setting.user = current_user - saved = notification_setting.save + notification_setting = current_user.notification_settings_for(project) + saved = notification_setting.update_attributes(notification_setting_params) render json: { saved: saved } end def update - notification_setting = project.notification_settings.find_by(user_id: current_user) + notification_setting = current_user.notification_settings_for(project) saved = notification_setting.update_attributes(notification_setting_params) render json: { saved: saved } diff --git a/app/models/member.rb b/app/models/member.rb index 7d5af1d5c8a..60efafef211 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -167,7 +167,7 @@ class Member < ActiveRecord::Base end def notification_setting - @notification_setting ||= user.notification_settings.find_by(source: source) + @notification_setting ||= user.notification_settings_for(source) end private diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 0928dda349e..42ec1ac9e1a 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -355,10 +355,10 @@ class NotificationService users.reject do |user| next user.notification_level == level unless project - setting = user.notification_settings.find_by(source: project) + setting = user.notification_settings_for(project) if !setting && project.group - setting = user.notification_settings.find_by(source: project.group) + setting = user.notification_settings_for(project.group) end # reject users who globally set mention notification and has no setting per project/group diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index c4d52584a4b..d7c72dc0811 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -89,8 +89,8 @@ describe NotificationService, services: true do note.project.group.add_user(@u_watcher, GroupMember::MASTER) note.project.save - @u_watcher.notification_settings.find_by(source: note.project).participating! - @u_watcher.notification_settings.find_by(source: note.project.group).global! + @u_watcher.notification_settings_for(note.project).participating! + @u_watcher.notification_settings_for(note.project.group).global! ActionMailer::Base.deliveries.clear end -- cgit v1.2.1 From bee28e1785ad7844bd518c19106beee7d8a4c560 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 18:57:18 -0300 Subject: Requires user to be signed in when changing notification settings --- .../groups/notification_settings_controller.rb | 2 ++ .../projects/notification_settings_controller.rb | 2 ++ .../notification_settings_controller_spec.rb | 17 ++++++++++++ .../notification_settings_controller_spec.rb | 31 ++++++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 spec/controllers/groups/notification_settings_controller_spec.rb create mode 100644 spec/controllers/projects/notification_settings_controller_spec.rb diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb index 1b46f26a378..de13b16ccf2 100644 --- a/app/controllers/groups/notification_settings_controller.rb +++ b/app/controllers/groups/notification_settings_controller.rb @@ -1,4 +1,6 @@ class Groups::NotificationSettingsController < Groups::ApplicationController + before_action :authenticate_user! + def update notification_setting = current_user.notification_settings_for(group) saved = notification_setting.update_attributes(notification_setting_params) diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb index 90d294a4624..e536725c5b1 100644 --- a/app/controllers/projects/notification_settings_controller.rb +++ b/app/controllers/projects/notification_settings_controller.rb @@ -1,4 +1,6 @@ class Projects::NotificationSettingsController < Projects::ApplicationController + before_action :authenticate_user! + def create notification_setting = current_user.notification_settings_for(project) saved = notification_setting.update_attributes(notification_setting_params) diff --git a/spec/controllers/groups/notification_settings_controller_spec.rb b/spec/controllers/groups/notification_settings_controller_spec.rb new file mode 100644 index 00000000000..3572535d61c --- /dev/null +++ b/spec/controllers/groups/notification_settings_controller_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Groups::NotificationSettingsController do + let(:group) { create(:group) } + + describe '#update' do + context 'when not authorized' do + it 'redirects to sign in page' do + put :update, + group_id: group.to_param, + notification_setting: { level: NotificationSetting.levels[:participating] } + + expect(response).to redirect_to(new_user_session_path) + end + end + end +end diff --git a/spec/controllers/projects/notification_settings_controller_spec.rb b/spec/controllers/projects/notification_settings_controller_spec.rb new file mode 100644 index 00000000000..7e32a75b812 --- /dev/null +++ b/spec/controllers/projects/notification_settings_controller_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Projects::NotificationSettingsController do + let(:project) { create(:empty_project) } + + describe '#create' do + context 'when not authorized' do + it 'redirects to sign in page' do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + notification_setting: { level: NotificationSetting.levels[:participating] } + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + describe '#update' do + context 'when not authorized' do + it 'redirects to sign in page' do + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + notification_setting: { level: NotificationSetting.levels[:participating] } + + expect(response).to redirect_to(new_user_session_path) + end + end + end +end -- cgit v1.2.1 From fe58c1f13cc0758bbbd8f85b8794b458b3a72b55 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 19:29:31 -0300 Subject: Fix partial for update project notifications --- app/views/projects/buttons/_notifications.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index 2b9d8f2ac81..49f541399f2 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,5 +1,5 @@ - if @notification_setting - = form_for [@project.namespace.becomes(Namespace), @project, @notification_setting], remote: true, html: { class: 'inline', id: 'notification-form' } do |f| + = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), remote: true, html: { class: 'inline', id: 'notification-form' } do |f| = f.hidden_field :level %span.dropdown %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} -- cgit v1.2.1 From c162e0278cb845f6209e926d49474926b6a45956 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 11 Apr 2016 23:07:06 -0700 Subject: Check and report import job status to help diagnose issues with forking --- app/models/project.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 3e1f04b4158..6298dc8d1c8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -388,9 +388,15 @@ class Project < ActiveRecord::Base def add_import_job if forked? - RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) + job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) else - RepositoryImportWorker.perform_async(self.id) + job_id = RepositoryImportWorker.perform_async(self.id) + end + + if job_id + Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}" + else + Rails.logger.error "Import job failed to start for #{path_with_namespace}" end end -- cgit v1.2.1 From a6ba94dbd109637b996246601f1bc2b62dc0a8d7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 09:32:12 +0100 Subject: Filtering by any label keeps the text on the toggle button --- app/assets/javascripts/labels_select.js.coffee | 2 +- spec/features/issues/filter_issues_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d1fe116397a..90385621879 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -218,7 +218,7 @@ class @LabelsSelect selectable: true toggleLabel: (selected) -> - if selected and selected.title isnt 'Any Label' + if selected and selected.title? selected.title else defaultLabel diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 91de06e31f9..69b22232f10 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -84,16 +84,25 @@ describe 'Filter issues', feature: true do it 'should filter by any label' do find('.dropdown-menu-labels a', text: 'Any Label').click + page.within '.labels-filter' do + expect(page).to have_content 'Any Label' + end expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Label') end it 'should filter by no label' do find('.dropdown-menu-labels a', text: 'No Label').click + page.within '.labels-filter' do + expect(page).to have_content 'No Label' + end expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') end it 'should filter by no label' do find('.dropdown-menu-labels a', text: label.title).click + page.within '.labels-filter' do + expect(page).to have_content label.title + end expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end end -- cgit v1.2.1 From 8299129382e59a0b407be8c476b8ae2d0704d688 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 12:24:16 +0200 Subject: Instrument all service classes Fixes gitlab-org/gitlab-ce#15162 --- config/initializers/metrics.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 1b445bbbd10..9bd43dad0a0 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -1,4 +1,5 @@ if Gitlab::Metrics.enabled? + require 'pathname' require 'influxdb' require 'connection_pool' require 'method_source' @@ -98,6 +99,17 @@ if Gitlab::Metrics.enabled? config.instrument_methods(Gitlab::ReferenceExtractor) config.instrument_instance_methods(Gitlab::ReferenceExtractor) + + # Instrument all service classes + services = Rails.root.join('app', 'services') + + Dir[services.join('**', '*.rb')].each do |file_path| + path = Pathname.new(file_path).relative_path_from(services) + const = path.to_s.sub('.rb', '').camelize.constantize + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end end GC::Profiler.enable -- cgit v1.2.1 From 10080ce3624e199bd770a924d8d7f178008d4cb7 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 12:32:34 +0200 Subject: API: Expose updated_at for notes --- CHANGELOG | 1 + doc/api/notes.md | 3 +++ lib/api/entities.rb | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c08b148c3ac..75743872801 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.7.0 (unreleased) - API: Expose user location (Robert Schilling) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 + - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) v 8.6.5 - Fix importing from GitHub Enterprise. !3529 diff --git a/doc/api/notes.md b/doc/api/notes.md index d4d63e825ab..9168ab00d7e 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -32,6 +32,7 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:22:45Z", + "updated_at": "2013-10-02T10:22:45Z", "system": true, "upvote": false, "downvote": false, @@ -51,6 +52,7 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:56:03Z", + "updated_at": "2013-10-02T09:56:03Z", "system": true, "upvote": false, "downvote": false, @@ -223,6 +225,7 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T08:57:14Z", + "updated_at": "2013-10-02T08:57:14Z", "system": false, "upvote": false, "downvote": false, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5ed9b7b1d9f..939469b3886 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -212,7 +212,7 @@ module API expose :note, as: :body expose :attachment_identifier, as: :attachment expose :author, using: Entities::UserBasic - expose :created_at + expose :created_at, :updated_at expose :system?, as: :system expose :noteable_id, :noteable_type # upvote? and downvote? are deprecated, always return false -- cgit v1.2.1 From 9d03e8fd5c564b20f3a1ef18583aa4c7c2b27cfc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 7 Apr 2016 10:59:54 +0200 Subject: API: Add iid filter to milestones --- CHANGELOG | 1 + lib/api/milestones.rb | 2 ++ spec/requests/api/milestones_spec.rb | 8 +++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c08b148c3ac..e5e027506c2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.7.0 (unreleased) - Add default scope to projects to exclude projects pending deletion - Ensure empty recipients are rejected in BuildsEmailService - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) + - API: Fix milestone filtering by `iid` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 0f3f505fa05..84b4d4cdd6d 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -21,6 +21,7 @@ module API # state (optional) - Return "active" or "closed" milestones # Example Request: # GET /projects/:id/milestones + # GET /projects/:id/milestones?iid=42 # GET /projects/:id/milestones?state=active # GET /projects/:id/milestones?state=closed get ":id/milestones" do @@ -28,6 +29,7 @@ module API milestones = user_project.milestones milestones = filter_milestones_state(milestones, params[:state]) + milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present? present paginate(milestones), with: Entities::Milestone end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index d97bf6d38ff..344f0fe0b7f 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -50,10 +50,12 @@ describe API::API, api: true do end it 'should return a project milestone by iid' do - get api("/projects/#{project.id}/milestones?iid=#{milestone.iid}", user) + get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user) + expect(response.status).to eq 200 - expect(json_response.first['title']).to eq milestone.title - expect(json_response.first['id']).to eq milestone.id + expect(json_response.size).to eq(1) + expect(json_response.first['title']).to eq closed_milestone.title + expect(json_response.first['id']).to eq closed_milestone.id end it 'should return 401 error if user not authenticated' do -- cgit v1.2.1 From 20d4ca4cc3599b4335b73fd7c3bb0354efe397b4 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 13:59:50 +0200 Subject: API: Ability to retrieve a single tag --- doc/api/tags.md | 46 +++++++++++++++++++++++++++++++++++++++++- lib/api/tags.rb | 14 +++++++++++++ spec/requests/api/tags_spec.rb | 17 ++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index 17d12e9cc62..d428ca0ef5c 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -38,6 +38,50 @@ Parameters: ] ``` +## Get a single repository tag + +Get a specific repository tag determined by its name. It returns 200 together +with the tag information if the tag exists. It returns 404 if the tag does not +exist. + +``` +GET /projects/:id/repository/tags/:tag_name +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `tag_name` | string | yes | The name of the tag | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/tags/v1.0.0 +``` + +Example Response: + +```json +{ + "name": "v5.0.0", + "message": null, + "commit": { + "id": "60a8ff033665e1207714d6670fcd7b65304ec02f", + "message": "v5.0.0\n", + "parent_ids": [ + "f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b" + ], + "authored_date": "2015-02-01T21:56:31.000+01:00", + "author_name": "Arthur Verschaeve", + "author_email": "contact@arthurverschaeve.be", + "committed_date": "2015-02-01T21:56:31.000+01:00", + "committer_name": "Arthur Verschaeve", + "committer_email": "contact@arthurverschaeve.be" + }, + "release": null +} +``` + ## Create a new tag Creates a new tag in the repository that points to the supplied ref. @@ -148,4 +192,4 @@ Parameters: "tag_name": "1.0.0", "description": "Amazing release. Wow" } -``` \ No newline at end of file +``` diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 2d8a9e51bb9..731a68082ba 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -16,6 +16,20 @@ module API with: Entities::RepoTag, project: user_project end + # Get a single repository tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # Example Request: + # GET /projects/:id/repository/tags/:tag_name + get ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do + tag = user_project.repository.find_tag(params[:tag_name]) + not_found!('Tag') unless tag + + present tag, with: Entities::RepoTag, project: user_project + end + # Create tag # # Parameters: diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index a15be07ed57..acbd9c3e332 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -40,6 +40,23 @@ describe API::API, api: true do end end + describe "GET /projects/:id/repository/tags/:tag_name" do + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + + it 'should return a specific tag' do + get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) + + expect(response.status).to eq(200) + expect(json_response['name']).to eq(tag_name) + end + + it 'should return 404 for an invalid tag name' do + get api("/projects/#{project.id}/repository/tags/foobar", user) + + expect(response.status).to eq(404) + end + end + describe 'POST /projects/:id/repository/tags' do context 'lightweight tags' do it 'should create a new tag' do -- cgit v1.2.1 From b3d8b995c3a5dddf1b27c00e2420effb62e548b4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 14:10:35 +0200 Subject: Un-instrument Banzai::ReferenceExtractor Instrumenting this class together with Gitlab::ReferenceExtractor causes a StackError for some reason. Since Gitlab::ReferenceExtractor has most of the interesting code we'll only instrument that class. --- config/initializers/metrics.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 1b445bbbd10..e85ac22f10f 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -85,9 +85,6 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(const) end - config.instrument_methods(Banzai::ReferenceExtractor) - config.instrument_instance_methods(Banzai::ReferenceExtractor) - config.instrument_methods(Banzai::Renderer) config.instrument_methods(Banzai::Querying) -- cgit v1.2.1 From f81352f531b22d02b1b80cfbd6daed809ea8cf5d Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 12:50:21 +0200 Subject: Fix minor styling issues from code review --- doc/api/tags.md | 4 ++-- lib/api/tags.rb | 6 +++--- spec/requests/api/tags_spec.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index d428ca0ef5c..ac9fac92f4c 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -40,8 +40,8 @@ Parameters: ## Get a single repository tag -Get a specific repository tag determined by its name. It returns 200 together -with the tag information if the tag exists. It returns 404 if the tag does not +Get a specific repository tag determined by its name. It returns `200` together +with the tag information if the tag exists. It returns `404` if the tag does not exist. ``` diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 731a68082ba..d1a10479e44 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -19,15 +19,15 @@ module API # Get a single repository tag # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project # tag_name (required) - The name of the tag # Example Request: # GET /projects/:id/repository/tags/:tag_name - get ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do + get ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do tag = user_project.repository.find_tag(params[:tag_name]) not_found!('Tag') unless tag - present tag, with: Entities::RepoTag, project: user_project + present tag, with: Entities::RepoTag, project: user_project end # Create tag diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index acbd9c3e332..9f9c3b1cf4c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -40,17 +40,17 @@ describe API::API, api: true do end end - describe "GET /projects/:id/repository/tags/:tag_name" do + describe 'GET /projects/:id/repository/tags/:tag_name' do let(:tag_name) { project.repository.tag_names.sort.reverse.first } - it 'should return a specific tag' do + it 'returns a specific tag' do get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) expect(response.status).to eq(200) expect(json_response['name']).to eq(tag_name) end - it 'should return 404 for an invalid tag name' do + it 'returns 404 for an invalid tag name' do get api("/projects/#{project.id}/repository/tags/foobar", user) expect(response.status).to eq(404) -- cgit v1.2.1 From ba21c00f01bf4274d0e4cc3892293fc1e581b260 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 01:21:02 +0200 Subject: Delete notes via API --- CHANGELOG | 5 ++++ app/controllers/projects/notes_controller.rb | 5 ++-- app/services/notes/delete_service.rb | 8 +++++ doc/api/notes.md | 45 ++++++++++++++++++++++++++++ lib/api/notes.rb | 17 +++++++++++ spec/requests/api/notes_spec.rb | 43 ++++++++++++++++++++++++++ spec/services/notes/delete_service_spec.rb | 15 ++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 app/services/notes/delete_service.rb create mode 100644 spec/services/notes/delete_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 593e8f77ab4..f4dbab8889a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,6 +46,11 @@ v 8.6.5 - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 - Unblock user when active_directory is disabled and it can be found !3550 - Fix a 2FA authentication spoofing vulnerability. + - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) + +v 8.6.5 (unreleased) + - Only update repository language if it is not set to improve performance + - Check permissions when user attempts to import members from another project v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 1b9dd568043..a9a69573eed 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -39,8 +39,7 @@ class Projects::NotesController < Projects::ApplicationController def destroy if note.editable? - note.destroy - note.reset_events_cache + Notes::DeleteService.new(project, current_user).execute(note) end respond_to do |format| @@ -73,7 +72,7 @@ class Projects::NotesController < Projects::ApplicationController note = noteable.notes.find_by(data) if note - note.destroy + Notes::DeleteService.new(project, current_user).execute(note) else Notes::CreateService.new(project, current_user, note_params).execute end diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb new file mode 100644 index 00000000000..7f1b30ec84e --- /dev/null +++ b/app/services/notes/delete_service.rb @@ -0,0 +1,8 @@ +module Notes + class DeleteService < BaseService + def execute(note) + note.destroy + note.reset_events_cache + end + end +end diff --git a/doc/api/notes.md b/doc/api/notes.md index 9168ab00d7e..82494bf83ff 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -105,6 +105,21 @@ Parameters: - `note_id` (required) - The ID of a note - `body` (required) - The content of a note +### Delete existing issue note + +Deletes an existing note of an issue. On success, this API method returns 200. +If the note does not exist, the API returns 404. + +``` +DELETE /projects/:id/issues/:issue_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `issue_id` (required) - The ID of an issue +- `note_id` (required) - The ID of a note + ## Snippets ### List all snippet notes @@ -182,6 +197,21 @@ Parameters: - `note_id` (required) - The ID of a note - `body` (required) - The content of a note +### Delete existing snippet note + +Deletes an existing note of a snippet. On success, this API method returns 200. +If the note does not exist, the API returns 404. + +``` +DELETE /projects/:id/snippets/:snippet_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `snippet_id` (required) - The ID of a snippet +- `note_id` (required) - The ID of a note + ## Merge Requests ### List all merge request notes @@ -262,3 +292,18 @@ Parameters: - `merge_request_id` (required) - The ID of a merge request - `note_id` (required) - The ID of a note - `body` (required) - The content of a note + +### Delete existing snippet note + +Deletes an existing note of a merge request. On success, this API method returns +200. If the note does not exist, the API returns 404. + +``` +DELETE /projects/:id/merge_requests/:merge_request_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - The ID of a merge request +- `note_id` (required) - The ID of a note diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 174473f5371..fd1704d395c 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -112,6 +112,23 @@ module API end end + # Delete a +notable+ note + # + # Parameters: + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue, MR, or snippet + # node_id (required) - The ID of a note + # Example Request: + # DELETE /projects/:id/issues/:noteable_id/notes/:note_id + # DELETE /projects/:id/snippets/:noteable_id/notes/:node_id + delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + note = user_project.notes.find(params[:note_id]) + not_found!('Note') unless note + authorize! :admin_note, note + ::Notes::DeleteService.new(user_project, current_user).execute(note) + true + end end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 39f9a06fe1b..23d3c63bc1c 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -241,4 +241,47 @@ describe API::API, api: true do end end + describe ':id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id' do + context 'when noteable is an Issue' do + it 'should delete a note' do + delete api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user) + expect(response.status).to eq(200) + end + + it 'should return a 404 error when note id not found' do + delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + expect(response.status).to eq(404) + end + end + + context 'when noteable is a Snippet' do + it 'should delete a note' do + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/#{snippet_note.id}", user) + expect(response.status).to eq(200) + end + + it 'should return a 404 error when note id not found' do + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/123", user) + expect(response.status).to eq(404) + end + end + + context 'when noteable is a Merge Request' do + it 'should delete a note' do + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/#{merge_request_note.id}", user) + expect(response.status).to eq(200) + end + + it 'should return a 404 error when note id not found' do + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/123", user) + expect(response.status).to eq(404) + end + end + end + end diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/delete_service_spec.rb new file mode 100644 index 00000000000..88e71c135d3 --- /dev/null +++ b/spec/services/notes/delete_service_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Notes::DeleteService, services: true do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } + let(:note) { create(:note, project: project, noteable: issue, author: user, note: 'Note') } + + describe '#execute' do + it 'deletes a note' do + Notes::DeleteService.new(project, user).execute(note) + expect(project.issues.find(issue.id).notes).to_not include(note) + end + end +end -- cgit v1.2.1 From 9aefaa41ab1442f81ffc15ad9a8279bd1e92c91a Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 19:04:17 +0200 Subject: Fix code review issues --- app/controllers/projects/notes_controller.rb | 2 +- doc/api/notes.md | 130 +++++++++++++++++++++++---- lib/api/notes.rb | 6 +- spec/requests/api/notes_spec.rb | 20 ++++- spec/services/notes/delete_service_spec.rb | 9 +- 5 files changed, 140 insertions(+), 27 deletions(-) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index a9a69573eed..707a0d0e5c6 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -72,7 +72,7 @@ class Projects::NotesController < Projects::ApplicationController note = noteable.notes.find_by(data) if note - Notes::DeleteService.new(project, current_user).execute(note) + note.destroy else Notes::CreateService.new(project, current_user, note_params).execute end diff --git a/doc/api/notes.md b/doc/api/notes.md index 82494bf83ff..2e0936f11b5 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -105,10 +105,10 @@ Parameters: - `note_id` (required) - The ID of a note - `body` (required) - The content of a note -### Delete existing issue note +### Delete an issue note -Deletes an existing note of an issue. On success, this API method returns 200. -If the note does not exist, the API returns 404. +Deletes an existing note of an issue. On success, this API method returns 200 +and the deleted note. If the note does not exist, the API returns 404. ``` DELETE /projects/:id/issues/:issue_id/notes/:note_id @@ -116,9 +116,41 @@ DELETE /projects/:id/issues/:issue_id/notes/:note_id Parameters: -- `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue -- `note_id` (required) - The ID of a note +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of an issue | +| `note_id` | integer | yes | The ID of a note | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/11/notes/636 +``` + +Example Response: + +```json +{ + "id": 636, + "body": "This is a good idea.", + "attachment": null, + "author": { + "id": 1, + "username": "pipin", + "email": "admin@example.com", + "name": "Pip", + "state": "active", + "created_at": "2013-09-30T13:46:01Z", + "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/pipin" + }, + "created_at": "2016-04-05T22:10:44.164Z", + "system": false, + "noteable_id": 11, + "noteable_type": "Issue", + "upvote": false, + "downvote": false +} +``` ## Snippets @@ -197,10 +229,10 @@ Parameters: - `note_id` (required) - The ID of a note - `body` (required) - The content of a note -### Delete existing snippet note +### Delete a snippet note -Deletes an existing note of a snippet. On success, this API method returns 200. -If the note does not exist, the API returns 404. +Deletes an existing note of a snippet. On success, this API method returns 200 +and the deleted note. If the note does not exist, the API returns 404. ``` DELETE /projects/:id/snippets/:snippet_id/notes/:note_id @@ -208,9 +240,41 @@ DELETE /projects/:id/snippets/:snippet_id/notes/:note_id Parameters: -- `id` (required) - The ID of a project -- `snippet_id` (required) - The ID of a snippet -- `note_id` (required) - The ID of a note +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `snippet_id` | integer | yes | The ID of a snippet | +| `note_id` | integer | yes | The ID of a note | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/snippets/52/notes/1659 +``` + +Example Response: + +```json +{ + "id": 1659, + "body": "This is a good idea.", + "attachment": null, + "author": { + "id": 1, + "username": "pipin", + "email": "admin@example.com", + "name": "Pip", + "state": "active", + "created_at": "2013-09-30T13:46:01Z", + "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/pipin" + }, + "created_at": "2016-04-06T16:51:53.239Z", + "system": false, + "noteable_id": 52, + "noteable_type": "Snippet", + "upvote": false, + "downvote": false +} +``` ## Merge Requests @@ -293,10 +357,10 @@ Parameters: - `note_id` (required) - The ID of a note - `body` (required) - The content of a note -### Delete existing snippet note +### Delete a merge request note Deletes an existing note of a merge request. On success, this API method returns -200. If the note does not exist, the API returns 404. +200 and the deleted note. If the note does not exist, the API returns 404. ``` DELETE /projects/:id/merge_requests/:merge_request_id/notes/:note_id @@ -304,6 +368,38 @@ DELETE /projects/:id/merge_requests/:merge_request_id/notes/:note_id Parameters: -- `id` (required) - The ID of a project -- `merge_request_id` (required) - The ID of a merge request -- `note_id` (required) - The ID of a note +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of a merge request | +| `note_id` | integer | yes | The ID of a note | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/7/notes/1602 +``` + +Example Response: + +```json +{ + "id": 1602, + "body": "This is a good idea.", + "attachment": null, + "author": { + "id": 1, + "username": "pipin", + "email": "admin@example.com", + "name": "Pip", + "state": "active", + "created_at": "2013-09-30T13:46:01Z", + "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/pipin" + }, + "created_at": "2016-04-05T22:11:59.923Z", + "system": false, + "noteable_id": 7, + "noteable_type": "MergeRequest", + "upvote": false, + "downvote": false +} +``` diff --git a/lib/api/notes.rb b/lib/api/notes.rb index fd1704d395c..2ed986b9ec5 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -112,10 +112,9 @@ module API end end - # Delete a +notable+ note + # Delete a +noteable+ note # # Parameters: - # Parameters: # id (required) - The ID of a project # noteable_id (required) - The ID of an issue, MR, or snippet # node_id (required) - The ID of a note @@ -124,10 +123,9 @@ module API # DELETE /projects/:id/snippets/:noteable_id/notes/:node_id delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do note = user_project.notes.find(params[:note_id]) - not_found!('Note') unless note authorize! :admin_note, note ::Notes::DeleteService.new(user_project, current_user).execute(note) - true + present note, with: Entities::Note end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 23d3c63bc1c..b35e67b5bd3 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -241,16 +241,22 @@ describe API::API, api: true do end end - describe ':id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id' do + describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do context 'when noteable is an Issue' do it 'should delete a note' do delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user) + expect(response.status).to eq(404) end it 'should return a 404 error when note id not found' do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + expect(response.status).to eq(404) end end @@ -259,12 +265,18 @@ describe API::API, api: true do it 'should delete a note' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/#{snippet_note.id}", user) + expect(response.status).to eq(404) end it 'should return a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/123", user) + expect(response.status).to eq(404) end end @@ -273,12 +285,18 @@ describe API::API, api: true do it 'should delete a note' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) + expect(response.status).to eq(200) + # Check if note is really deleted + delete api("/projects/#{project.id}/merge_requests/"\ + "#{merge_request.id}/notes/#{merge_request_note.id}", user) + expect(response.status).to eq(404) end it 'should return a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/123", user) + expect(response.status).to eq(404) end end diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/delete_service_spec.rb index 88e71c135d3..07aa57c4642 100644 --- a/spec/services/notes/delete_service_spec.rb +++ b/spec/services/notes/delete_service_spec.rb @@ -3,13 +3,14 @@ require 'spec_helper' describe Notes::DeleteService, services: true do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } - let(:user) { create(:user) } - let(:note) { create(:note, project: project, noteable: issue, author: user, note: 'Note') } + let(:note) { create(:note, project: project, noteable: issue) } describe '#execute' do it 'deletes a note' do - Notes::DeleteService.new(project, user).execute(note) - expect(project.issues.find(issue.id).notes).to_not include(note) + project = note.project + described_class.new(project, note.author).execute(note) + + expect(project.issues.find(issue.id).notes).not_to include(note) end end end -- cgit v1.2.1 From 49484f9a2c2efb670f044028c555f00378484dd4 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 14:20:58 +0200 Subject: Fix changelog entry --- CHANGELOG | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f4dbab8889a..5dd1cf5f286 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.7.0 (unreleased) - Ensure empty recipients are rejected in BuildsEmailService - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling) + - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) @@ -46,11 +47,6 @@ v 8.6.5 - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 - Unblock user when active_directory is disabled and it can be found !3550 - Fix a 2FA authentication spoofing vulnerability. - - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - -v 8.6.5 (unreleased) - - Only update repository language if it is not set to improve performance - - Check permissions when user attempts to import members from another project v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) -- cgit v1.2.1 From 6dbcb880cc72f7511358612bbc76e2ab9ded14c5 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 8 Apr 2016 12:41:37 +0200 Subject: Allow a project member to leave the projected through the API --- CHANGELOG | 1 + doc/api/projects.md | 6 ++++-- lib/api/project_members.rb | 13 +++++++++---- spec/requests/api/project_members_spec.rb | 20 +++++++++++++++++--- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 593e8f77ab4..3c03b26cf51 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.7.0 (unreleased) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) + - API: User can leave a project through the API when not master or owner. !3613 v 8.6.5 - Fix importing from GitHub Enterprise. !3529 diff --git a/doc/api/projects.md b/doc/api/projects.md index 3a909a2bc87..ab716c229dc 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -780,8 +780,10 @@ Parameters: - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project - `user_id` (required) - The ID of a team member -This method is idempotent and can be called multiple times with the same parameters. -Revoking team membership for a user who is not currently a team member is considered success. +This method removes the project member if the user has the proper access rights to do so. +It returns a status code 403 if the member does not have the proper rights to perform this action. +In all other cases this method is idempotent and revoking team membership for a user who is not +currently a team member is considered success. Please note that the returned JSON currently differs slightly. Thus you should not rely on the returned JSON structure. diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index c756bb479fc..4aefdf319c6 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -93,12 +93,17 @@ module API # Example Request: # DELETE /projects/:id/members/:user_id delete ":id/members/:user_id" do - authorize! :admin_project, user_project project_member = user_project.project_members.find_by(user_id: params[:user_id]) - unless project_member.nil? - project_member.destroy - else + + unless current_user.can?(:admin_project, user_project) || + current_user.can?(:destroy_project_member, project_member) + forbidden! + end + + if project_member.nil? { message: "Access revoked", id: params[:user_id].to_i } + else + project_member.destroy end end end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 4301588b16a..c112ca5e3ca 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -118,8 +118,10 @@ describe API::API, api: true do end describe "DELETE /projects/:id/members/:user_id" do - before { project_member } - before { project_member2 } + before do + project_member + project_member2 + end it "should remove user from project team" do expect do @@ -132,6 +134,7 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) end.to_not change { ProjectMember.count } + expect(response.status).to eq(200) end it "should return 200 if team member already removed" do @@ -145,8 +148,19 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/1000000", user) end.to change { ProjectMember.count }.by(0) expect(response.status).to eq(200) - expect(json_response['message']).to eq("Access revoked") expect(json_response['id']).to eq(1000000) + expect(json_response['message']).to eq('Access revoked') + end + + context 'when the user is not an admin or owner' do + it 'can leave the project' do + expect do + delete api("/projects/#{project.id}/members/#{user3.id}", user3) + end.to change { ProjectMember.count }.by(-1) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(project_member2.id) + end end end end -- cgit v1.2.1 From 08a217cfcfc2e2387c6d4070ed8b0e121f47f50c Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 15:28:09 +0200 Subject: Add changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c08b148c3ac..85539b1a12a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.7.0 (unreleased) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu) + - API: Ability to retrieve a single tag (Robert Schilling) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - Fix admin/projects when using visibility levels on search (PotHix) -- cgit v1.2.1 From 050110c9c90d39ea9c128ac183c13a37250fd7a1 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Tue, 12 Apr 2016 14:41:34 +0100 Subject: hide help block when user is creating a new project inside a group --- CHANGELOG | 1 + .../javascripts/behaviors/requires_input.js.coffee | 16 +++++++++++++++- app/helpers/namespaces_helper.rb | 12 ++++++++++-- app/views/projects/new.html.haml | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 593e8f77ab4..fa6d4d7551d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.7.0 (unreleased) - API: Fix milestone filtering by `iid` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups + - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee index 79d750d1847..0faa570ce13 100644 --- a/app/assets/javascripts/behaviors/requires_input.js.coffee +++ b/app/assets/javascripts/behaviors/requires_input.js.coffee @@ -35,4 +35,18 @@ $.fn.requiresInput = -> $form.on 'change input', fieldSelector, requireInput $ -> - $('form.js-requires-input').requiresInput() + $form = $('form.js-requires-input') + $form.requiresInput() + + # Hide or Show the help block when creating a new project + # based on the option selected + hideOrShowHelpBlock = (form) -> + selected = $('.js-select-namespace option:selected') + if selected.length and selected.data('options-parent') is 'groups' + return form.find('.help-block').hide() + else if selected.length + form.find('.help-block').show() + + hideOrShowHelpBlock($form) + + $('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form) diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index faba418c4db..94c6b548ecd 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -3,8 +3,16 @@ module NamespacesHelper groups = current_user.owned_groups + current_user.masters_groups users = [current_user.namespace] - group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ] - users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ] + data_attr_group = { 'data-options-parent' => 'groups' } + data_attr_users = { 'data-options-parent' => 'users' } + + group_opts = [ + "Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.path : g.human_name, g.id, data_attr_group] } + ] + + users_opts = [ + "Users", users.sort_by(&:human_name).map { |u| [display_path ? u.path : u.human_name, u.id, data_attr_users] } + ] options = [] options << group_opts diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 25233112132..a4c6094c69a 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -19,7 +19,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1} + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} .input-group-addon \/ - else -- cgit v1.2.1 From dc39c8372d760eceba50a35505dad8663b9e851e Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 15:43:29 +0200 Subject: Adapt tests to new testing guidelines --- lib/api/notes.rb | 2 ++ spec/requests/api/notes_spec.rb | 12 ++++++------ spec/services/notes/delete_service_spec.rb | 9 ++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 2ed986b9ec5..a1c98f5e8ff 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -124,7 +124,9 @@ module API delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note + ::Notes::DeleteService.new(user_project, current_user).execute(note) + present note, with: Entities::Note end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index b35e67b5bd3..a467bc935af 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -243,7 +243,7 @@ describe API::API, api: true do describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do context 'when noteable is an Issue' do - it 'should delete a note' do + it 'deletes a note' do delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) @@ -254,7 +254,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it 'should return a 404 error when note id not found' do + it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) expect(response.status).to eq(404) @@ -262,7 +262,7 @@ describe API::API, api: true do end context 'when noteable is a Snippet' do - it 'should delete a note' do + it 'deletes a note' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) @@ -273,7 +273,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it 'should return a 404 error when note id not found' do + it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/123", user) @@ -282,7 +282,7 @@ describe API::API, api: true do end context 'when noteable is a Merge Request' do - it 'should delete a note' do + it 'deletes a note' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) @@ -293,7 +293,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it 'should return a 404 error when note id not found' do + it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/123", user) diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/delete_service_spec.rb index 07aa57c4642..1d0a747a480 100644 --- a/spec/services/notes/delete_service_spec.rb +++ b/spec/services/notes/delete_service_spec.rb @@ -1,13 +1,12 @@ require 'spec_helper' describe Notes::DeleteService, services: true do - let(:project) { create(:empty_project) } - let(:issue) { create(:issue, project: project) } - let(:note) { create(:note, project: project, noteable: issue) } - describe '#execute' do it 'deletes a note' do - project = note.project + project = create(:empty_project) + issue = create(:issue, project: project) + note = create(:note, project: project, noteable: issue) + described_class.new(project, note.author).execute(note) expect(project.issues.find(issue.id).notes).not_to include(note) -- cgit v1.2.1 From d0cdc2ee73c8421906fbd011d0f44d638616a864 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 7 Apr 2016 10:12:49 +0200 Subject: API: Ability to update a group --- CHANGELOG | 1 + doc/api/groups.md | 81 ++++++++++++++++++++++++++++++++++++++++ lib/api/groups.rb | 30 ++++++++++++++- spec/requests/api/groups_spec.rb | 36 ++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 593e8f77ab4..ac686c4bde6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.7.0 (unreleased) - Make HTTP(s) label consistent on clone bar (Stan Hu) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API + - API: Ability to update a group (Robert Schilling) - Fix Error 500 after renaming a project path (Stan Hu) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) diff --git a/doc/api/groups.md b/doc/api/groups.md index d1b5c9f5f04..59046190d0f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -126,6 +126,87 @@ Parameters: - `id` (required) - The ID or path of a group - `project_id` (required) - The ID of a project +## Update group + +Updates the project group. Only available to group owners and administrators. + +``` +PUT /groups/:id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the group | +| `name` | string | no | The name of the group | +| `path` | string | no | The path of the group | +| `description` | string | no | The description of the group | +| `visibility_level` | integer | no | The visibility_level of the group. 0 for private, 10 for internal, 20 for public. | + +```bash +curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" + +``` + +Example response: + +```json +{ + "id": 5, + "name": "Experimental", + "path": "h5bp", + "description": "foo", + "visibility_level": 10, + "avatar_url": null, + "web_url": "http://gitlab.example.com/groups/h5bp", + "projects": [ + { + "id": 9, + "description": "foo", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 10, + "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git", + "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git", + "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate", + "name": "Html5 Boilerplate", + "name_with_namespace": "Experimental / Html5 Boilerplate", + "path": "html5-boilerplate", + "path_with_namespace": "h5bp/html5-boilerplate", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": true, + "created_at": "2016-04-05T21:40:50.169Z", + "last_activity_at": "2016-04-06T16:52:08.432Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 5, + "name": "Experimental", + "path": "h5bp", + "owner_id": null, + "created_at": "2016-04-05T21:40:49.152Z", + "updated_at": "2016-04-07T08:07:48.466Z", + "description": "foo", + "avatar": { + "url": null + }, + "share_with_group_lock": false, + "visibility_level": 10 + }, + "avatar_url": null, + "star_count": 1, + "forks_count": 0, + "open_issues_count": 3, + "public_builds": true + } + ] +} +``` + ## Remove group Removes group with all projects inside. diff --git a/lib/api/groups.rb b/lib/api/groups.rb index c165de21a75..964f691afcc 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -23,8 +23,10 @@ module API # Create group. Available only for users who can create groups. # # Parameters: - # name (required) - The name of the group - # path (required) - The path of the group + # name (required) - The name of the group + # path (required) - The path of the group + # description (optional) - The description of the group + # visibility_level (optional) - The visibility level of the group # Example Request: # POST /groups post do @@ -42,6 +44,30 @@ module API end end + # Update group. Available only for users who can administrate groups. + # + # Parameters: + # id (required) - The ID of a group + # path (optional) - The path of the group + # description (optional) - The description of the group + # visibility_level (optional) - The visibility level of the group + # Example Request: + # PUT /groups/:id + put ':id' do + group = find_group(params[:id]) + authorize! :admin_group, group + + attrs = attributes_for_keys [:name, :path, :description, :visibility_level] + + ::Groups::UpdateService.new(group, current_user, attrs).execute + + if group.errors.any? + render_validation_error!(group) + else + present group, with: Entities::GroupDetail + end + end + # Get a single group, with containing projects # # Parameters: diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 41c9cacd455..e7ccbff7ae2 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -97,6 +97,42 @@ describe API::API, api: true do end end + describe 'PUT /groups/:id' do + let(:new_group_name) { 'New Group'} + + context "when authenticated the group owner" do + it 'updates the group' do + put api("/groups/#{group1.id}", user1), name: new_group_name + + expect(response.status).to eq(200) + expect(json_response['name']).to eq(new_group_name) + end + + it 'returns 404 for a non existing group' do + put api('/groups/1328', user1) + + expect(response.status).to eq(404) + end + end + + context "when authenticated the admin" do + it 'updates the group' do + put api("/groups/#{group1.id}", admin), name: new_group_name + + expect(response.status).to eq(200) + expect(json_response['name']).to eq(new_group_name) + end + end + + context "when authenticated an user" do + it 'updates the group' do + put api("/groups/#{group1.id}", user2), name: new_group_name + + expect(response.status).to eq(403) + end + end + end + describe "GET /groups/:id/projects" do context "when authenticated as user" do it "should return the group's projects" do -- cgit v1.2.1 From 435892ed4398afb67043c62be8831df07d2078f2 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 16:07:58 +0200 Subject: Changelog entry for service class instrumentation [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c08b148c3ac..f4f864b301a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) + - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). -- cgit v1.2.1 From c87068658924d170f7eea787c7f924ef42d288a6 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Tue, 12 Apr 2016 16:44:12 +0200 Subject: Add my name --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a53a0615ca7..e77b659f261 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,7 +36,7 @@ v 8.7.0 (unreleased) - Update number of Todos in the sidebar when it's marked as "Done". !3600 v 8.6.6 - - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) v 8.6.5 - Fix importing from GitHub Enterprise. !3529 -- cgit v1.2.1 From ef22b76b732c2bf4ce52b8a73570ac2921f9caa4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 19:33:26 -0300 Subject: Simplify Projects::NotificationSettingsController --- app/controllers/projects/notification_settings_controller.rb | 7 ------- app/views/projects/buttons/_notifications.html.haml | 2 +- config/routes.rb | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb index e536725c5b1..7d81cc03c73 100644 --- a/app/controllers/projects/notification_settings_controller.rb +++ b/app/controllers/projects/notification_settings_controller.rb @@ -1,13 +1,6 @@ class Projects::NotificationSettingsController < Projects::ApplicationController before_action :authenticate_user! - def create - notification_setting = current_user.notification_settings_for(project) - saved = notification_setting.update_attributes(notification_setting_params) - - render json: { saved: saved } - end - def update notification_setting = current_user.notification_settings_for(project) saved = notification_setting.update_attributes(notification_setting_params) diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index 49f541399f2..c1e3e5b73a2 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,5 +1,5 @@ - if @notification_setting - = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), remote: true, html: { class: 'inline', id: 'notification-form' } do |f| + = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f| = f.hidden_field :level %span.dropdown %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} diff --git a/config/routes.rb b/config/routes.rb index 552385110dd..48601b7567b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -608,7 +608,7 @@ Rails.application.routes.draw do resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] - resource :notification_setting, only: [:create, :update] + resource :notification_setting, only: [:update] resources :refs, only: [] do collection do -- cgit v1.2.1 From aabb466e5b35477b39cc57642083df361cd5d112 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 11 Apr 2016 19:54:13 -0300 Subject: Improve specs for group/project notification controller --- .../notification_settings_controller_spec.rb | 17 +++++++++- .../notification_settings_controller_spec.rb | 39 ++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/spec/controllers/groups/notification_settings_controller_spec.rb b/spec/controllers/groups/notification_settings_controller_spec.rb index 3572535d61c..0786e45515a 100644 --- a/spec/controllers/groups/notification_settings_controller_spec.rb +++ b/spec/controllers/groups/notification_settings_controller_spec.rb @@ -2,16 +2,31 @@ require 'spec_helper' describe Groups::NotificationSettingsController do let(:group) { create(:group) } + let(:user) { create(:user) } describe '#update' do context 'when not authorized' do it 'redirects to sign in page' do put :update, group_id: group.to_param, - notification_setting: { level: NotificationSetting.levels[:participating] } + notification_setting: { level: :participating } expect(response).to redirect_to(new_user_session_path) end end + + context 'when authorized' do + before do + sign_in(user) + end + + it 'returns success' do + put :update, + group_id: group.to_param, + notification_setting: { level: :participating } + + expect(response.status).to eq 200 + end + end end end diff --git a/spec/controllers/projects/notification_settings_controller_spec.rb b/spec/controllers/projects/notification_settings_controller_spec.rb index 7e32a75b812..385877a26df 100644 --- a/spec/controllers/projects/notification_settings_controller_spec.rb +++ b/spec/controllers/projects/notification_settings_controller_spec.rb @@ -2,6 +2,11 @@ require 'spec_helper' describe Projects::NotificationSettingsController do let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + end describe '#create' do context 'when not authorized' do @@ -9,11 +14,26 @@ describe Projects::NotificationSettingsController do post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, - notification_setting: { level: NotificationSetting.levels[:participating] } + notification_setting: { level: :participating } expect(response).to redirect_to(new_user_session_path) end end + + context 'when authorized' do + before do + sign_in(user) + end + + it 'returns success' do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + notification_setting: { level: :participating } + + expect(response.status).to eq 200 + end + end end describe '#update' do @@ -22,10 +42,25 @@ describe Projects::NotificationSettingsController do put :update, namespace_id: project.namespace.to_param, project_id: project.to_param, - notification_setting: { level: NotificationSetting.levels[:participating] } + notification_setting: { level: :participating } expect(response).to redirect_to(new_user_session_path) end end + + context 'when authorized' do + before do + sign_in(user) + end + + it 'returns success' do + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + notification_setting: { level: :participating } + + expect(response.status).to eq 200 + end + end end end -- cgit v1.2.1 From cba2c437e582dd5880ec45cc4ff2fccda2315ad5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 11 Apr 2016 15:38:36 -0400 Subject: Move RepositoryArchiveCacheWorker to sidekiq-cron Closes #15105 --- app/controllers/projects/repositories_controller.rb | 1 - config/gitlab.yml.example | 3 +++ config/initializers/1_settings.rb | 3 +++ lib/api/repositories.rb | 1 - 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 5c7614cfbaf..bb7a6b6a5ab 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - RepositoryArchiveCacheWorker.perform_async headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format])) head :ok rescue => ex diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 35c7c425a5a..1a512a2227f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -156,6 +156,9 @@ production: &base stuck_ci_builds_worker: cron: "0 0 * * *" + # Remove outdated repository archives + repository_archive_cache_worker: + cron: "0 * * * *" # # 2. GitLab CI settings diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 72c4d8d61ce..ca74349e85d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -239,6 +239,9 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker' +Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *' +Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker' # diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 0d0f0d4616d..62161aadb9a 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -98,7 +98,6 @@ module API authorize! :download_code, user_project begin - RepositoryArchiveCacheWorker.perform_async header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format]) rescue not_found!('File') -- cgit v1.2.1 From 61a62e00e3b08e6ed962b029564e3a2446e169fd Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 12 Apr 2016 12:57:39 -0300 Subject: Fix specs for Projects::NotificationSettingsController --- .../notification_settings_controller_spec.rb | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/spec/controllers/projects/notification_settings_controller_spec.rb b/spec/controllers/projects/notification_settings_controller_spec.rb index 385877a26df..4908b545648 100644 --- a/spec/controllers/projects/notification_settings_controller_spec.rb +++ b/spec/controllers/projects/notification_settings_controller_spec.rb @@ -8,34 +8,6 @@ describe Projects::NotificationSettingsController do project.team << [user, :developer] end - describe '#create' do - context 'when not authorized' do - it 'redirects to sign in page' do - post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - notification_setting: { level: :participating } - - expect(response).to redirect_to(new_user_session_path) - end - end - - context 'when authorized' do - before do - sign_in(user) - end - - it 'returns success' do - post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - notification_setting: { level: :participating } - - expect(response.status).to eq 200 - end - end - end - describe '#update' do context 'when not authorized' do it 'redirects to sign in page' do -- cgit v1.2.1 From 061370790e415361c920e1404063955f4932e5ef Mon Sep 17 00:00:00 2001 From: Charles May Date: Tue, 5 Jan 2016 21:26:31 +0000 Subject: Fix a bug with trailing slash in teamcity_url See https://gitlab.com/gitlab-org/gitlab-ce/issues/3515 --- CHANGELOG | 3 +++ app/models/project_services/teamcity_service.rb | 31 ++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df0..c6ca0ea3de1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -123,6 +123,9 @@ v 8.6.0 - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - HTTP error pages work independently from location and config (Artem Sidorenko) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set + - Fix avatar stretching by providing a cropping feature (Johann Pardanaud) + - Fix a bug whith trailing slash in teamcity_url (Charles May) + - Don't load all of GitLab in mail_room - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Added omniauth-auth0 Gem (Daniel Carraro) diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index b8e9416131a..246c5eb4a82 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -85,13 +85,15 @@ class TeamcityService < CiService end def build_info(sha) - url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ - "branch:unspecified:any,number:#{sha}") + url = URI.join( + teamcity_url, + "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}" + ).to_s auth = { username: username, password: password, } - @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end def build_page(sha, ref) @@ -100,12 +102,14 @@ class TeamcityService < CiService if @response.code != 200 # If actual build link can't be determined, # send user to build summary page. - "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" + URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s else # If actual build link is available, go to build result page. built_id = @response['build']['id'] - "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ - "&buildTypeId=#{build_type}" + URI.join( + teamcity_url, + "#{teamcity_url}/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + ).to_s end end @@ -140,12 +144,13 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) - self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", - body: ""\ - ""\ - '', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: auth - ) + self.class.post( + URI.join(teamcity_url, "/httpAuth/app/rest/buildQueue").to_s, + body: ""\ + ""\ + '', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: auth + ) end end -- cgit v1.2.1 From 5fb572417e0c331afb62c8bbaa561b0fe7836fc5 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 19:08:35 +0200 Subject: Fix minor issues according development guidelines --- doc/api/groups.md | 2 +- lib/api/groups.rb | 10 ++++------ spec/requests/api/groups_spec.rb | 16 ++++++++++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/doc/api/groups.md b/doc/api/groups.md index 59046190d0f..2821bc21b81 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -140,7 +140,7 @@ PUT /groups/:id | `name` | string | no | The name of the group | | `path` | string | no | The path of the group | | `description` | string | no | The description of the group | -| `visibility_level` | integer | no | The visibility_level of the group. 0 for private, 10 for internal, 20 for public. | +| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | ```bash curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 964f691afcc..91e420832f3 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -47,7 +47,7 @@ module API # Update group. Available only for users who can administrate groups. # # Parameters: - # id (required) - The ID of a group + # id (required) - The ID of a group # path (optional) - The path of the group # description (optional) - The description of the group # visibility_level (optional) - The visibility level of the group @@ -59,12 +59,10 @@ module API attrs = attributes_for_keys [:name, :path, :description, :visibility_level] - ::Groups::UpdateService.new(group, current_user, attrs).execute - - if group.errors.any? - render_validation_error!(group) - else + if ::Groups::UpdateService.new(group, current_user, attrs).execute present group, with: Entities::GroupDetail + else + render_validation_error!(group) end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index e7ccbff7ae2..7383c7d11aa 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -100,7 +100,7 @@ describe API::API, api: true do describe 'PUT /groups/:id' do let(:new_group_name) { 'New Group'} - context "when authenticated the group owner" do + context 'when authenticated as the group owner' do it 'updates the group' do put api("/groups/#{group1.id}", user1), name: new_group_name @@ -115,7 +115,7 @@ describe API::API, api: true do end end - context "when authenticated the admin" do + context 'when authenticated as the admin' do it 'updates the group' do put api("/groups/#{group1.id}", admin), name: new_group_name @@ -124,13 +124,21 @@ describe API::API, api: true do end end - context "when authenticated an user" do - it 'updates the group' do + context 'when authenticated as an user that can see the group' do + it 'does not updates the group' do put api("/groups/#{group1.id}", user2), name: new_group_name expect(response.status).to eq(403) end end + + context 'when authenticated as an user that cannot see the group' do + it 'returns 403 when trying to update the group' do + put api("/groups/#{group2.id}", user1), name: new_group_name + + expect(response.status).to eq(403) + end + end end describe "GET /groups/:id/projects" do -- cgit v1.2.1 From dca50ac1d4a6da5724b66643bcb18a4a2e3f5558 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 22 Mar 2016 11:33:38 +0000 Subject: Project dropdown in header uses new dropdown --- app/assets/javascripts/gl_dropdown.js.coffee | 4 +++- app/assets/javascripts/project_select.js.coffee | 32 +++++++++++++++++++++++++ app/assets/stylesheets/framework/header.scss | 5 ++++ app/helpers/projects_helper.rb | 14 ++--------- app/views/layouts/header/_default.html.haml | 2 ++ app/views/layouts/project.html.haml | 7 ++++++ 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index ee1d0fad289..2dc37257e22 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -122,7 +122,9 @@ class GitLabDropdown FILTER_INPUT = '.dropdown-input .dropdown-input-field' constructor: (@el, @options) -> - @dropdown = $(@el).parent() + self = @ + selector = $(@el).data "target" + @dropdown = if selector? then $(selector) else $(@el).parent() # Set Defaults { diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee index be8ab9b428d..704bd8dee53 100644 --- a/app/assets/javascripts/project_select.js.coffee +++ b/app/assets/javascripts/project_select.js.coffee @@ -1,5 +1,37 @@ class @ProjectSelect constructor: -> + $('.js-projects-dropdown-toggle').each (i, dropdown) -> + $dropdown = $(dropdown) + + $dropdown.glDropdown( + filterable: true + filterRemote: true + search: + fields: ['name_with_namespace'] + data: (term, callback) -> + finalCallback = (projects) -> + callback projects + + if @includeGroups + projectsCallback = (projects) -> + groupsCallback = (groups) -> + data = groups.concat(projects) + finalCallback(data) + + Api.groups term, false, groupsCallback + else + projectsCallback = finalCallback + + if @groupId + Api.groupProjects @groupId, term, projectsCallback + else + Api.projects term, @orderBy, projectsCallback + url: (project) -> + project.web_url + text: (project) -> + project.name_with_namespace + ) + $('.ajax-project-select').each (i, select) -> @groupId = $(select).data('group-id') @includeGroups = $(select).data('include-groups') diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index b3397d16016..3f015427d07 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -69,6 +69,7 @@ header { } .header-content { + position: relative; height: $header-height; padding-right: 20px; @@ -76,6 +77,10 @@ header { padding-right: 0; } + .dropdown-menu { + margin-top: -5px; + } + .title { margin: 0; font-size: 19px; diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4e4c6e301d5..3621b943f3c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -65,18 +65,8 @@ module ProjectsHelper link_to(simple_sanitize(owner.name), user_path(owner)) end - project_link = link_to project_path(project), { class: "project-item-select-holder" } do - link_output = simple_sanitize(project.name) - - if current_user - link_output += project_select_tag :project_path, - class: "project-item-select js-projects-dropdown", - data: { include_groups: false, order_by: 'last_activity_at' } - end - - link_output - end - project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user + project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder"} + project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".header-content", toggle: "dropdown" } if current_user full_title = namespace_link + ' / ' + project_link full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 0f3b8119379..44339293095 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -45,6 +45,8 @@ %h1.title= title + = yield :header_content + = render 'shared/outdated_browser' - if @project && !@project.empty_repo? diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index a7ef31acd3d..35be616b174 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -17,4 +17,11 @@ - content_for :scripts_body do = render "layouts/init_auto_complete" if current_user +- content_for :header_content do + .dropdown-menu.dropdown-select + = dropdown_title("Go to a project") + = dropdown_filter("Search your projects") + = dropdown_content + = dropdown_loading + = render template: "layouts/application" -- cgit v1.2.1 From f870857dddd7630dc19a77ded7784a2261e046e3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 Mar 2016 16:28:46 +0000 Subject: Updated tests --- app/helpers/projects_helper.rb | 2 +- app/views/layouts/project.html.haml | 2 +- spec/features/projects_spec.rb | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3621b943f3c..83ebc124171 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -65,7 +65,7 @@ module ProjectsHelper link_to(simple_sanitize(owner.name), user_path(owner)) end - project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder"} + project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".header-content", toggle: "dropdown" } if current_user full_title = namespace_link + ' / ' + project_link diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 35be616b174..2c5911fa2fb 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -18,7 +18,7 @@ = render "layouts/init_auto_complete" if current_user - content_for :header_content do - .dropdown-menu.dropdown-select + .dropdown-menu.dropdown-select.dropdown-menu-projects = dropdown_title("Go to a project") = dropdown_filter("Search your projects") = dropdown_content diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index ed97b6cb577..782c0bfe666 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -100,8 +100,7 @@ feature 'Project', feature: true do it 'click toggle and show dropdown', js: true do find('.js-projects-dropdown-toggle').click - wait_for_ajax - expect(page).to have_css('.select2-results li', count: 1) + expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 1) end end -- cgit v1.2.1 From 14b124faca9518167798069aaaedbcc67994dd2f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Mar 2016 13:21:22 +0100 Subject: Tests update --- spec/javascripts/fixtures/project_title.html.haml | 26 +++++++++++++++++------ spec/javascripts/project_title_spec.js.coffee | 20 +++++------------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml index e5850b62659..3696f241c21 100644 --- a/spec/javascripts/fixtures/project_title.html.haml +++ b/spec/javascripts/fixtures/project_title.html.haml @@ -1,7 +1,19 @@ -%h1.title - %a - GitLab Org - %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"} - GitLab Test - %input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"} - %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle +.header-content + %h1.title + %a + GitLab Org + %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"} + GitLab Test + %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content" } + .dropdown-menu.dropdown-select.dropdown-menu-projects + .dropdown-title + %span Go to a project + %button.dropdown-title-button.dropdown-menu-close{"aria-label" => "Close", type: "button"} + %i.fa.fa-times.dropdown-menu-close-icon + .dropdown-input + %input.dropdown-input-field{id: "", placeholder: "Search your projects", type: "search", value: ""} + %i.fa.fa-search.dropdown-input-search + %i.fa.fa-times.dropdown-input-clear.js-dropdown-input-clear{role: "button"} + .dropdown-content + .dropdown-loading + %i.fa.fa-spinner.fa-spin diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee index 47c7b7febe3..3d8de2ff989 100644 --- a/spec/javascripts/project_title_spec.js.coffee +++ b/spec/javascripts/project_title_spec.js.coffee @@ -1,4 +1,6 @@ +#= require bootstrap #= require select2 +#= require gl_dropdown #= require api #= require project_select #= require project @@ -14,9 +16,6 @@ describe 'Project Title', -> fixture.load('project_title.html') @project = new Project() - spyOn(@project, 'changeProject').and.callFake (url) -> - window.current_project_url = url - describe 'project list', -> beforeEach => @projects_data = fixture.load('projects.json')[0] @@ -29,18 +28,9 @@ describe 'Project Title', -> it 'to show on toggle click', => $('.js-projects-dropdown-toggle').click() - - expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true) - expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length) + expect($('.header-content').hasClass('open')).toBe(true) it 'hide dropdown', -> - $("#select2-drop-mask").click() - - expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false) - - it 'change project when clicking item', -> - $('.js-projects-dropdown-toggle').click() - $('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup') + $(".dropdown-menu-close-icon").click() - expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false) - expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate') + expect($('.header-content').hasClass('open')).toBe(false) -- cgit v1.2.1 From 6416f8eab17556871984118c2dde04714a52bdf6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 4 Apr 2016 10:44:51 +0100 Subject: Updated based on Ruby feedback --- app/helpers/projects_helper.rb | 4 ++-- app/views/layouts/project.html.haml | 11 +++++----- spec/javascripts/fixtures/project_title.html.haml | 25 ++++++++++++----------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 83ebc124171..cc411da459f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -66,10 +66,10 @@ module ProjectsHelper end project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } - project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".header-content", toggle: "dropdown" } if current_user + project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) if current_user full_title = namespace_link + ' / ' + project_link - full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + full_title << ' · '.html_safe + link_to(simple_sanitize(name), url) if name full_title end diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 2c5911fa2fb..6dfe7fbdae8 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -18,10 +18,11 @@ = render "layouts/init_auto_complete" if current_user - content_for :header_content do - .dropdown-menu.dropdown-select.dropdown-menu-projects - = dropdown_title("Go to a project") - = dropdown_filter("Search your projects") - = dropdown_content - = dropdown_loading + .js-dropdown-menu-projects + .dropdown-menu.dropdown-select.dropdown-menu-projects + = dropdown_title("Go to a project") + = dropdown_filter("Search your projects") + = dropdown_content + = dropdown_loading = render template: "layouts/application" diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml index 3696f241c21..4547feeb212 100644 --- a/spec/javascripts/fixtures/project_title.html.haml +++ b/spec/javascripts/fixtures/project_title.html.haml @@ -5,15 +5,16 @@ %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"} GitLab Test %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content" } - .dropdown-menu.dropdown-select.dropdown-menu-projects - .dropdown-title - %span Go to a project - %button.dropdown-title-button.dropdown-menu-close{"aria-label" => "Close", type: "button"} - %i.fa.fa-times.dropdown-menu-close-icon - .dropdown-input - %input.dropdown-input-field{id: "", placeholder: "Search your projects", type: "search", value: ""} - %i.fa.fa-search.dropdown-input-search - %i.fa.fa-times.dropdown-input-clear.js-dropdown-input-clear{role: "button"} - .dropdown-content - .dropdown-loading - %i.fa.fa-spinner.fa-spin + .js-dropdown-menu-projects + .dropdown-menu.dropdown-select.dropdown-menu-projects + .dropdown-title + %span Go to a project + %button.dropdown-title-button.dropdown-menu-close{"aria-label" => "Close", type: "button"} + %i.fa.fa-times.dropdown-menu-close-icon + .dropdown-input + %input.dropdown-input-field{id: "", placeholder: "Search your projects", type: "search", value: ""} + %i.fa.fa-search.dropdown-input-search + %i.fa.fa-times.dropdown-input-clear.js-dropdown-input-clear{role: "button"} + .dropdown-content + .dropdown-loading + %i.fa.fa-spinner.fa-spin -- cgit v1.2.1 From 4293485a22bfdbd2afdbb20dcf5d777f379fac87 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Apr 2016 15:20:22 +0100 Subject: Updated Ruby Added CHANGELOG item --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 07274ab5c1d..d12f703c7bc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.6.5 v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) - Redesign the Labels page + - Project switcher uses new dropdown styling v 8.6.3 - Mentions on confidential issues doesn't create todos for non-members. !3374 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index cc411da459f..ab77853da1a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -66,10 +66,13 @@ module ProjectsHelper end project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } - project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) if current_user - full_title = namespace_link + ' / ' + project_link - full_title << ' · '.html_safe + link_to(simple_sanitize(name), url) if name + if current_user + project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) + end + + full_title = namespace_link + ' / ' << project_link + full_title << ' · '.html_safe << link_to(simple_sanitize(name), url) if name full_title end -- cgit v1.2.1 From 63e54f1555f02b93347588fbf332c7521d19d2a6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 8 Apr 2016 19:44:23 +0100 Subject: Updated based on Ruby feedback --- CHANGELOG | 8 ++++++++ app/helpers/projects_helper.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d12f703c7bc..ab2ef90fb08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -60,6 +60,14 @@ v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) - Redesign the Labels page - Project switcher uses new dropdown styling + - Project switcher uses new dropdown styling + +v 8.6.5 (unreleased) + - Only update repository language if it is not set to improve performance + - Check permissions when user attempts to import members from another project + +v 8.6.4 + - Don't attempt to fetch any tags from a forked repo (Stan Hu) v 8.6.3 - Mentions on confidential issues doesn't create todos for non-members. !3374 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index ab77853da1a..7e00aacceaa 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -71,7 +71,7 @@ module ProjectsHelper project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) end - full_title = namespace_link + ' / ' << project_link + full_title = "#{namespace_link} / #{project_link}".html_safe full_title << ' · '.html_safe << link_to(simple_sanitize(name), url) if name full_title -- cgit v1.2.1 From 1ac6bdb5c85f14557ed41b5b81d6ee9d577739a1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 13:23:29 +0100 Subject: Updated CHANGELOG --- CHANGELOG | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ab2ef90fb08..071e35167fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,6 +46,7 @@ v 8.7.0 (unreleased) v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) + - Project switcher uses new dropdown styling v 8.6.5 - Fix importing from GitHub Enterprise. !3529 @@ -59,15 +60,6 @@ v 8.6.5 v 8.6.4 - Don't attempt to fetch any tags from a forked repo (Stan Hu) - Redesign the Labels page - - Project switcher uses new dropdown styling - - Project switcher uses new dropdown styling - -v 8.6.5 (unreleased) - - Only update repository language if it is not set to improve performance - - Check permissions when user attempts to import members from another project - -v 8.6.4 - - Don't attempt to fetch any tags from a forked repo (Stan Hu) v 8.6.3 - Mentions on confidential issues doesn't create todos for non-members. !3374 -- cgit v1.2.1 From 38cff18af0ed48bcd5916b6b6bb6ceeb9ab062fd Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Fri, 1 Apr 2016 15:04:03 -0700 Subject: Adjust the default trusted_proxies to only include localhost, and allow other trusted proxies to be configured. --- config/initializers/1_settings.rb | 1 + config/initializers/trusted_proxies.rb | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 config/initializers/trusted_proxies.rb diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 72c4d8d61ce..2167da306f2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -190,6 +190,7 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.send Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] +Settings.gitlab['trusted_proxies'] ||= [] # diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb new file mode 100644 index 00000000000..b8cc025bae2 --- /dev/null +++ b/config/initializers/trusted_proxies.rb @@ -0,0 +1,2 @@ +Rails.application.config.action_dispatch.trusted_proxies = + [ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies) -- cgit v1.2.1 From bb372ac97f733c45f22dc31e09b98a78411d4f86 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 6 Apr 2016 06:49:46 -0700 Subject: Add changelog entries, install docs, and gitlab.yml.example entry for the trusted_proxies setting --- CHANGELOG | 2 ++ config/gitlab.yml.example | 9 +++++++++ doc/install/installation.md | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07274ab5c1d..584e60a0e06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ v 8.7.0 (unreleased) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) + - Add setting for customizing the list of trusted proxies !3524 + - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - Expose project badges in project settings diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 35c7c425a5a..56caee47c97 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -46,6 +46,15 @@ production: &base # # relative_url_root: /gitlab + # Trusted Proxies + # Customize if you have GitLab behind a reverse proxy which is running on a different machine. + # Add the IP address for your reverse proxy to the list, otherwise users will appear signed in from that address. + trusted_proxies: + # Examples: + #- 192.168.1.0/24 + #- 192.168.2.1 + #- 2001:0db8::/32 + # Uncomment and customize if you can't use the default user to run GitLab (default: 'git') # user: git diff --git a/doc/install/installation.md b/doc/install/installation.md index f8f7d6a9ebe..bfea4ce193e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -530,6 +530,15 @@ See the [omniauth integration document](../integration/omniauth.md) GitLab can build your projects. To enable that feature you need GitLab Runners to do that for you. Checkout the [GitLab Runner section](https://about.gitlab.com/gitlab-ci/#gitlab-runner) to install it +### Adding your Trusted Proxies + +If you are using a reverse proxy on an separate machine, you may want to add the +proxy to the trusted proxies list. Otherwise users will appear signed in from the +proxy's IP address. + +You can add trusted proxies in `config/gitlab.yml` by customizing the `trusted_proxies` +option in section 1. Please restart GitLab after editing this file. + ### Custom Redis Connection If you'd like Resque to connect to a Redis server on a non-standard port or on a different host, you can configure its connection string via the `config/resque.yml` file. -- cgit v1.2.1 From e18f20d7118b7c8f2ff18a6e4255d6c7c0995b04 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Tue, 12 Apr 2016 11:02:41 -0700 Subject: Updated trusted proxies doc section to link to the restart GitLab docs --- doc/install/installation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index bfea4ce193e..e721e70a596 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -537,7 +537,8 @@ proxy to the trusted proxies list. Otherwise users will appear signed in from th proxy's IP address. You can add trusted proxies in `config/gitlab.yml` by customizing the `trusted_proxies` -option in section 1. Please restart GitLab after editing this file. +option in section 1. Save the file and [reconfigure GitLab](../administration/restart_gitlab.md) +for the changes to take effect. ### Custom Redis Connection -- cgit v1.2.1 From 42a391f744a2ea43272483f998395c910153e1d2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 Apr 2016 14:28:07 -0400 Subject: Update spring and spring-commands-spinach Spring changelog: https://git.io/vVAUY --- Gemfile | 4 ++-- Gemfile.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 258b5612cd5..199ef65d922 100644 --- a/Gemfile +++ b/Gemfile @@ -285,9 +285,9 @@ group :development, :test do gem 'teaspoon', '~> 1.1.0' gem 'teaspoon-jasmine', '~> 2.2.0' - gem 'spring', '~> 1.6.4' + gem 'spring', '~> 1.7.0' gem 'spring-commands-rspec', '~> 1.0.4' - gem 'spring-commands-spinach', '~> 1.0.0' + gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'rubocop', '~> 0.38.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 9da44a46583..ad7d7c18559 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -769,10 +769,10 @@ GEM spinach (>= 0.4) spinach-rerun-reporter (0.0.2) spinach (~> 0.8) - spring (1.6.4) + spring (1.7.1) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - spring-commands-spinach (1.0.0) + spring-commands-spinach (1.1.0) spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) @@ -1030,9 +1030,9 @@ DEPENDENCIES slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) spinach-rerun-reporter (~> 0.0.2) - spring (~> 1.6.4) + spring (~> 1.7.0) spring-commands-rspec (~> 1.0.4) - spring-commands-spinach (~> 1.0.0) + spring-commands-spinach (~> 1.1.0) spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.6.0) state_machines-activerecord (~> 0.3.0) -- cgit v1.2.1 From a64f1c763615c049e551c82a9f3a7c53525a172c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 12 Apr 2016 15:39:33 -0300 Subject: Add changelog entry, improve specs and model code --- CHANGELOG | 1 + app/models/merge_request.rb | 13 ++++--------- spec/controllers/projects/merge_requests_controller_spec.rb | 9 ++++----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 54d79259b30..cf84ce8116e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.7.0 (unreleased) - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) - Add default scope to projects to exclude projects pending deletion + - Allow to close merge requests which source projects(forks) are deleted. - Ensure empty recipients are rejected in BuildsEmailService - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8292445bcac..e410febdfff 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base validates :target_project, presence: true validates :target_branch, presence: true validates :merge_user, presence: true, if: :merge_when_build_succeeds? - validate :validate_branches + validate :validate_branches, unless: :allow_broken validate :validate_fork scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } @@ -213,14 +213,12 @@ class MergeRequest < ActiveRecord::Base end def validate_branches - return if allow_broken - if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" end if opened? || reopened? - similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened + similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id if similar_mrs.any? errors.add :validate_branches, @@ -346,12 +344,9 @@ class MergeRequest < ActiveRecord::Base end def hook_attrs - source_hook_attrs = source_project.hook_attrs if source_project.present? - target_hook_attrs = target_project.hook_attrs if target_project.present? - attrs = { - source: source_hook_attrs, - target: target_hook_attrs, + source: source_project.try(:hook_attrs), + target: target_project.hook_attrs, last_commit: nil, work_in_progress: work_in_progress? } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 0f2cd34132a..c54e83339a1 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -159,19 +159,18 @@ describe Projects::MergeRequestsController do describe 'PUT #update' do context 'there is no source project' do - let(:project) { create(:project) } - let(:fork_project) { create(:forked_project_with_submodules) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + let(:project) { create(:project) } + let(:fork_project) { create(:forked_project_with_submodules) } + let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } before do fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) fork_project.save merge_request.reload + fork_project.destroy end it 'closes MR without errors' do - fork_project.destroy - post :update, namespace_id: project.namespace.path, project_id: project.path, -- cgit v1.2.1 From 447f3613b78ac4ba4ad6bda1811447b48e126b0c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 8 Apr 2016 15:48:18 -0700 Subject: Wrap text in notes box if longer than code in diff --- app/assets/stylesheets/pages/notes.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 7295fe51121..88ba5e53a0d 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -155,6 +155,7 @@ ul.notes { border-width: 1px 0; padding: 0; vertical-align: top; + white-space: normal; &.parallel { border-width: 1px; } -- cgit v1.2.1 From d3ff7ca0846a88961d3f954c62398c54aa69c059 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 11 Apr 2016 10:00:45 -0500 Subject: Input updates --- app/assets/stylesheets/pages/merge_requests.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 22 ++++++++++++++++- app/assets/stylesheets/pages/notes.scss | 31 +++++++++++++++++++++--- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index b79335eab91..4ef548ffbe7 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -142,6 +142,7 @@ overflow: hidden; font-size: 90%; margin: 0 3px; + word-break: break-all; } .mr-list { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4d4d508396d..5e5722c2c33 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -16,6 +16,7 @@ .new-note { margin: 0; border: none; + border-right: 1px solid $table-border-gray; } } @@ -71,12 +72,25 @@ border-color: $focus-border-color; } } + + p { + code { + white-space: normal; + } + + pre { + code { + white-space: pre; + } + } + } } } .discussion-form { padding: $gl-padding-top $gl-padding; - background-color: #fff; + background-color: $white-light; + border-right: 1px solid $table-border-gray; } .note-edit-form { @@ -118,7 +132,13 @@ .discussion-reply-holder { background-color: $white-light; + border-right: 1px solid $table-border-gray; padding: 10px 16px; + max-width: 800px; + + .new-note { + border-right: none; + } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 88ba5e53a0d..07dd0292453 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -81,9 +81,15 @@ ul.notes { @include md-typography; // On diffs code should wrap nicely and not overflow - pre { + p { code { - white-space: pre; + white-space: normal; + } + + pre { + code { + white-space: pre; + } } } @@ -112,6 +118,10 @@ ul.notes { margin: 10px 0; } } + + a { + word-break: break-all; + } } .note-header { @@ -127,7 +137,7 @@ ul.notes { margin-right: 10px; } .line_content { - white-space: pre-wrap; + white-space: pre; } } @@ -145,20 +155,33 @@ ul.notes { background: $background-color; color: $text-color; } + &.notes_line2 { text-align: center; padding: 10px 0; border-left: 1px solid #ddd !important; } + &.notes_content { - background-color: #fff; + background-color: $background-color; border-width: 1px 0; padding: 0; vertical-align: top; white-space: normal; + &.parallel { border-width: 1px; } + + .new-note { + max-width: 800px; + } + + .notes { + max-width: 800px; + background-color: $white-light; + border-right: 1px solid $table-border-gray; + } } } } -- cgit v1.2.1 From 4e5ae5a281be109ca22929bb21732f0ee1f6630d Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 12 Apr 2016 15:40:53 -0500 Subject: Fix Grafana docs and link from Influx page --- doc/monitoring/performance/gitlab_configuration.md | 1 + doc/monitoring/performance/grafana_configuration.md | 6 +++--- doc/monitoring/performance/influxdb_configuration.md | 1 + doc/monitoring/performance/influxdb_schema.md | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index b856e7935a3..90e99302210 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -37,3 +37,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [InfluxDB Configuration](influxdb_configuration.md) - [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 416c9870aa0..10ef1009818 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -91,18 +91,18 @@ JSON file. Open the dashboard dropdown menu and click 'Import' -![Grafana dashboard dropdown](/img/grafana_dashboard_dropdown.png) +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) Click 'Choose file' and browse to the location where you downloaded or cloned the dashboard repository. Pick one of the JSON files to import. -![Grafana dashboard import](/img/grafana_dashboard_import.png) +![Grafana dashboard import](img/grafana_dashboard_import.png) Once the dashboard is imported, be sure to click save icon in the top bar. If you do not save the dashboard after importing it will be removed when you navigate away. -![Grafana save icon](/img/grafana_save_icon.png) +![Grafana save icon](img/grafana_save_icon.png) Repeat this process for each dashboard you wish to import. diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index 3a2b598b78f..63aa03985ef 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -181,6 +181,7 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md [influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management [influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index a5a8aebd2d1..d31b3788f36 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -85,3 +85,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md -- cgit v1.2.1 From 60736db429666f4145004408b722799ef144eb5f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 12 Apr 2016 15:07:24 -0500 Subject: Remove max-width from comments --- app/assets/stylesheets/pages/note_form.scss | 8 -------- app/assets/stylesheets/pages/notes.scss | 6 ------ 2 files changed, 14 deletions(-) diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 5e5722c2c33..f4da17fadaa 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -16,7 +16,6 @@ .new-note { margin: 0; border: none; - border-right: 1px solid $table-border-gray; } } @@ -90,7 +89,6 @@ .discussion-form { padding: $gl-padding-top $gl-padding; background-color: $white-light; - border-right: 1px solid $table-border-gray; } .note-edit-form { @@ -132,13 +130,7 @@ .discussion-reply-holder { background-color: $white-light; - border-right: 1px solid $table-border-gray; padding: 10px 16px; - max-width: 800px; - - .new-note { - border-right: none; - } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 07dd0292453..e421a31549a 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -173,14 +173,8 @@ ul.notes { border-width: 1px; } - .new-note { - max-width: 800px; - } - .notes { - max-width: 800px; background-color: $white-light; - border-right: 1px solid $table-border-gray; } } } -- cgit v1.2.1 From 6a238c37e002c9d8dcc51af2cb3dff44c900139c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 12 Apr 2016 18:06:52 -0300 Subject: Fix todo_target_path for todos where target was removed --- app/assets/javascripts/todos.js.coffee | 2 ++ app/helpers/todos_helper.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 886da72e261..00d2b641723 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -59,6 +59,8 @@ class @Todos goToTodoUrl: (e)-> todoLink = $(this).data('url') + return unless todoLink + if e.metaKey e.preventDefault() window.open(todoLink,'_blank') diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index edc5686cf08..2f066682180 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -20,6 +20,8 @@ module TodosHelper end def todo_target_path(todo) + return unless todo.target.present? + anchor = dom_id(todo.note) if todo.note.present? if todo.for_commit? -- cgit v1.2.1 From fd248b0f068ad0239d0d4ddc462f091eecfe981e Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 12 Apr 2016 16:13:31 -0500 Subject: (doc) fix typo to ssh keys doc url --- doc/ci/ssh_keys/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index d790015aca1..7f825e6a065 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -30,7 +30,7 @@ This is the universal solution which works with any type of executor ## SSH keys when using the Docker executor You will first need to create an SSH key pair. For more information, follow the -instructions to [generate an SSH key](../ssh/README.md). +instructions to [generate an SSH key](../../ssh/README.md). Then, create a new **Secret Variable** in your project settings on GitLab following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` @@ -63,7 +63,7 @@ before_script: As a final step, add the _public_ key from the one you created earlier to the services that you want to have an access to from within the build environment. If you are accessing a private GitLab repository you need to add it as a -[deploy key](../ssh/README.md#deploy-keys). +[deploy key](../../ssh/README.md#deploy-keys). That's it! You can now have access to private servers or repositories in your build environment. @@ -79,12 +79,12 @@ on, and use that key for all projects that are run on this machine. First, you need to login to the server that runs your builds. Then from the terminal login as the `gitlab-runner` user and generate the SSH -key pair as described in the [SSH keys documentation](../ssh/README.md). +key pair as described in the [SSH keys documentation](../../ssh/README.md). As a final step, add the _public_ key from the one you created earlier to the services that you want to have an access to from within the build environment. If you are accessing a private GitLab repository you need to add it as a -[deploy key](../ssh/README.md#deploy-keys). +[deploy key](../../ssh/README.md#deploy-keys). Once done, try to login to the remote server in order to accept the fingerprint: -- cgit v1.2.1 From 2e13f6c326b920f1b78ca592dc1b938b62d5eef3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 Apr 2016 14:39:08 -0400 Subject: Add `Gitlab.com?` method To be used as a feature flag for GitLab.com-only features, such as welcome emails. We will be careful to only use this to disable features or functionality that do not make sense for any installations that aren't GitLab.com. We will not use this to restrict features from other installations or keep them "exclusive" to GitLab.com. --- lib/gitlab.rb | 3 +++ spec/lib/gitlab_spec.rb | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 spec/lib/gitlab_spec.rb diff --git a/lib/gitlab.rb b/lib/gitlab.rb index 6108697bc20..7479e729db1 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -1,4 +1,7 @@ require 'gitlab/git' module Gitlab + def self.com? + Gitlab.config.gitlab.url == 'https://gitlab.com' + end end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb new file mode 100644 index 00000000000..c59dfea5c55 --- /dev/null +++ b/spec/lib/gitlab_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe Gitlab, lib: true do + describe '.com?' do + it 'is true when on GitLab.com' do + stub_config_setting(url: 'https://gitlab.com') + + expect(described_class.com?).to eq true + end + + it 'is false when not on GitLab.com' do + stub_config_setting(url: 'http://example.com') + + expect(described_class.com?).to eq false + end + end +end -- cgit v1.2.1 From d8296f873871120b7f4134bdcf8854a09b9e8be8 Mon Sep 17 00:00:00 2001 From: connorshea Date: Tue, 12 Apr 2016 16:11:58 -0600 Subject: Remove Bootstrap Carousel The Bootstrap carousel module is used for image carousels, and we don't use it anywhere on the site. Also separated the Bootstrap JavaScript into separate components and removed the carousel component. Fixes #14670. --- app/assets/javascripts/application.js.coffee | 12 +++++++++++- app/assets/stylesheets/framework/tw_bootstrap.scss | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b05138ac1ac..6f435e4c542 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -22,7 +22,17 @@ #= require cal-heatmap #= require turbolinks #= require autosave -#= require bootstrap +#= require bootstrap/affix +#= require bootstrap/alert +#= require bootstrap/button +#= require bootstrap/collapse +#= require bootstrap/dropdown +#= require bootstrap/modal +#= require bootstrap/scrollspy +#= require bootstrap/tab +#= require bootstrap/transition +#= require bootstrap/tooltip +#= require bootstrap/popover #= require select2 #= require raphael #= require g.raphael diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index dd42db1840f..96bab7880c2 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -43,7 +43,6 @@ @import "bootstrap/modals"; @import "bootstrap/tooltip"; @import "bootstrap/popovers"; -@import "bootstrap/carousel"; // Utility classes .clearfix { -- cgit v1.2.1 From a0008f2720a1dc1d5ba79dcf2e27c041ed52fb52 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 12 Apr 2016 23:57:42 +0000 Subject: improve formatting --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index 724c7cca0f1..d2660930653 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,7 +3,7 @@ ## User documentation - [API](api/README.md) Automate GitLab via a simple and powerful API. -- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, .gitlab-ci.yml options, and examples. +- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). -- cgit v1.2.1 From 2ce7559d70e228963280df1f50176e9b2fa1e7b8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 13 Apr 2016 00:00:15 -0700 Subject: Fix repository cache invalidation issue when project is recreated with an empty repo To reproduce: 1. Create a project with some content 2. Rename the project 3. Create a new project with the same name. 4. Boom - 404. After step 2, the branch and tag counts were not being cleared. This would cause `repository.has_visible_content?` to erroneously return `true` for the newly-created project. Closes #13384 --- CHANGELOG | 1 + app/models/repository.rb | 2 ++ spec/models/repository_spec.rb | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 071e35167fa..21a4aea91b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.7.0 (unreleased) - Update number of Todos in the sidebar when it's marked as "Done". !3600 - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - API: User can leave a project through the API when not master or owner. !3613 + - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) diff --git a/app/models/repository.rb b/app/models/repository.rb index 462b48118ef..0b2289cfa39 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -253,6 +253,8 @@ class Repository # This ensures this particular cache is flushed after the first commit to a # new repository. expire_emptiness_caches if empty? + expire_branch_count_cache + expire_tag_count_cache end def expire_branch_cache(branch_name = nil) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4e49c413f23..c3a4016fa49 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -393,6 +393,8 @@ describe Repository, models: true do describe '#expire_cache' do it 'expires all caches' do expect(repository).to receive(:expire_branch_cache) + expect(repository).to receive(:expire_branch_count_cache) + expect(repository).to receive(:expire_tag_count_cache) repository.expire_cache end -- cgit v1.2.1 From 3ea955a637127e6e11bc9fe270f87f63226b9d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Apr 2016 15:11:31 +0200 Subject: Improve TeamcityService and its specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 4 +- app/models/project_services/teamcity_service.rb | 6 +- .../project_services/teamcity_service_spec.rb | 251 ++++++++++++++++----- 3 files changed, 203 insertions(+), 58 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c6ca0ea3de1..df40a1e1b81 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.7.0 (unreleased) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API - Fix Error 500 after renaming a project path (Stan Hu) + - Fix a bug whith trailing slash in teamcity_url (Charles May) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 @@ -123,9 +124,6 @@ v 8.6.0 - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - HTTP error pages work independently from location and config (Artem Sidorenko) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - - Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - - Fix a bug whith trailing slash in teamcity_url (Charles May) - - Don't load all of GitLab in mail_room - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Added omniauth-auth0 Gem (Daniel Carraro) diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 246c5eb4a82..8dceee5e2c5 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -91,7 +91,7 @@ class TeamcityService < CiService ).to_s auth = { username: username, - password: password, + password: password } @response = HTTParty.get(url, verify: false, basic_auth: auth) end @@ -108,7 +108,7 @@ class TeamcityService < CiService built_id = @response['build']['id'] URI.join( teamcity_url, - "#{teamcity_url}/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" ).to_s end end @@ -145,7 +145,7 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) self.class.post( - URI.join(teamcity_url, "/httpAuth/app/rest/buildQueue").to_s, + URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, body: ""\ ""\ '', diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index f26b47a856c..bc7423cee69 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -21,73 +21,220 @@ require 'spec_helper' describe TeamcityService, models: true do - describe "Associations" do + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } - - context "when a password was previously set" do - before do - @teamcity_service = TeamcityService.create( - project: create(:project), - properties: { - teamcity_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) + describe 'Validations' do + describe '#teamcity_url' do + it 'does not validate the presence of teamcity_url if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:teamcity_url) end - - it "reset password if url changed" do - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.save - expect(@teamcity_service.password).to be_nil + + it 'validates the presence of teamcity_url if service is active' do + teamcity_service = service + teamcity_service.active = true + + expect(teamcity_service).to validate_presence_of(:teamcity_url) + end + end + + describe '#build_type' do + it 'does not validate the presence of build_type if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:build_type) + end + + it 'validates the presence of build_type if service is active' do + teamcity_service = service + teamcity_service.active = true + + expect(teamcity_service).to validate_presence_of(:build_type) end - - it "does not reset password if username changed" do - @teamcity_service.username = "some_name" - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") + end + + describe '#username' do + it 'does not validate the presence of username if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:username) end - it "does not reset password if new url is set together with password, even if it's the same password" do - @teamcity_service.teamcity_url = 'http://gitlab_edited.com' - @teamcity_service.password = 'password' - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") - expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") + it 'does not validate the presence of username if username is nil' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.password = nil + + expect(teamcity_service).not_to validate_presence_of(:username) end - it "should reset password if url changed, even if setter called multiple times" do - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.save - expect(@teamcity_service.password).to be_nil + it 'validates the presence of username if service is active and username is present' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.password = 'secret' + + expect(teamcity_service).to validate_presence_of(:username) end end - - context "when no password was previously set" do - before do - @teamcity_service = TeamcityService.create( - project: create(:project), - properties: { - teamcity_url: 'http://gitlab.com', - username: 'mic' - } - ) + + describe '#password' do + it 'does not validate the presence of password if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:password) + end + + it 'does not validate the presence of password if username is nil' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.username = nil + + expect(teamcity_service).not_to validate_presence_of(:password) end - it "saves password if new url is set together with password" do - @teamcity_service.teamcity_url = 'http://gitlab_edited.com' - @teamcity_service.password = 'password' - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") - expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") + it 'validates the presence of password if service is active and username is present' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.username = 'john' + + expect(teamcity_service).to validate_presence_of(:password) end end end + + describe 'Callbacks' do + describe 'before_update :reset_password' do + context 'when a password was previously set' do + it 'resets password if url changed' do + teamcity_service = service + + teamcity_service.teamcity_url = 'http://gitlab1.com' + teamcity_service.save + + expect(teamcity_service.password).to be_nil + end + + it 'does not reset password if username changed' do + teamcity_service = service + + teamcity_service.username = 'some_name' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + teamcity_service = service + + teamcity_service.teamcity_url = 'http://gitlab_edited.com' + teamcity_service.password = 'password' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + end + end + + it 'saves password if new url is set together with password when no password was previously set' do + teamcity_service = service + teamcity_service.password = nil + + teamcity_service.teamcity_url = 'http://gitlab_edited.com' + teamcity_service.password = 'password' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + end + end + end + + describe '#build_page' do + it 'returns a specific URL when status is 500' do + stub_request(status: 500) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildTypeId=foo') + end + + it 'returns a build URL when teamcity_url has no trailing slash' do + stub_request(body: %Q({"build":{"id":"666"}})) + + expect(service(teamcity_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') + end + + it 'returns a build URL when teamcity_url has a trailing slash' do + stub_request(body: %Q({"build":{"id":"666"}})) + + expect(service(teamcity_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') + end + end + + describe '#commit_status' do + it 'sets commit status to :error when status is 500' do + stub_request(status: 500) + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + + it 'sets commit status to "pending" when status is 404' do + stub_request(status: 404) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "success" when build status contains SUCCESS' do + stub_request(build_status: 'YAY SUCCESS!') + + expect(service.commit_status('123', 'unused')).to eq('success') + end + + it 'sets commit status to "failed" when build status contains FAILURE' do + stub_request(build_status: 'NO FAILURE!') + + expect(service.commit_status('123', 'unused')).to eq('failed') + end + + it 'sets commit status to "pending" when build status contains Pending' do + stub_request(build_status: 'NO Pending!') + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to :error when build status is unknown' do + stub_request(build_status: 'FOO BAR!') + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + end + + def service(teamcity_url: 'http://gitlab.com') + described_class.create( + project: build_stubbed(:empty_project), + properties: { + teamcity_url: teamcity_url, + username: 'mic', + password: 'password', + build_type: 'foo' + } + ) + end + + def stub_request(status: 200, body: nil, build_status: 'success') + teamcity_full_url = 'http://mic:password@gitlab.com/httpAuth/app/rest/builds/branch:unspecified:any,number:123' + body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) + + WebMock.stub_request(:get, teamcity_full_url).to_return( + status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body + ) + end end -- cgit v1.2.1 From 4a09a6c6f21c0bcd48123759cd6804276d810929 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 13 Apr 2016 11:31:06 +0300 Subject: Move 'New branch from issue' feature doc to web_editor.md [ci skip] --- .../basicsimages/new_branch_button.png | Bin 120622 -> 0 bytes doc/gitlab-basics/create-branch.md | 9 ------ doc/workflow/img/new_branch_from_issue.png | Bin 0 -> 120622 bytes doc/workflow/web_editor.md | 32 +++++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) delete mode 100644 doc/gitlab-basics/basicsimages/new_branch_button.png create mode 100644 doc/workflow/img/new_branch_from_issue.png diff --git a/doc/gitlab-basics/basicsimages/new_branch_button.png b/doc/gitlab-basics/basicsimages/new_branch_button.png deleted file mode 100644 index 394c139e17e..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_branch_button.png and /dev/null differ diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 9d688b9389b..7556b0f663e 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -32,15 +32,6 @@ Fill out the information required: ![Branch info](basicsimages/branch_info.png) -## From an issue -When an issue should be resolved one could also create a branch on the issue page. A button is displayed after the description unless there is already a branch or a referenced merge request. - -![New Branch Button](basicsimages/new_branch_button.png) - -The branch created diverges from the default branch of the project, usually `master`. The branch name will be based on the title of the issue and as suffix its ID. Thus the example screenshot above will yield a branch named `et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum-2`. -After the branch is created the user can edit files in the repository to fix the issue. When a merge request is created the -description field will display `Closes #2` to use the issue closing pattern. This will close the issue once the merge request is merged. - ### Note: You will be able to find and select the name of your branch in the white box next to a project's name: diff --git a/doc/workflow/img/new_branch_from_issue.png b/doc/workflow/img/new_branch_from_issue.png new file mode 100644 index 00000000000..394c139e17e Binary files /dev/null and b/doc/workflow/img/new_branch_from_issue.png differ diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index 4a451d98953..5685a9d89dd 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -66,6 +66,35 @@ the target branch. Click **Create directory** to finish. ## Create a new branch +There are multiple ways to create a branch from GitLab's web interface. + +### Create a new branch from an issue + +>**Note:** +This feature was [introduced][ce-2808] in GitLab 8.6. + +In case your development workflow dictates to have an issue for every merge +request, you can quickly create a branch right on the issue page which will be +tied with the issue itself. You can see a **New Branch** button after the issue +description, unless there is already a branch with the same name or a referenced +merge request. + +![New Branch Button](img/new_branch_from_issue.png) + +Once you click it, a new branch will be created that diverges from the default +branch of your project, by default `master`. The branch name will be based on +the title of the issue and as suffix it will have its ID. Thus, the example +screenshot above will yield a branch named +`et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum-2`. + +After the branch is created, you can edit files in the repository to fix +the issue. When a merge request is created based on the newly created branch, +the description field will automatically display the [issue closing pattern] +`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the +merge request is merged. + +### Create a new branch from a project's dashboard + If you want to make changes to several files before creating a new merge request, you can create a new branch up front. From a project's files page, choose **New branch** from the dropdown. @@ -118,3 +147,6 @@ appear that is labeled **Start a new merge request with these changes**. After you commit the changes you will be taken to a new merge request form. ![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png) + +[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808 +[issue closing pattern]: ../customization/issue_closing.md -- cgit v1.2.1 From b2f48d8c46cebcf2a576c18b661c3481b3450f3b Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 21:34:24 +0200 Subject: API: Return 404 if user does not have access to group --- CHANGELOG | 1 + lib/api/helpers.rb | 3 +-- spec/requests/api/groups_spec.rb | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f3fc54219e4..77a88714517 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.7.0 (unreleased) - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Expose user location (Robert Schilling) + - API: Do not leak group existence via return code (Robert Schilling) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4921ae99e78..96af7d7675c 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -91,8 +91,7 @@ module API if can?(current_user, :read_group, group) group else - forbidden!("#{current_user.username} lacks sufficient "\ - "access to #{group.name}") + not_found!('Group') end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7383c7d11aa..083d5c459c6 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -61,7 +61,8 @@ describe API::API, api: true do it "should not return a group not attached to user1" do get api("/groups/#{group2.id}", user1) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end @@ -92,7 +93,8 @@ describe API::API, api: true do it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}", user1) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end end @@ -157,7 +159,8 @@ describe API::API, api: true do it "should not return a group not attached to user1" do get api("/groups/#{group2.id}/projects", user1) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end @@ -189,7 +192,8 @@ describe API::API, api: true do it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}/projects", user1) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end end @@ -247,7 +251,8 @@ describe API::API, api: true do it "should not remove a group not attached to user1" do delete api("/groups/#{group2.id}", user1) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end -- cgit v1.2.1 From ca40479c512f327c12adf51b47be46d75e4e333c Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 11:20:45 +0200 Subject: API: Avoid group leak while updating the group --- spec/requests/api/groups_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 083d5c459c6..37ddab83c30 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -135,10 +135,10 @@ describe API::API, api: true do end context 'when authenticated as an user that cannot see the group' do - it 'returns 403 when trying to update the group' do + it 'returns 404 when trying to update the group' do put api("/groups/#{group2.id}", user1), name: new_group_name - expect(response.status).to eq(403) + expect(response.status).to eq(404) end end end -- cgit v1.2.1 From 3240ecfbefc7ae5994be6ef01b52c1cbdaa09057 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 12:00:21 +0200 Subject: Added ability to add custom tags to transactions One use case for this is manually setting the "action" tag for Grape API calls. Due to Grape running blocks there are no human readable method names that can be used for the "action" tag, thus we have to set these manually on a case by case basis. --- CHANGELOG | 1 + lib/gitlab/metrics.rb | 10 ++++++++++ spec/lib/gitlab/metrics_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9b0c6ba4609..ac5c10a8a4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) + - Developers can now add custom tags to transactions (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 2a0a5629be5..484970c5a10 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -104,6 +104,16 @@ module Gitlab retval end + # Adds a tag to the current transaction (if any) + # + # name - The name of the tag to add. + # value - The value of the tag. + def self.tag_transaction(name, value) + trans = current_transaction + + trans.add_tag(name, value) if trans + end + # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. if enabled? diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 3dee13e27f4..10177c0e8dd 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -98,4 +98,29 @@ describe Gitlab::Metrics do end end end + + describe '.tag_transaction' do + context 'without a transaction' do + it 'does nothing' do + expect_any_instance_of(Gitlab::Metrics::Transaction). + not_to receive(:add_tag) + + Gitlab::Metrics.tag_transaction(:foo, 'bar') + end + end + + context 'with a transaction' do + let(:transaction) { Gitlab::Metrics::Transaction.new } + + it 'adds the tag to the transaction' do + expect(Gitlab::Metrics).to receive(:current_transaction). + and_return(transaction) + + expect(transaction).to receive(:add_tag). + with(:foo, 'bar') + + Gitlab::Metrics.tag_transaction(:foo, 'bar') + end + end + end end -- cgit v1.2.1 From 482f67edb46423d4fc567e061d6546d8dfafc7bb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 7 Apr 2016 14:07:17 +0200 Subject: API: Ability to move an issue --- CHANGELOG | 1 + doc/api/issues.md | 51 ++++++++++++++++++++++++++++++++++++++ lib/api/issues.rb | 23 +++++++++++++++++ spec/requests/api/issues_spec.rb | 53 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9b0c6ba4609..771d7e4799d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.7.0 (unreleased) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API - API: Ability to update a group (Robert Schilling) + - API: Ability to move issues - Fix Error 500 after renaming a project path (Stan Hu) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) diff --git a/doc/api/issues.md b/doc/api/issues.md index 1c635a6cdcf..a540a27ce11 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -351,6 +351,57 @@ DELETE /projects/:id/issues/:issue_id curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85 ``` +## Move an issue + +Moves an issue to a different project. If the operation is successful, a status code `200` together with moved issue is returned. If the project, issue, or target project is not found, error `404` is returned. If the target project equals the source project or the user has insufficient permissions to move an issue, error `400` together with an explaining error message is returned. + +``` +POST /projects/:id/issues/:issue_id/move +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | +| `new_project_id` | integer | yes | The ID the new project | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move +``` + +Example response: + +```json +{ + "id": 92, + "iid": 11, + "project_id": 5, + "title": "Sit voluptas tempora quisquam aut doloribus et.", + "description": "Repellat voluptas quibusdam voluptatem exercitationem.", + "state": "opened", + "created_at": "2016-04-05T21:41:45.652Z", + "updated_at": "2016-04-07T12:20:17.596Z", + "labels": [], + "milestone": null, + "assignee": { + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/axel.block" + }, + "author": { + "name": "Kris Steuber", + "username": "solon.cremin", + "id": 10, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/solon.cremin" + } +} +``` + ## Comments on issues Comments are done via the [notes](notes.md) resource. diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c4ea05ee6cf..894d9794322 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -195,6 +195,29 @@ module API end end + # Move an existing issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # new_project_id (required) - The ID of the new project + # Example Request: + # POST /projects/:id/issues/:issue_id/move + post ":id/issues/:issue_id/move" do + required_attributes! [:new_project_id] + + issue = user_project.issues.find(params[:issue_id]) + new_project = Project.find(params[:new_project_id]) + + begin + issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) + present issue, with: Entities::Issue + rescue ::Issues::MoveService::MoveError => error + render_api_error!(error.message, 400) + end + end + + # # Delete a project issue # # Parameters: diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 822d3ad3017..db4ee46975a 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -7,7 +7,7 @@ describe API::API, api: true do let(:author) { create(:author) } let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, namespace: user.namespace ) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -501,4 +501,55 @@ describe API::API, api: true do end end end + + describe '/projects/:id/issues/:issue_id/move' do + let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } + + it 'moves an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + new_project_id: target_project.id + + expect(response.status).to eq(201) + expect(json_response['project_id']).to eq(target_project.id) + end + + it 'returns an error if target and source project are the same' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + new_project_id: project.id + + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Cannot move issue to project it originates from!') + end + + it "returns an error if I don't have the permission" do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + new_project_id: target_project2.id + + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') + end + + it 'moves the issue to another namespace if I am admin' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", admin), + new_project_id: target_project2.id + + expect(response.status).to eq(201) + expect(json_response['project_id']).to eq(target_project2.id) + end + + it 'returns 404 if the source issue is not found' do + post api("/projects/#{project.id}/issues/123/move", user), + new_project_id: target_project.id + + expect(response.status).to eq(404) + end + + it 'returns 404 if the target project is not found' do + post api("/projects/1234/issues/#{issue.id}/move", user), + new_project_id: target_project.id + + expect(response.status).to eq(404) + end + end end -- cgit v1.2.1 From 244219376d84f0c78db3b898f0b7a18c88f9a840 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 13 Apr 2016 12:28:07 +0300 Subject: Tie example config to JIRA screenshot Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15203 --- doc/project_services/img/jira_service_page.png | Bin 35496 -> 49122 bytes doc/project_services/jira.md | 22 +++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/doc/project_services/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png index 2b37eda3520..c225daa81e1 100644 Binary files a/doc/project_services/img/jira_service_page.png and b/doc/project_services/img/jira_service_page.png differ diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md index 27170c1eb19..b626c746c79 100644 --- a/doc/project_services/jira.md +++ b/doc/project_services/jira.md @@ -1,9 +1,9 @@ # GitLab JIRA integration -_**Note:** +>**Note:** Full JIRA integration was previously exclusive to GitLab Enterprise Edition. With [GitLab 8.3 forward][8_3_post], this feature in now [backported][jira-ce] -to GitLab Community Edition as well._ +to GitLab Community Edition as well. --- @@ -88,8 +88,9 @@ password as they will be needed when configuring GitLab in the next section. ### Configuring GitLab -_**Note:** The currently supported JIRA versions are v6.x and v7.x. and GitLab -7.8 or higher is required._ +>**Note:** +The currently supported JIRA versions are v6.x and v7.x. and GitLab +7.8 or higher is required. --- @@ -113,13 +114,24 @@ Fill in the required details on the page, as described in the table below. | `Api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`. It is of the form: `https:///rest/api/2`. | | `Username` | The username of the user created in [configuring JIRA step](#configuring-jira). | | `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). | -| `JIRA issue transition` | This setting is very important to set up correctly. It is the ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot](img/jira_issues_workflow.png)). By default, this ID is set to `2` | +| `JIRA issue transition` | This setting is very important to set up correctly. It is the ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | After saving the configuration, your GitLab project will be able to interact with the linked JIRA project. +For example, given the settings below: + +- the JIRA URL is `https://jira.example.com` +- the project is named `GITLAB` +- the user is named `gitlab` +- the JIRA issue transition is 151 (based on the [JIRA issue transition][trans]) + +the following screenshot shows how the JIRA service settings should look like. + ![JIRA service page](img/jira_service_page.png) +[trans]: img/jira_issues_workflow.png + --- ## JIRA issues -- cgit v1.2.1 From 2b036025d619c51cff74e4eb430f50d43d1d8cdb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 18:38:18 +0200 Subject: Update tests for moving issues via API --- CHANGELOG | 2 +- doc/api/issues.md | 8 ++++-- lib/api/issues.rb | 14 +++++----- spec/requests/api/issues_spec.rb | 57 ++++++++++++++++++++++++++-------------- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 771d7e4799d..b86c9d1fa39 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,7 +14,7 @@ v 8.7.0 (unreleased) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API - API: Ability to update a group (Robert Schilling) - - API: Ability to move issues + - API: Ability to move issues (Robert Schilling) - Fix Error 500 after renaming a project path (Stan Hu) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) diff --git a/doc/api/issues.md b/doc/api/issues.md index a540a27ce11..a3ac48fba7e 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -353,7 +353,11 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c ## Move an issue -Moves an issue to a different project. If the operation is successful, a status code `200` together with moved issue is returned. If the project, issue, or target project is not found, error `404` is returned. If the target project equals the source project or the user has insufficient permissions to move an issue, error `400` together with an explaining error message is returned. +Moves an issue to a different project. If the operation is successful, a status +code `201` together with moved issue is returned. If the project, issue, or +target project is not found, error `404` is returned. If the target project +equals the source project or the user has insufficient permissions to move an +issue, error `400` together with an explaining error message is returned. ``` POST /projects/:id/issues/:issue_id/move @@ -363,7 +367,7 @@ POST /projects/:id/issues/:issue_id/move | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | | `issue_id` | integer | yes | The ID of a project's issue | -| `new_project_id` | integer | yes | The ID the new project | +| `to_project_id` | integer | yes | The ID the new project | ```bash curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 894d9794322..850e99981ff 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -198,20 +198,20 @@ module API # Move an existing issue # # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # new_project_id (required) - The ID of the new project + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # to_project_id (required) - The ID of the new project # Example Request: # POST /projects/:id/issues/:issue_id/move - post ":id/issues/:issue_id/move" do - required_attributes! [:new_project_id] + post ':id/issues/:issue_id/move' do + required_attributes! [:to_project_id] issue = user_project.issues.find(params[:issue_id]) - new_project = Project.find(params[:new_project_id]) + new_project = Project.find(params[:to_project_id]) begin issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) - present issue, with: Entities::Issue + present issue, with: Entities::Issue, current_user: current_user rescue ::Issues::MoveService::MoveError => error render_api_error!(error.message, 400) end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index db4ee46975a..3d7a31cbb6a 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -508,48 +508,65 @@ describe API::API, api: true do it 'moves an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), - new_project_id: target_project.id + to_project_id: target_project.id expect(response.status).to eq(201) expect(json_response['project_id']).to eq(target_project.id) end - it 'returns an error if target and source project are the same' do - post api("/projects/#{project.id}/issues/#{issue.id}/move", user), - new_project_id: project.id + context 'when source and target projects are the same' do + it 'returns 400 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + to_project_id: project.id - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Cannot move issue to project it originates from!') + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Cannot move issue to project it originates from!') + end end - it "returns an error if I don't have the permission" do - post api("/projects/#{project.id}/issues/#{issue.id}/move", user), - new_project_id: target_project2.id + context 'when the user does not have the permission to move issues' do + it 'returns 400 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + to_project_id: target_project2.id - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') + end end it 'moves the issue to another namespace if I am admin' do post api("/projects/#{project.id}/issues/#{issue.id}/move", admin), - new_project_id: target_project2.id + to_project_id: target_project2.id expect(response.status).to eq(201) expect(json_response['project_id']).to eq(target_project2.id) end - it 'returns 404 if the source issue is not found' do - post api("/projects/#{project.id}/issues/123/move", user), - new_project_id: target_project.id + context 'when issue does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/#{project.id}/issues/123/move", user), + to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response.status).to eq(404) + end end - it 'returns 404 if the target project is not found' do - post api("/projects/1234/issues/#{issue.id}/move", user), - new_project_id: target_project.id + context 'when source project does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/123/issues/#{issue.id}/move", user), + to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response.status).to eq(404) + end + end + + context 'when target project does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + to_project_id: 123 + + expect(response.status).to eq(404) + end end end end -- cgit v1.2.1 From fdbf3682023a2ed647c625ec0609dac3227218b2 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 12:03:05 +0200 Subject: Fix doc for moving an issue --- doc/api/issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index a3ac48fba7e..f09847aef95 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -367,7 +367,7 @@ POST /projects/:id/issues/:issue_id/move | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | | `issue_id` | integer | yes | The ID of a project's issue | -| `to_project_id` | integer | yes | The ID the new project | +| `to_project_id` | integer | yes | The ID of the new project | ```bash curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move -- cgit v1.2.1 From 4cd04443f5f69665ce1139726751af678e0e55c3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 12:10:12 +0200 Subject: Fix group_member_spec to not leak information --- spec/requests/api/group_members_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 3e8b4aa1f88..96d89e69209 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -42,9 +42,10 @@ describe API::API, api: true do end end - it "users not part of the group should get access error" do + it 'users not part of the group should get access error' do get api("/groups/#{group_with_members.id}/members", stranger) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end end @@ -165,12 +166,13 @@ describe API::API, api: true do end end - describe "DELETE /groups/:id/members/:user_id" do - context "when not a member of the group" do + describe 'DELETE /groups/:id/members/:user_id' do + context 'when not a member of the group' do it "should not delete guest's membership of group_with_members" do random_user = create(:user) delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - expect(response.status).to eq(403) + + expect(response.status).to eq(404) end end -- cgit v1.2.1 From 33f786b5d32d02a032af9d258167ddd2bb61d44a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 13 Apr 2016 13:12:05 +0300 Subject: clean up ExclusiveLease --- lib/gitlab/exclusive_lease.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index c2260a5f7ac..ffe49364379 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -52,11 +52,6 @@ module Gitlab private - def redis - # Maybe someday we want to use a connection pool... - @redis ||= Redis.new(url: Gitlab::RedisConfig.url) - end - def redis_key "gitlab:exclusive_lease:#{@key}" end -- cgit v1.2.1 From bd0be13f5b52b8eaee019d722980b29acbc55b05 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Apr 2016 14:17:42 +0200 Subject: API: Ability to subscribe and unsubscribe from an issue --- CHANGELOG | 1 + doc/api/issues.md | 108 +++++++++++++++++++++++++++++++++++++++ lib/api/helpers.rb | 4 ++ lib/api/issues.rb | 53 +++++++++++++++++++ spec/requests/api/issues_spec.rb | 31 +++++++++++ 5 files changed, 197 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c633c4bb35f..01fffcc3696 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.7.0 (unreleased) - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) + - API: Ability to subscribe and unsubscribe from an issue (Robert Schilling) - Expose project badges in project settings - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) diff --git a/doc/api/issues.md b/doc/api/issues.md index f09847aef95..4f7d948eba1 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -406,6 +406,114 @@ Example response: } ``` +## Subscribe to an issue + +Subscribes to an issue to receive notifications. If the operation is successful, +status code `201` together with the updated issue is returned. If the user is +already subscribed to the issue, the status code `304` is returned. If the +project or issue is not found, status code `404` is returned. + +``` +POST /projects/:id/issues/:issue_id/subscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe +``` + +Example response: + +```json +{ + "id": 92, + "iid": 11, + "project_id": 5, + "title": "Sit voluptas tempora quisquam aut doloribus et.", + "description": "Repellat voluptas quibusdam voluptatem exercitationem.", + "state": "opened", + "created_at": "2016-04-05T21:41:45.652Z", + "updated_at": "2016-04-07T12:20:17.596Z", + "labels": [], + "milestone": null, + "assignee": { + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/axel.block" + }, + "author": { + "name": "Kris Steuber", + "username": "solon.cremin", + "id": 10, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/solon.cremin" + } +} +``` + +## Unsubscribe from an issue + +Unsubscribes from an issue to not receive notifications from that issue. If the +operation is successful, status code `201` together with the updated issue is +returned. If the user is not subscribed to the issue, the status code `304` +is returned. If the project or issue is not found, status code `404` is +returned. + +``` +POST /projects/:id/issues/:issue_id/unsubscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe +``` + +Example response: + +```json +{ + "id": 93, + "iid": 12, + "project_id": 5, + "title": "Incidunt et rerum ea expedita iure quibusdam.", + "description": "Et cumque architecto sed aut ipsam.", + "state": "opened", + "created_at": "2016-04-05T21:41:45.217Z", + "updated_at": "2016-04-07T13:02:37.905Z", + "labels": [], + "milestone": null, + "assignee": { + "name": "Edwardo Grady", + "username": "keyon", + "id": 21, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/keyon" + }, + "author": { + "name": "Vivian Hermann", + "username": "orville", + "id": 11, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", + "web_url": "http://lgitlab.example.com/u/orville" + }, + "subscribed": false +} +``` + ## Comments on issues Comments are done via the [notes](notes.md) resource. diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4921ae99e78..aa0597564ed 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -241,6 +241,10 @@ module API render_api_error!('413 Request Entity Too Large', 413) end + def not_modified! + render_api_error!('304 Not modified', 304) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 850e99981ff..49911fa2988 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -231,6 +231,59 @@ module API authorize!(:destroy_issue, issue) issue.destroy end + + # Subscribes to a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id + post ":id/issues/:issue_idsubscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if !issue.subscribed?(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end + + # Subscribes to a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id/subscribe + post ":id/issues/:issue_id/subscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if !issue.subscribed?(current_user) + issue.toggle_subscription(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end + + # Unsubscribes from a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id/unsubscribe + post ":id/issues/:issue_id/unsubscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if issue.subscribed?(current_user) + issue.unsubscribe(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 3d7a31cbb6a..437caf466b0 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } + let(:user2) { create(:user) } let(:non_member) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:assignee) } @@ -569,4 +570,34 @@ describe API::API, api: true do end end end + + describe 'POST :id/issues/:issue_id/subscribe' do + it 'subscribes to an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(true) + end + + it 'returns 304 if already subscribed' do + post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) + + expect(response.status).to eq(304) + end + end + + describe 'POST :id/issues/:issue_id/unsubscribe' do + it 'unsubscribes from an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(false) + end + + it 'returns 304 if not subscribed' do + post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2) + + expect(response.status).to eq(304) + end + end end -- cgit v1.2.1 From f875189b3962bde6e4b7b8c4ffdd18af83cbc922 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Apr 2016 15:03:34 +0200 Subject: API: Ability to subscribe and unsubscribe from a merge request --- CHANGELOG | 2 +- doc/api/merge_requests.md | 147 +++++++++++++++++++++++++++++++ lib/api/issues.rb | 4 +- lib/api/merge_requests.rb | 36 ++++++++ spec/requests/api/merge_requests_spec.rb | 30 +++++++ 5 files changed, 216 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 01fffcc3696..1184ffce76c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,7 @@ v 8.7.0 (unreleased) - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - - API: Ability to subscribe and unsubscribe from an issue (Robert Schilling) + - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 20db73ea6c0..16cb8926265 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -606,3 +606,150 @@ Example response: }, ] ``` + +## Subscribe to a merge request + +Subscribes to a merge request to receive notification. If the operation is +successful, status code `201` together with the updated merge request is +returned. If the user is already subscribed to the merge request, the status +code `304` is returned. If the project or merge request is not found, status +code `404` is returned. + +``` +POST /projects/:id/merge_requests/:merge_request_id/subscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +``` + +Example response: + +```json +{ + "id": 17, + "iid": 1, + "project_id": 5, + "title": "Et et sequi est impedit nulla ut rem et voluptatem.", + "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "state": "opened", + "created_at": "2016-04-05T21:42:23.233Z", + "updated_at": "2016-04-05T22:11:52.900Z", + "target_branch": "ui-dev-kit", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Eileen Skiles", + "username": "leila", + "id": 19, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/leila" + }, + "assignee": { + "name": "Celine Wehner", + "username": "carli", + "id": 16, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/carli" + }, + "source_project_id": 5, + "target_project_id": 5, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 7, + "iid": 1, + "project_id": 5, + "title": "v2.0", + "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "state": "closed", + "created_at": "2016-04-05T21:41:40.905Z", + "updated_at": "2016-04-05T21:41:40.905Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true +} +``` +## Unsubscribe from a merge request + +Unsubscribes from a merge request to not receive notifications from that merge +request. If the operation is successful, status code `201` together with the +updated merge request is returned. If the user is not subscribed to the merge +request, the status code `304` is returned. If the project or merge request is +not found, status code `404` is returned. + +``` +POST /projects/:id/merge_requests/:merge_request_id/unsubscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +``` + +Example response: + +```json +{ + "id": 17, + "iid": 1, + "project_id": 5, + "title": "Et et sequi est impedit nulla ut rem et voluptatem.", + "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "state": "opened", + "created_at": "2016-04-05T21:42:23.233Z", + "updated_at": "2016-04-05T22:11:52.900Z", + "target_branch": "ui-dev-kit", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Eileen Skiles", + "username": "leila", + "id": 19, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/leila" + }, + "assignee": { + "name": "Celine Wehner", + "username": "carli", + "id": 16, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/carli" + }, + "source_project_id": 5, + "target_project_id": 5, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 7, + "iid": 1, + "project_id": 5, + "title": "v2.0", + "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "state": "closed", + "created_at": "2016-04-05T21:41:40.905Z", + "updated_at": "2016-04-05T21:41:40.905Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": false +} +``` diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 49911fa2988..049618c00f7 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -235,7 +235,7 @@ module API # Subscribes to a project issue # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id @@ -270,7 +270,7 @@ module API # Unsubscribes from a project issue # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id/unsubscribe diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4e7de8867b4..d166484ba54 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -327,6 +327,42 @@ module API issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) present paginate(issues), with: Entities::Issue, current_user: current_user end + + # Subscribes to a merge request + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of a merge request + # Example Request: + # POST /projects/:id/issues/:merge_request_id/subscribe + post "#{path}/subscribe" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + if !merge_request.subscribed?(current_user) + merge_request.toggle_subscription(current_user) + present merge_request, with: Entities::MergeRequest, current_user: current_user + else + not_modified! + end + end + + # Unsubscribes from a merge request + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of a merge request + # Example Request: + # POST /projects/:id/merge_requests/:merge_request_id/unsubscribe + post "#{path}/unsubscribe" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + if merge_request.subscribed?(current_user) + merge_request.unsubscribe(current_user) + present merge_request, with: Entities::MergeRequest, current_user: current_user + else + not_modified! + end + end end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 25fa30b2f21..b71c72a3829 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -516,6 +516,36 @@ describe API::API, api: true do end end + describe 'POST :id/merge_requests/:merge_request_id/subscribe' do + it 'subscribes to a merge request' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(true) + end + + it 'returns 304 if already subscribed' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user) + + expect(response.status).to eq(304) + end + end + + describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do + it 'unsubscribes from a merge request' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(false) + end + + it 'returns 304 if not subscribed' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin) + + expect(response.status).to eq(304) + end + end + def mr_with_later_created_and_updated_at_time merge_request merge_request.created_at += 1.hour -- cgit v1.2.1 From fa3009095fbb995550a20e5d0cbc994f4290fbea Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 14:46:59 +0200 Subject: Make subscription API more RESTful --- doc/api/issues.md | 27 ++++++++++++++------------- doc/api/merge_requests.md | 28 ++++++++++++++-------------- lib/api/helpers.rb | 2 +- lib/api/issues.rb | 29 ++++++----------------------- lib/api/merge_requests.rb | 14 +++++++------- spec/requests/api/issues_spec.rb | 26 +++++++++++++++++++------- spec/requests/api/merge_requests_spec.rb | 14 +++++++------- 7 files changed, 68 insertions(+), 72 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index 4f7d948eba1..42024becc36 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -408,13 +408,14 @@ Example response: ## Subscribe to an issue -Subscribes to an issue to receive notifications. If the operation is successful, -status code `201` together with the updated issue is returned. If the user is -already subscribed to the issue, the status code `304` is returned. If the -project or issue is not found, status code `404` is returned. +Subscribes the authenticated user to an issue to receive notifications. If the +operation is successful, status code `201` together with the updated issue is +returned. If the user is already subscribed to the issue, the status code `304` +is returned. If the project or issue is not found, status code `404` is +returned. ``` -POST /projects/:id/issues/:issue_id/subscribe +POST /projects/:id/issues/:issue_id/subscription ``` | Attribute | Type | Required | Description | @@ -423,7 +424,7 @@ POST /projects/:id/issues/:issue_id/subscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription ``` Example response: @@ -461,14 +462,14 @@ Example response: ## Unsubscribe from an issue -Unsubscribes from an issue to not receive notifications from that issue. If the -operation is successful, status code `201` together with the updated issue is -returned. If the user is not subscribed to the issue, the status code `304` -is returned. If the project or issue is not found, status code `404` is -returned. +Unsubscribes the authenticated user from the issue to not receive notifications +from it. If the operation is successful, status code `200` together with the +updated issue is returned. If the user is not subscribed to the issue, the +status code `304` is returned. If the project or issue is not found, status code +`404` is returned. ``` -POST /projects/:id/issues/:issue_id/unsubscribe +DELETE /projects/:id/issues/:issue_id/subscription ``` | Attribute | Type | Required | Description | @@ -477,7 +478,7 @@ POST /projects/:id/issues/:issue_id/unsubscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription ``` Example response: diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 16cb8926265..3c18ebfa31e 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -609,14 +609,14 @@ Example response: ## Subscribe to a merge request -Subscribes to a merge request to receive notification. If the operation is -successful, status code `201` together with the updated merge request is -returned. If the user is already subscribed to the merge request, the status -code `304` is returned. If the project or merge request is not found, status -code `404` is returned. +Subscribes the authenticated user to a merge request to receive notification. If +the operation is successful, status code `201` together with the updated merge +request is returned. If the user is already subscribed to the merge request, the +status code `304` is returned. If the project or merge request is not found, +status code `404` is returned. ``` -POST /projects/:id/merge_requests/:merge_request_id/subscribe +POST /projects/:id/merge_requests/:merge_request_id/subscription ``` | Attribute | Type | Required | Description | @@ -625,7 +625,7 @@ POST /projects/:id/merge_requests/:merge_request_id/subscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription ``` Example response: @@ -682,14 +682,14 @@ Example response: ``` ## Unsubscribe from a merge request -Unsubscribes from a merge request to not receive notifications from that merge -request. If the operation is successful, status code `201` together with the -updated merge request is returned. If the user is not subscribed to the merge -request, the status code `304` is returned. If the project or merge request is -not found, status code `404` is returned. +Unsubscribes the authenticated user from a merge request to not receive +notifications from that merge request. If the operation is successful, status +code `200` together with the updated merge request is returned. If the user is +not subscribed to the merge request, the status code `304` is returned. If the +project or merge request is not found, status code `404` is returned. ``` -POST /projects/:id/merge_requests/:merge_request_id/unsubscribe +DELETE /projects/:id/merge_requests/:merge_request_id/subscription ``` | Attribute | Type | Required | Description | @@ -698,7 +698,7 @@ POST /projects/:id/merge_requests/:merge_request_id/unsubscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription ``` Example response: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index aa0597564ed..54452f763a6 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -242,7 +242,7 @@ module API end def not_modified! - render_api_error!('304 Not modified', 304) + render_api_error!('304 Not Modified', 304) end def render_validation_error!(model) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 049618c00f7..37d25073074 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -238,32 +238,15 @@ module API # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: - # POST /projects/:id/issues/:issue_id - post ":id/issues/:issue_idsubscribe" do + # POST /projects/:id/issues/:issue_id/subscription + post ":id/issues/:issue_id/subscription" do issue = user_project.issues.find_by(id: params[:issue_id]) - if !issue.subscribed?(current_user) - present issue, with: Entities::Issue, current_user: current_user - else + if issue.subscribed?(current_user) not_modified! - end - end - - # Subscribes to a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # POST /projects/:id/issues/:issue_id/subscribe - post ":id/issues/:issue_id/subscribe" do - issue = user_project.issues.find_by(id: params[:issue_id]) - - if !issue.subscribed?(current_user) + else issue.toggle_subscription(current_user) present issue, with: Entities::Issue, current_user: current_user - else - not_modified! end end @@ -273,8 +256,8 @@ module API # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: - # POST /projects/:id/issues/:issue_id/unsubscribe - post ":id/issues/:issue_id/unsubscribe" do + # DELETE /projects/:id/issues/:issue_id/subscription + delete ":id/issues/:issue_id/subscription" do issue = user_project.issues.find_by(id: params[:issue_id]) if issue.subscribed?(current_user) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d166484ba54..7e78609ecb9 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -334,15 +334,15 @@ module API # id (required) - The ID of a project # merge_request_id (required) - The ID of a merge request # Example Request: - # POST /projects/:id/issues/:merge_request_id/subscribe - post "#{path}/subscribe" do + # POST /projects/:id/issues/:merge_request_id/subscription + post "#{path}/subscription" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - if !merge_request.subscribed?(current_user) + if merge_request.subscribed?(current_user) + not_modified! + else merge_request.toggle_subscription(current_user) present merge_request, with: Entities::MergeRequest, current_user: current_user - else - not_modified! end end @@ -352,8 +352,8 @@ module API # id (required) - The ID of a project # merge_request_id (required) - The ID of a merge request # Example Request: - # POST /projects/:id/merge_requests/:merge_request_id/unsubscribe - post "#{path}/unsubscribe" do + # DELETE /projects/:id/merge_requests/:merge_request_id/subscription + delete "#{path}/subscription" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) if merge_request.subscribed?(current_user) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 437caf466b0..8361a1649e0 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -571,33 +571,45 @@ describe API::API, api: true do end end - describe 'POST :id/issues/:issue_id/subscribe' do + describe 'POST :id/issues/:issue_id/subscription' do it 'subscribes to an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) expect(response.status).to eq(304) end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/subscription", user) + + expect(response.status).to eq(404) + end end - describe 'POST :id/issues/:issue_id/unsubscribe' do + describe 'DELETE :id/issues/:issue_id/subscription' do it 'unsubscribes from an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(304) end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/subscription", user) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b71c72a3829..c247fcf9c96 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -516,31 +516,31 @@ describe API::API, api: true do end end - describe 'POST :id/merge_requests/:merge_request_id/subscribe' do + describe 'POST :id/merge_requests/:merge_request_id/subscription' do it 'subscribes to a merge request' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin) + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) expect(response.status).to eq(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user) + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) expect(response.status).to eq(304) end end - describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do + describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do it 'unsubscribes from a merge request' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user) + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin) + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) expect(response.status).to eq(304) end -- cgit v1.2.1 From ea2193aaeb1127746dc78d2dda7037d998911662 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 15:52:16 +0200 Subject: API: Star and unstar a project --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/projects.md | 127 +++++++++++++++++++++++++++++++++++++ lib/api/helpers.rb | 4 ++ lib/api/projects.rb | 33 ++++++++++ spec/requests/api/projects_spec.rb | 50 +++++++++++++++ 6 files changed, 216 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c633c4bb35f..7a2af5a0eb8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.7.0 (unreleased) - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) + - API: Ability to star and unstar a project (Robert Schilling) - Add default scope to projects to exclude projects pending deletion - Allow to close merge requests which source projects(forks) are deleted. - Ensure empty recipients are rejected in BuildsEmailService diff --git a/doc/api/README.md b/doc/api/README.md index 7629ef294ac..3a8fa6cebd1 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -108,6 +108,7 @@ The following table shows the possible return codes for API requests. | ------------- | ----------- | | `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | | `201 Created` | The `POST` request was successful and the resource is returned as JSON. | +| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | | `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | | `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | | `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | diff --git a/doc/api/projects.md b/doc/api/projects.md index ab716c229dc..c5ffc5514c7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -491,6 +491,133 @@ Parameters: - `id` (required) - The ID of the project to be forked +### Star a project + +Stars a given project. Returns status code 201 and the project on success and +304 if the project is already starred. + +``` +POST /projects/:id/star +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the project | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" +``` + +Example response: + +```json +{ + "id": 3, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 10, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": true, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 1 +} +``` + +### Unstar a project + +Unstars a given project. Returns status code 201 and the project on success +and 304 if the project is already unstarred. + +``` +POST /projects/:id/unstar +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the project | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar" +``` + +Example response: + +```json +{ + "id": 3, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 10, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": true, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0 +} +``` + + ### Archive a project Archives the project if the user is either admin or the project owner of this project. This action is diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4921ae99e78..aa0597564ed 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -241,6 +241,10 @@ module API render_api_error!('413 Request Entity Too Large', 413) end + def not_modified! + render_api_error!('304 Not modified', 304) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 24b31005475..ebcf7a4eedd 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -272,6 +272,39 @@ module API present user_project, with: Entities::Project end + # Star project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # POST /projects/:id/star + post ':id/star' do + if !current_user.starred?(user_project) + current_user.toggle_star(user_project) + user_project.reload + present user_project, with: Entities::Project + + else + not_modified! + end + end + + # Unstar project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # POST /projects/:id/unstar + post ':id/unstar' do + if current_user.starred?(user_project) + current_user.toggle_star(user_project) + user_project.reload + present user_project, with: Entities::Project + else + not_modified! + end + end + # Remove project # # Parameters: diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index be2034e0f39..f05622f77fe 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1020,6 +1020,56 @@ describe API::API, api: true do end end + describe 'POST /projects/:id/star' do + context 'on an unstarred project' do + it 'stars the project' do + post api("/projects/#{project.id}/star", user) + + expect(response.status).to eq(201) + expect(json_response['star_count']).to eq(1) + end + end + + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'does not modify the star count' do + post api("/projects/#{project.id}/star", user) + + expect(response.status).to eq(304) + expect(project.star_count).to eq(1) + end + end + end + + describe 'POST /projects/:id/unstar' do + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'unstars the project' do + post api("/projects/#{project.id}/unstar", user) + + expect(response.status).to eq(201) + expect(json_response['star_count']).to eq(0) + end + end + + context 'on an unstarred project' do + it 'does not modify the star count' do + post api("/projects/#{project.id}/unstar", user) + + expect(response.status).to eq(304) + expect(project.star_count).to eq(0) + end + end + end + describe 'DELETE /projects/:id' do context 'when authenticated as user' do it 'should remove project' do -- cgit v1.2.1 From 3ab9ea8dae1edc6ab8c8563843342890736eb24c Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 18:52:43 +0200 Subject: Make staring API more restful --- doc/api/projects.md | 12 ++++++------ lib/api/projects.rb | 11 +++++------ spec/requests/api/projects_spec.rb | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index c5ffc5514c7..b25c9080080 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -493,8 +493,8 @@ Parameters: ### Star a project -Stars a given project. Returns status code 201 and the project on success and -304 if the project is already starred. +Stars a given project. Returns status code `201` and the project on success and +`304` if the project is already starred. ``` POST /projects/:id/star @@ -556,11 +556,11 @@ Example response: ### Unstar a project -Unstars a given project. Returns status code 201 and the project on success -and 304 if the project is already unstarred. +Unstars a given project. Returns status code `200` and the project on success +and `304` if the project is not starred. ``` -POST /projects/:id/unstar +DELETE /projects/:id/star ``` | Attribute | Type | Required | Description | @@ -568,7 +568,7 @@ POST /projects/:id/unstar | `id` | integer | yes | The ID of the project | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" ``` Example response: diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ebcf7a4eedd..c7fdfbfe57b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -279,13 +279,12 @@ module API # Example Request: # POST /projects/:id/star post ':id/star' do - if !current_user.starred?(user_project) + if current_user.starred?(user_project) + not_modified! + else current_user.toggle_star(user_project) user_project.reload present user_project, with: Entities::Project - - else - not_modified! end end @@ -294,8 +293,8 @@ module API # Parameters: # id (required) - The ID of a project # Example Request: - # POST /projects/:id/unstar - post ':id/unstar' do + # DELETE /projects/:id/unstar + delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index f05622f77fe..2a7c55fe65e 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1045,7 +1045,7 @@ describe API::API, api: true do end end - describe 'POST /projects/:id/unstar' do + describe 'DELETE /projects/:id/star' do context 'on a starred project' do before do user.toggle_star(project) @@ -1053,16 +1053,16 @@ describe API::API, api: true do end it 'unstars the project' do - post api("/projects/#{project.id}/unstar", user) + delete api("/projects/#{project.id}/star", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['star_count']).to eq(0) end end context 'on an unstarred project' do it 'does not modify the star count' do - post api("/projects/#{project.id}/unstar", user) + delete api("/projects/#{project.id}/star", user) expect(response.status).to eq(304) expect(project.star_count).to eq(0) -- cgit v1.2.1 From 3b9edce803c91b5f51675291fdf22f1159cea456 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 15:19:50 +0200 Subject: Instrument the HousekeepingService class This allows us to track how much time is spent in updating the "pushes_since_gc" column as well as the time needed to obtain the lease. --- CHANGELOG | 1 + app/services/projects/housekeeping_service.rb | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5399e3e5b8b..ec8db471572 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) + - The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Developers can now add custom tags to transactions (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index a0973c5d260..3b7c36f0908 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -26,7 +26,9 @@ module Projects GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) ensure - @project.update_column(:pushes_since_gc, 0) + Gitlab::Metrics.measure(:reset_pushes_since_gc) do + @project.update_column(:pushes_since_gc, 0) + end end def needed? @@ -34,14 +36,18 @@ module Projects end def increment! - @project.increment!(:pushes_since_gc) + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + @project.increment!(:pushes_since_gc) + end end private def try_obtain_lease - lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) - lease.try_obtain + Gitlab::Metrics.measure(:obtain_housekeeping_lease) do + lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) + lease.try_obtain + end end end end -- cgit v1.2.1 From acf911eeae0e952aec52d0e491efb69c99fb31f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 12 Apr 2016 18:09:52 +0200 Subject: Fix a bug with trailing slash in bamboo_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, improve specs for BambooService Similar to https://gitlab.com/gitlab-org/gitlab-ce/issues/3515 Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/models/project_services/bamboo_service.rb | 20 +- .../models/project_services/bamboo_service_spec.rb | 262 +++++++++++++++++---- 3 files changed, 221 insertions(+), 62 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 382318a203c..8646d99b64d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.7.0 (unreleased) - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 - Add endpoints to archive or unarchive a project !3372 + - Fix a bug whith trailing slash in bamboo_url - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 9e7f642180e..060062aaf7a 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -82,17 +82,17 @@ class BambooService < CiService end def build_info(sha) - url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s if username.blank? && password.blank? - @response = HTTParty.get(parsed_url.to_s, verify: false) + @response = HTTParty.get(url, verify: false) else - get_url = "#{url}&os_authType=basic" + url << '&os_authType=basic' auth = { - username: username, - password: password, + username: username, + password: password } - @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end end @@ -101,11 +101,11 @@ class BambooService < CiService if @response.code != 200 || @response['results']['results']['size'] == '0' # If actual build link can't be determined, send user to build summary page. - "#{bamboo_url}/browse/#{build_key}" + URI.join(bamboo_url, "/browse/#{build_key}").to_s else # If actual build link is available, go to build result page. result_key = @response['results']['results']['result']['planResultKey']['key'] - "#{bamboo_url}/browse/#{result_key}" + URI.join(bamboo_url, "/browse/#{result_key}").to_s end end @@ -134,7 +134,7 @@ class BambooService < CiService return unless supported_events.include?(data[:object_kind]) # Bamboo requires a GET and does not take any data. - self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", - verify: false) + url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s + self.class.get(url, verify: false) end end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index c34b2487ecf..31b2c90122d 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -21,74 +21,232 @@ require 'spec_helper' describe BambooService, models: true do - describe "Associations" do + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } - - context "when a password was previously set" do - before do - @bamboo_service = BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) + describe 'Validations' do + describe '#bamboo_url' do + it 'does not validate the presence of bamboo_url if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:bamboo_url) + end + + it 'validates the presence of bamboo_url if service is active' do + bamboo_service = service + bamboo_service.active = true + + expect(bamboo_service).to validate_presence_of(:bamboo_url) + end + end + + describe '#build_key' do + it 'does not validate the presence of build_key if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:build_key) end - - it "reset password if url changed" do - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.save - expect(@bamboo_service.password).to be_nil + + it 'validates the presence of build_key if service is active' do + bamboo_service = service + bamboo_service.active = true + + expect(bamboo_service).to validate_presence_of(:build_key) + end + end + + describe '#username' do + it 'does not validate the presence of username if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:username) + end + + it 'does not validate the presence of username if username is nil' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.password = nil + + expect(bamboo_service).not_to validate_presence_of(:username) + end + + it 'validates the presence of username if service is active and username is present' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.password = 'secret' + + expect(bamboo_service).to validate_presence_of(:username) end - - it "does not reset password if username changed" do - @bamboo_service.username = "some_name" - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") + end + + describe '#password' do + it 'does not validate the presence of password if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:password) end - it "does not reset password if new url is set together with password, even if it's the same password" do - @bamboo_service.bamboo_url = 'http://gitlab_edited.com' - @bamboo_service.password = 'password' - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") - expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") + it 'does not validate the presence of password if username is nil' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.username = nil + + expect(bamboo_service).not_to validate_presence_of(:password) end - it "should reset password if url changed, even if setter called multiple times" do - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.save - expect(@bamboo_service.password).to be_nil + it 'validates the presence of password if service is active and username is present' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.username = 'john' + + expect(bamboo_service).to validate_presence_of(:password) end end - - context "when no password was previously set" do - before do - @bamboo_service = BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic' - } - ) + end + + describe 'Callbacks' do + describe 'before_update :reset_password' do + context 'when a password was previously set' do + it 'resets password if url changed' do + bamboo_service = service + + bamboo_service.bamboo_url = 'http://gitlab1.com' + bamboo_service.save + + expect(bamboo_service.password).to be_nil + end + + it 'does not reset password if username changed' do + bamboo_service = service + + bamboo_service.username = 'some_name' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + bamboo_service = service + + bamboo_service.bamboo_url = 'http://gitlab_edited.com' + bamboo_service.password = 'password' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com') + end end - it "saves password if new url is set together with password" do - @bamboo_service.bamboo_url = 'http://gitlab_edited.com' - @bamboo_service.password = 'password' - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") - expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") + it 'saves password if new url is set together with password when no password was previously set' do + bamboo_service = service + bamboo_service.password = nil + + bamboo_service.bamboo_url = 'http://gitlab_edited.com' + bamboo_service.password = 'password' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com') end + end + end + + describe '#build_page' do + it 'returns a specific URL when status is 500' do + stub_request(status: 500) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') + end + + it 'returns a specific URL when response has no results' do + stub_request(body: %Q({"results":{"results":{"size":"0"}}})) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') + end + + it 'returns a build URL when bamboo_url has no trailing slash' do + stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) + + expect(service(bamboo_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') + end + + it 'returns a build URL when bamboo_url has a trailing slash' do + stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) + + expect(service(bamboo_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') + end + end + + describe '#commit_status' do + it 'sets commit status to :error when status is 500' do + stub_request(status: 500) + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + + it 'sets commit status to "pending" when status is 404' do + stub_request(status: 404) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "pending" when response has no results' do + stub_request(body: %Q({"results":{"results":{"size":"0"}}})) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "success" when build state contains Success' do + stub_request(build_state: 'YAY Success!') + expect(service.commit_status('123', 'unused')).to eq('success') end + + it 'sets commit status to "failed" when build state contains Failed' do + stub_request(build_state: 'NO Failed!') + + expect(service.commit_status('123', 'unused')).to eq('failed') + end + + it 'sets commit status to "pending" when build state contains Pending' do + stub_request(build_state: 'NO Pending!') + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to :error when build state is unknown' do + stub_request(build_state: 'FOO BAR!') + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + end + + def service(bamboo_url: 'http://gitlab.com') + described_class.create( + project: build_stubbed(:empty_project), + properties: { + bamboo_url: bamboo_url, + username: 'mic', + password: 'password', + build_key: 'foo' + } + ) + end + + def stub_request(status: 200, body: nil, build_state: 'success') + bamboo_full_url = 'http://mic:password@gitlab.com/rest/api/latest/result?label=123&os_authType=basic' + body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}}) + + WebMock.stub_request(:get, bamboo_full_url).to_return( + status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body + ) end end -- cgit v1.2.1 From 54231aa4e036179d035ddd3641bc15a5b31883f2 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 12:50:00 +0200 Subject: Styling changes to code and docs --- doc/api/projects.md | 1 - lib/api/helpers.rb | 2 +- lib/api/projects.rb | 4 +++- spec/requests/api/projects_spec.rb | 10 ++++------ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index b25c9080080..de1faadebf5 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -617,7 +617,6 @@ Example response: } ``` - ### Archive a project Archives the project if the user is either admin or the project owner of this project. This action is diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index aa0597564ed..54452f763a6 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -242,7 +242,7 @@ module API end def not_modified! - render_api_error!('304 Not modified', 304) + render_api_error!('304 Not Modified', 304) end def render_validation_error!(model) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c7fdfbfe57b..cc2c7a0c503 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -284,6 +284,7 @@ module API else current_user.toggle_star(user_project) user_project.reload + present user_project, with: Entities::Project end end @@ -293,11 +294,12 @@ module API # Parameters: # id (required) - The ID of a project # Example Request: - # DELETE /projects/:id/unstar + # DELETE /projects/:id/star delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload + present user_project, with: Entities::Project else not_modified! diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2a7c55fe65e..fccd08bd6da 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1023,7 +1023,7 @@ describe API::API, api: true do describe 'POST /projects/:id/star' do context 'on an unstarred project' do it 'stars the project' do - post api("/projects/#{project.id}/star", user) + expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) expect(response.status).to eq(201) expect(json_response['star_count']).to eq(1) @@ -1037,10 +1037,9 @@ describe API::API, api: true do end it 'does not modify the star count' do - post api("/projects/#{project.id}/star", user) + expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } expect(response.status).to eq(304) - expect(project.star_count).to eq(1) end end end @@ -1053,7 +1052,7 @@ describe API::API, api: true do end it 'unstars the project' do - delete api("/projects/#{project.id}/star", user) + expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) expect(response.status).to eq(200) expect(json_response['star_count']).to eq(0) @@ -1062,10 +1061,9 @@ describe API::API, api: true do context 'on an unstarred project' do it 'does not modify the star count' do - delete api("/projects/#{project.id}/star", user) + expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } expect(response.status).to eq(304) - expect(project.star_count).to eq(0) end end end -- cgit v1.2.1 From a9200d93d3e3d586302887fcaa0cf8f5fbd9a613 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 16:30:20 +0200 Subject: Ensure that issues and merge requests are found --- doc/api/merge_requests.md | 1 + lib/api/issues.rb | 8 ++++---- spec/requests/api/issues_spec.rb | 6 +++--- spec/requests/api/merge_requests_spec.rb | 12 ++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3c18ebfa31e..2057f9d77aa 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -680,6 +680,7 @@ Example response: "subscribed": true } ``` + ## Unsubscribe from a merge request Unsubscribes the authenticated user from a merge request to not receive diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 37d25073074..4cdecadfe0f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -239,8 +239,8 @@ module API # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id/subscription - post ":id/issues/:issue_id/subscription" do - issue = user_project.issues.find_by(id: params[:issue_id]) + post ':id/issues/:issue_id/subscription' do + issue = user_project.issues.find(params[:issue_id]) if issue.subscribed?(current_user) not_modified! @@ -257,8 +257,8 @@ module API # issue_id (required) - The ID of a project issue # Example Request: # DELETE /projects/:id/issues/:issue_id/subscription - delete ":id/issues/:issue_id/subscription" do - issue = user_project.issues.find_by(id: params[:issue_id]) + delete ':id/issues/:issue_id/subscription' do + issue = user_project.issues.find(params[:issue_id]) if issue.subscribed?(current_user) issue.unsubscribe(current_user) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 8361a1649e0..86ea223f206 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -594,20 +594,20 @@ describe API::API, api: true do describe 'DELETE :id/issues/:issue_id/subscription' do it 'unsubscribes from an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) + delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) + delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(304) end it 'returns 404 if the issue is not found' do - post api("/projects/#{project.id}/issues/123/subscription", user) + delete api("/projects/#{project.id}/issues/123/subscription", user) expect(response.status).to eq(404) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c247fcf9c96..1fa7e76894f 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -529,6 +529,12 @@ describe API::API, api: true do expect(response.status).to eq(304) end + + it 'returns 404 if the merge request is not found' do + post api("/projects/#{project.id}/merge_requests/123/subscription", user) + + expect(response.status).to eq(404) + end end describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do @@ -544,6 +550,12 @@ describe API::API, api: true do expect(response.status).to eq(304) end + + it 'returns 404 if the merge request is not found' do + post api("/projects/#{project.id}/merge_requests/123/subscription", user) + + expect(response.status).to eq(404) + end end def mr_with_later_created_and_updated_at_time -- cgit v1.2.1 From 31e28ebcebc054eaeef2eddba64ff2ff7ca3104f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 15:55:54 +0200 Subject: Load related MRs/branches asynchronously Currently this works by loading the HAML partials via XHR. While this is not the nicest setup it _is_ the easiest setup using the tools we currently have. Loading this data asynchronously doesn't make loading the related MRs/branches itself faster, it merely ensures that loading the issue itself is not slowed down. Fixes gitlab-org/gitlab-ce#14949 --- CHANGELOG | 1 + app/assets/javascripts/issue.js.coffee | 23 +++++++++++++++ app/controllers/projects/issues_controller.rb | 40 ++++++++++++++++++++------- app/views/projects/issues/show.html.haml | 8 ++++-- config/routes.rb | 2 ++ features/steps/shared/issuable.rb | 7 +++-- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed59bc1b252..adbcd8f8bcf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Developers can now add custom tags to transactions (Yorick Peterse) + - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 946d83b7bdd..c7d74a12f99 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -10,6 +10,9 @@ class @Issue @initTaskList() @initIssueBtnEventListeners() + @initMergeRequests() + @initRelatedBranches() + initTaskList: -> $('.detail-page-description .js-task-list-container').taskList('enable') $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList @@ -69,3 +72,23 @@ class @Issue type: 'PATCH' url: $('form.js-issuable-update').attr('action') data: patchData + + initMergeRequests: -> + $container = $('#merge-requests') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load referenced merge requests', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) + + initRelatedBranches: -> + $container = $('#related-branches') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load related branches', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6d649e72f84..c26cfeccf1d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableActions before_action :module_enabled - before_action :issue, only: [:edit, :update, :show] + before_action :issue, + only: [:edit, :update, :show, :referenced_merge_requests, :related_branches] # Allow read any issue before_action :authorize_read_issue!, only: [:show] @@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController # Allow issues bulk update before_action :authorize_admin_issues!, only: [:bulk_update] - # Cross-reference merge requests - before_action :closed_by_merge_requests, only: [:show] - respond_to :html def index @@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue - @merge_requests = @issue.referenced_merge_requests(current_user) - @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch) respond_to do |format| format.html @@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController end end + def referenced_merge_requests + @merge_requests = @issue.referenced_merge_requests(current_user) + @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_merge_requests') + } + end + end + end + + def related_branches + merge_requests = @issue.referenced_merge_requests(current_user) + + @related_branches = @issue.related_branches - + merge_requests.map(&:source_branch) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_related_branches') + } + end + end + end + def bulk_update result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) end - def closed_by_merge_requests - @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user) - end - protected def issue diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6fa059cbe68..5fe5ddc0819 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -64,9 +64,11 @@ = @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') - .merge-requests - = render 'merge_requests' - = render 'related_branches' + #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. + + #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. .content-block.content-block-small = render 'new_branch' diff --git a/config/routes.rb b/config/routes.rb index 48601b7567b..688b83d2c95 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -701,6 +701,8 @@ Rails.application.routes.draw do resources :issues, constraints: { id: /\d+/ } do member do post :toggle_subscription + get :referenced_merge_requests + get :related_branches end collection do post :bulk_update diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index b6d70a26c21..24b3fb6eacb 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -71,13 +71,16 @@ module SharedIssuable step 'I should not see any related merge requests' do page.within '.issue-details' do - expect(page).not_to have_content('.merge-requests') + expect(page).not_to have_content('#merge-requests .merge-requests-title') end end step 'I should see the "Enterprise fix" related merge request' do - page.within '.merge-requests' do + page.within '#merge-requests .merge-requests-title' do expect(page).to have_content('1 Related Merge Request') + end + + page.within '#merge-requests ul' do expect(page).to have_content('Enterprise fix') end end -- cgit v1.2.1 From 0a37976a4dcc88c7cbb746c89de122d3d10a453d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 17:29:04 +0200 Subject: Updated InfluxDB/Grafana setup/import docs The grafana-dashboards repository now contains _all_ GitLab.com dashboards and thus requires some extra continuous queries to be set up. The repository now also provided a way to automatically import/export dashboards. [ci skip] --- .../performance/grafana_configuration.md | 48 ++++++++++++++-------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 10ef1009818..a79c8d48d3b 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -61,24 +61,32 @@ contents below and paste it in to the interactive session: ``` CREATE RETENTION POLICY gitlab_30d ON gitlab DURATION 30d REPLICATION 1 DEFAULT CREATE RETENTION POLICY seven_days ON gitlab DURATION 7d REPLICATION 1 -CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.rails_transaction_timings FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.sidekiq_transaction_timings FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m), method END -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m), method END -CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM gitlab.gitlab_30d.rails_memory_usage GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM gitlab.gitlab_30d.sidekiq_memory_usage GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM gitlab.gitlab_30d.sidekiq_file_descriptors GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM gitlab.gitlab_30d.rails_file_descriptors GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END +CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM rails_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM sidekiq_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM rails_method_calls GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM sidekiq_method_calls GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM rails_method_calls GROUP BY time(1m), method END; +CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM sidekiq_method_calls GROUP BY time(1m), method END; +CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM rails_memory_usage GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM sidekiq_memory_usage GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM sidekiq_file_descriptors GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM rails_file_descriptors GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_allowed_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_authorized_keys_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.rails_transaction_timings FROM rails_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.sidekiq_transaction_timings FROM sidekiq_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_transaction_counts FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.grape_transaction_timings FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END; ``` ## Import Dashboards @@ -106,6 +114,10 @@ navigate away. Repeat this process for each dashboard you wish to import. +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + [grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards --- -- cgit v1.2.1 From 2244aaf98f6c9b68e30febf6b80f2c0d965e541a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 13 Apr 2016 13:20:57 +0300 Subject: Redis configuration consistency --- config/initializers/session_store.rb | 2 +- config/initializers/sidekiq.rb | 6 ++---- lib/gitlab/redis.rb | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 70285255877..88cb859871c 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -14,7 +14,7 @@ if Rails.env.test? Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" else redis_config = Gitlab::Redis.redis_store_options - redis_config[:namespace] = 'session:gitlab' + redis_config[:namespace] = Gitlab::Redis::SESSION_NAMESPACE Gitlab::Application.config.session_store( :redis_store, # Using the cookie_store would enable session replay attacks. diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 9182d929809..f1eec674888 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,9 +1,7 @@ -SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab' - Sidekiq.configure_server do |config| config.redis = { url: Gitlab::Redis.url, - namespace: SIDEKIQ_REDIS_NAMESPACE + namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE } config.server_middleware do |chain| @@ -30,6 +28,6 @@ end Sidekiq.configure_client do |config| config.redis = { url: Gitlab::Redis.url, - namespace: SIDEKIQ_REDIS_NAMESPACE + namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE } end diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 319447669dc..5c352c96de5 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -1,6 +1,8 @@ module Gitlab class Redis CACHE_NAMESPACE = 'cache:gitlab' + SESSION_NAMESPACE = 'session:gitlab' + SIDEKIQ_NAMESPACE = 'resque:gitlab' attr_reader :url -- cgit v1.2.1