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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3 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.3