From 95047f1dc7109ea7e5ebead4115ec01bb8a75ba3 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 30 Dec 2016 17:30:01 +0200 Subject: Make diff discussion more reliable --- app/models/merge_request.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 61845bf4036..83f752d6826 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -861,9 +861,11 @@ class MergeRequest < ActiveRecord::Base paths: paths ) - active_diff_notes.each do |note| - service.execute(note) - Gitlab::Timeless.timeless(note, &:save) + transaction do + active_diff_notes.each do |note| + service.execute(note) + Gitlab::Timeless.timeless(note, &:save) + end end end -- cgit v1.2.1 From 52867e15acacf842e26816c9143c59fc9086c6fb Mon Sep 17 00:00:00 2001 From: Brian Neel Date: Mon, 14 Nov 2016 20:30:12 -0500 Subject: Disable automatic login feature when clicking on email confirmation links --- app/controllers/confirmations_controller.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 3da44b9b888..306afb65f10 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController if signed_in?(resource_name) after_sign_in_path_for(resource) else - sign_in(resource) - if signed_in?(resource_name) - after_sign_in_path_for(resource) - else - new_session_path(resource_name) - end + flash[:notice] += " Please sign in." + new_session_path(resource_name) end end end -- cgit v1.2.1 From 51c4b20c48f29fe34fd1306f7a115f645eb9fb71 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Jan 2017 14:43:50 +0200 Subject: Refactor Namespace code related to nested groups Signed-off-by: Dmitriy Zaporozhets --- app/helpers/groups_helper.rb | 2 +- app/models/group.rb | 2 +- app/models/namespace.rb | 22 ++++++++++++++++++++-- app/models/route.rb | 4 ++-- 4 files changed, 24 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 77dc9e7d538..926c9703628 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -14,7 +14,7 @@ module GroupsHelper def group_title(group, name = nil, url = nil) full_title = '' - group.parents.each do |parent| + group.ancestors.each do |parent| full_title += link_to(simple_sanitize(parent.name), group_path(parent)) full_title += ' / '.html_safe end diff --git a/app/models/group.rb b/app/models/group.rb index 99675ddb366..4cdfd022094 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -201,7 +201,7 @@ class Group < Namespace end def members_with_parents - GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id)) + GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id)) end def users_with_parents diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d41833de66f..d3a4ddbb3bf 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -183,8 +183,26 @@ class Namespace < ActiveRecord::Base end end - def parents - @parents ||= parent ? parent.parents + [parent] : [] + # Scopes the model on ancestors of the record + def ancestors + if parent_id + path = route.path + paths = [] + + until path.blank? + path = path.rpartition('/').first + paths << path + end + + self.class.joins(:route).where('routes.path IN (?)', paths).order_id_asc + else + self.class.none + end + end + + # Scopes the model on direct and indirect children of the record + def descendants + self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").order_id_asc end private diff --git a/app/models/route.rb b/app/models/route.rb index caf596efa79..ebd18dce737 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -8,9 +8,9 @@ class Route < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } - after_update :rename_children, if: :path_changed? + after_update :rename_descendants, if: :path_changed? - def rename_children + def rename_descendants # We update each row separately because MySQL does not have regexp_replace. # rubocop:disable Rails/FindEach Route.where('path LIKE ?', "#{path_was}/%").each do |route| -- cgit v1.2.1 From c3a940000ea20d6682313640e1a0fda9ff68dbdf Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Wed, 12 Oct 2016 19:07:36 +0200 Subject: Handles unsubscribe from notifications via email - allows unsubscription processing of email in format "reply+%{key}+unsubscribe@acme.com" (example) - if config.address includes %{key} and replies are enabled every unsubscriable message will include mailto: link in its List-Unsubscribe header --- app/mailers/notify.rb | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0bc1c19e9cd..0cd3456b4de 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -107,15 +107,11 @@ class Notify < BaseMailer def mail_thread(model, headers = {}) add_project_headers + add_unsubscription_headers_and_links + headers["X-GitLab-#{model.class.name}-ID"] = model.id headers['X-GitLab-Reply-Key'] = reply_key - if !@labels_url && @sent_notification && @sent_notification.unsubscribable? - headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>" - - @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) - end - if Gitlab::IncomingEmail.enabled? address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)) address.display_name = @project.name_with_namespace @@ -171,4 +167,16 @@ class Notify < BaseMailer headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Path'] = @project.path_with_namespace end + + def add_unsubscription_headers_and_links + return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable? + + list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)] + if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard? + list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}" + end + + headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',') + @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) + end end -- cgit v1.2.1 From 2c45a73c8e0b07aec0d688a75beb54ebfd08ac07 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 16 Jan 2017 16:56:53 -0500 Subject: Fixed label select toggle not updating correctly Closes #26119 --- app/assets/javascripts/labels_select.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index ec2fc87bece..4dbc82a73c4 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -336,7 +336,11 @@ .removeClass('is-active') } - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return; + } + + if ($dropdown.hasClass('js-filter-bulk-update')) { _this.enableBulkLabelDropdown(); _this.setDropdownData($dropdown, isMarking, this.id(label)); return; -- cgit v1.2.1 From d6b11dafd37e78c12c982c42f274928293cdfa53 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Thu, 5 Jan 2017 14:36:06 +0100 Subject: Support notes without project --- app/mailers/emails/notes.rb | 8 +++++++ app/models/ability.rb | 11 +++++++++ app/models/concerns/participable.rb | 6 ++++- app/models/note.rb | 11 ++++++--- app/services/notes/create_service.rb | 7 +++--- app/services/notes/post_process_service.rb | 6 +++-- app/services/notification_service.rb | 27 ++++++++++++++++------ .../notify/note_personal_snippet_email.html.haml | 1 + 8 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 app/views/notify/note_personal_snippet_email.html.haml (limited to 'app') diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 0d20c9092c4..46fa6fd9f6d 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -38,6 +38,14 @@ module Emails mail_answer_thread(@snippet, note_thread_options(recipient_id)) end + def note_personal_snippet_email(recipient_id, note_id) + setup_note_mail(note_id, recipient_id) + + @snippet = @note.noteable + @target_url = snippet_url(@note.noteable) + mail_answer_thread(@snippet, note_thread_options(recipient_id)) + end + private def note_target_url_options diff --git a/app/models/ability.rb b/app/models/ability.rb index fa8f8bc3a5f..5bad5c17747 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,6 +22,17 @@ class Ability end end + # Given a list of users and a snippet this method returns the users that can + # read the given snippet. + def users_that_can_read_personal_snippet(users, snippet) + case snippet.visibility_level + when Snippet::INTERNAL, Snippet::PUBLIC + users + when Snippet::PRIVATE + users.select { |user| snippet.author == user } + end + end + # Returns an Array of Issues that can be read by the given user. # # issues - The issues to reduce down to those readable by the user. diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 70740c76e43..5d8a223fc21 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -96,6 +96,10 @@ module Participable participants.merge(ext.users) - Ability.users_that_can_read_project(participants.to_a, project) + if self.is_a?(PersonalSnippet) + Ability.users_that_can_read_personal_snippet(participants.to_a, self) + else + Ability.users_that_can_read_project(participants.to_a, project) + end end end diff --git a/app/models/note.rb b/app/models/note.rb index 0c1b05dabf2..cbf1d0adda7 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -43,7 +43,8 @@ class Note < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true delegate :title, to: :noteable, allow_nil: true - validates :note, :project, presence: true + validates :note, presence: true + validates :project, presence: true, unless: :for_personal_snippet? # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -53,7 +54,7 @@ class Note < ActiveRecord::Base validates :commit_id, presence: true, if: :for_commit? validates :author, presence: true - validate unless: [:for_commit?, :importing?] do |note| + validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note| unless note.noteable.try(:project) == note.project errors.add(:invalid_project, 'Note and noteable project mismatch') end @@ -83,7 +84,7 @@ class Note < ActiveRecord::Base after_initialize :ensure_discussion_id before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :set_discussion_id - after_save :keep_around_commit + after_save :keep_around_commit, unless: :for_personal_snippet? class << self def model_name @@ -165,6 +166,10 @@ class Note < ActiveRecord::Base noteable_type == "Snippet" end + def for_personal_snippet? + noteable_type == "Snippet" && noteable.type == 'PersonalSnippet' + end + # override to return commits, which are not active record def noteable if for_commit? diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index cdd765c85eb..b4f8b33d564 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -3,9 +3,10 @@ module Notes def execute merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) - note = project.notes.new(params) - note.author = current_user - note.system = false + note = Note.new(params) + note.project = project + note.author = current_user + note.system = false if note.award_emoji? noteable = note.noteable diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index e4cd3fc7833..45d916800f6 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -10,8 +10,10 @@ module Notes # Skip system notes, like status changes and cross-references and awards unless @note.system? EventCreateService.new.leave_note(@note, @note.author) - @note.create_cross_references! - execute_note_hooks + unless @note.for_personal_snippet? + @note.create_cross_references! + execute_note_hooks + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c3b61e68eab..2a467ade542 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -178,8 +178,15 @@ class NotificationService recipients = [] mentioned_users = note.mentioned_users - mentioned_users.select! do |user| - user.can?(:read_project, note.project) + + if note.for_personal_snippet? + mentioned_users.select! do |user| + user.can?(:read_personal_snippet, note.noteable) + end + else + mentioned_users.select! do |user| + user.can?(:read_project, note.project) + end end # Add all users participating in the thread (author, assignee, comment authors) @@ -192,11 +199,13 @@ class NotificationService recipients = recipients.concat(participants) - # Merge project watchers - recipients = add_project_watchers(recipients, note.project) + unless note.for_personal_snippet? + # Merge project watchers + recipients = add_project_watchers(recipients, note.project) - # Merge project with custom notification - recipients = add_custom_notifications(recipients, note.project, :new_note) + # Merge project with custom notification + recipients = add_custom_notifications(recipients, note.project, :new_note) + end # Reject users with Mention notification level, except those mentioned in _this_ note. recipients = reject_mention_users(recipients - mentioned_users, note.project) @@ -212,7 +221,11 @@ class NotificationService recipients = recipients.uniq # build notify method like 'note_commit_email' - notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + if note.for_personal_snippet? + notify_method = "note_personal_snippet_email".to_sym + else + notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + end recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later diff --git a/app/views/notify/note_personal_snippet_email.html.haml b/app/views/notify/note_personal_snippet_email.html.haml new file mode 100644 index 00000000000..3da199095d8 --- /dev/null +++ b/app/views/notify/note_personal_snippet_email.html.haml @@ -0,0 +1 @@ +render 'note_message' -- cgit v1.2.1 From bf708e55c2e6035b64861a1cda8bfe3d3b4a2105 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Wed, 18 Jan 2017 18:37:55 -0500 Subject: make mentions working when project not specified --- app/models/concerns/cache_markdown_field.rb | 5 ++++- app/models/concerns/mentionable.rb | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 90bd6490a02..25970158e70 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -112,7 +112,10 @@ module CacheMarkdownField invalidation_method = "#{html_field}_invalidated?".to_sym define_method(cache_method) do - html = Banzai::Renderer.cacheless_render_field(self, markdown_field) + options = { + skip_project_check: is_a?(Note) && for_personal_snippet? + } + html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options) __send__("#{html_field}=", html) true end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 8ab0401d288..9ded015aad3 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -49,7 +49,11 @@ module Mentionable self.class.mentionable_attrs.each do |attr, options| text = __send__(attr) - options = options.merge(cache_key: [self, attr], author: author) + options = options.merge( + cache_key: [self, attr], + author: author, + skip_project_check: is_a?(Note) && for_personal_snippet? + ) extractor.analyze(text, options) end -- cgit v1.2.1 From eed6bfe2e5179f4d8d07c244d2b2f880da8e183f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 19 Jan 2017 22:45:50 -0600 Subject: Remove rogue scrollbars for some issue comments Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/25989 --- app/assets/stylesheets/pages/notes.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index cbe38b60d60..da0caa30c26 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -195,10 +195,10 @@ ul.notes { } .note-body { - overflow: auto; + overflow-x: auto; + overflow-y: hidden; .note-text { - overflow: auto; word-wrap: break-word; @include md-typography; // Reset ul style types since we're nested inside a ul already -- cgit v1.2.1 From 7ce39486b54a375eb13a11deb1835d6fcf218a4a Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Thu, 19 Jan 2017 15:42:18 +0100 Subject: Only show Merge Request button when user can create a MR The Create Merge Request button only should be shown when the user is allowed to create a Merge request. --- app/helpers/compare_helper.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index aa54ee07bdc..8fcb82f17d5 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -4,6 +4,7 @@ module CompareHelper to.present? && from != to && project.feature_available?(:merge_requests, current_user) && + can?(current_user, :create_merge_request, project) && project.repository.branch_names.include?(from) && project.repository.branch_names.include?(to) end -- cgit v1.2.1 From b7f4553e3e4681d5a4a53af35650bcbb1c83b390 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 20 Jan 2017 12:25:53 +0100 Subject: Backport changes introduced by https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1083 --- app/models/namespace.rb | 5 +++ app/services/ci/register_build_service.rb | 56 +++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 17 deletions(-) (limited to 'app') diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d41833de66f..778b9c127ad 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base include CacheMarkdownField include Sortable include Gitlab::ShellAdapter + include Gitlab::CurrentSettings include Routable cache_markdown_field :description, pipeline: :description @@ -174,6 +175,10 @@ class Namespace < ActiveRecord::Base end end + def shared_runners_enabled? + projects.with_shared_runners.any? + end + def full_name @full_name ||= if parent diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index 74b5ebf372b..cd548b3c8d5 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -2,34 +2,30 @@ module Ci # This class responsible for assigning # proper pending build to runner on runner API request class RegisterBuildService - def execute(current_runner) - builds = Ci::Build.pending.unstarted + include Gitlab::CurrentSettings + attr_reader :runner + + def initialize(runner) + @runner = runner + end + + def execute builds = - if current_runner.shared? - builds. - # don't run projects which have not enabled shared runners and builds - joins(:project).where(projects: { shared_runners_enabled: true }). - joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). - - # this returns builds that are ordered by number of running builds - # we prefer projects that don't use shared runners at all - joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). - where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). - order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') + if runner.shared? + builds_for_shared_runner else - # do run projects which are only assigned to this runner (FIFO) - builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC') + builds_for_specific_runner end build = builds.find do |build| - current_runner.can_pick?(build) + runner.can_pick?(build) end if build # In case when 2 runners try to assign the same build, second runner will be declined # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. - build.runner_id = current_runner.id + build.runner_id = runner.id build.run! end @@ -41,9 +37,35 @@ module Ci private + def builds_for_shared_runner + new_builds. + # don't run projects which have not enabled shared runners and builds + joins(:project).where(projects: { shared_runners_enabled: true }). + joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). + where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). + + # Implement fair scheduling + # this returns builds that are ordered by number of running builds + # we prefer projects that don't use shared runners at all + joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). + order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') + end + + def builds_for_specific_runner + new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC') + end + def running_builds_for_shared_runners Ci::Build.running.where(runner: Ci::Runner.shared). group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds') end + + def new_builds + Ci::Build.pending.unstarted + end + + def shared_runner_build_limits_feature_enabled? + ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true' + end end end -- cgit v1.2.1 From 17f0c99c39f7919f7b8ae97034aa0d1cf646017f Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 21 Jan 2017 17:23:07 -0600 Subject: remove vestigial onsubmit event trigger --- app/views/shared/issuable/_search_bar.html.haml | 4 ---- 1 file changed, 4 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 44152319736..be5f08d8a40 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -125,10 +125,6 @@ new MilestoneSelect(); new IssueStatusSelect(); new SubscriptionSelect(); - $('form.filter-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); - }); $(document).off('page:restore').on('page:restore', function (event) { if (gl.FilteredSearchManager) { -- cgit v1.2.1 From 37382d3299c7d229931295fe9848674b3121ae7d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 21 Jan 2017 01:44:36 -0600 Subject: allow issue filter submission by clicking "Keep typing and press enter" --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 3 +++ .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 6 ++++++ .../javascripts/filtered_search/filtered_search_manager.js.es6 | 8 ++++++++ app/views/shared/issuable/_search_bar.html.haml | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index f4ec3b206cc..7d297b8eee8 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -20,6 +20,9 @@ if (selected.tagName === 'LI') { if (selected.hasAttribute('data-value')) { this.dismissDropdown(); + } else if (selected.getAttribute('data-action') === 'submit') { + this.dismissDropdown(); + this.dispatchFormSubmitEvent(); } else { const token = selected.querySelector('.js-filter-hint').innerText.trim(); const tag = selected.querySelector('.js-filter-tag').innerText.trim(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 9128ea907b3..d2e7b61b345 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -84,6 +84,12 @@ })); } + dispatchFormSubmitEvent() { + // dispatchEvent() is necessary as form.submit() does not + // trigger event handlers + this.input.form.dispatchEvent(new Event('submit')); + } + hideDropdown() { this.getCurrentHook().list.hide(); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c7b72b36561..ae19bb68360 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -25,6 +25,7 @@ } bindEvents() { + this.handleFormSubmit = this.handleFormSubmit.bind(this); this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); this.checkForEnterWrapper = this.checkForEnter.bind(this); @@ -32,6 +33,7 @@ this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); this.tokenChange = this.tokenChange.bind(this); + this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit); this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); @@ -42,6 +44,7 @@ } unbindEvents() { + this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit); this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); @@ -88,6 +91,11 @@ this.dropdownManager.resetDropdowns(); } + handleFormSubmit(e) { + e.preventDefault(); + this.search(); + } + loadSearchParamsFromURL() { const params = gl.utils.getUrlParamsArray(); const usernameParams = this.getUsernameParams(); diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index be5f08d8a40..e9644ca0f12 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -17,7 +17,7 @@ = icon('times') #js-dropdown-hint.dropdown-menu.hint-dropdown %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value' => '' } + %li.filter-dropdown-item{ 'data-action' => 'submit' } %button.btn.btn-link = icon('search') %span -- cgit v1.2.1 From d07acd42b5bcf6d4ec55bcfa7f99932319c522b3 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 21 Jan 2017 13:11:28 -0600 Subject: add a space after selecting a dropdown item --- .../filtered_search/filtered_search_dropdown.js.es6 | 1 + .../filtered_search/filtered_search_dropdown_manager.js.es6 | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index d2e7b61b345..859d6515531 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -39,6 +39,7 @@ } this.dismissDropdown(); + this.dispatchInputEvent(); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 408a0dfd768..00e1c28692f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -61,11 +61,19 @@ const word = `${tokenName}:${tokenValue}`; // Get the string to replace - const selectionStart = input.selectionStart; + let newCaretPosition = input.selectionStart; const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input); input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`; - gl.FilteredSearchDropdownManager.updateInputCaretPosition(selectionStart, input); + + // If we have added a tokenValue at the end of the input, + // add a space and set selection to the end + if (right >= inputValue.length && tokenValue !== '') { + input.value += ' '; + newCaretPosition = input.value.length; + } + + gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input); } static updateInputCaretPosition(selectionStart, input) { -- cgit v1.2.1 From 3cff2cc713cd4c7166b8b946e1c12cbba1dec9a5 Mon Sep 17 00:00:00 2001 From: samrose3 Date: Sun, 22 Jan 2017 19:07:41 -0500 Subject: Prevent copying of line numbers in parallel diff view --- app/views/projects/diffs/_parallel_view.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index b087485aa17..f361204ecac 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,7 +14,7 @@ - left_line_code = diff_file.line_code(left) - left_position = diff_file.position(left) %td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } - %a{ href: "##{left_line_code}" }= raw(left.old_pos) + %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text) - else %td.old_line.diff-line-num.empty-cell @@ -27,7 +27,7 @@ - right_line_code = diff_file.line_code(right) - right_position = diff_file.position(right) %td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } - %a{ href: "##{right_line_code}" }= raw(right.new_pos) + %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text) - else %td.old_line.diff-line-num.empty-cell -- cgit v1.2.1 From 14cd8f08eca7f63196248302f71f1a351ca85957 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 20 Jan 2017 11:19:15 +0000 Subject: Fixed keyboard navigation not working in filtered search bar --- app/assets/javascripts/droplab/droplab_filter.js | 7 ++++++- .../filtered_search/filtered_search_manager.js.es6 | 19 ++++++++++++++--- app/assets/stylesheets/framework/filters.scss | 24 +++++++++++++++------- 3 files changed, 39 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 41a220831f9..9eb7893fe95 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -3,6 +3,7 @@ /* global droplab */ require('../window')(function(w){ + var whiteListedKeys = [37, 38, 39, 40]; w.droplabFilter = { keydownWrapper: function(e){ @@ -17,6 +18,10 @@ require('../window')(function(w){ return; } + if (whiteListedKeys.indexOf(e.detail.which) !== -1) { + return; + } + if (config && config.filterFunction && typeof config.filterFunction === 'function') { filterFunction = config.filterFunction; } else { @@ -57,4 +62,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -}); \ No newline at end of file +}); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c7b72b36561..e502802a399 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -61,13 +61,26 @@ } checkForEnter(e) { + if (e.keyCode === 38 || e.keyCode === 40) { + const selectionStart = this.filteredSearchInput.selectionStart; + + e.preventDefault(); + this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); + } + if (e.keyCode === 13) { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const dropdownEl = dropdown.element; + const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); + e.preventDefault(); - // Prevent droplab from opening dropdown - this.dropdownManager.destroyDroplab(); + if (!activeElements.length) { + // Prevent droplab from opening dropdown + this.dropdownManager.destroyDroplab(); - this.search(); + this.search(); + } } } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index d957ec64654..4b05ec691a8 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -79,6 +79,16 @@ overflow: auto; } +%filter-dropdown-item-btn-hover { + background-color: $dropdown-hover-color; + color: $white-light; + text-decoration: none; + + .avatar { + border-color: $white-light; + } +} + .filter-dropdown-item { .btn { border: none; @@ -103,13 +113,7 @@ &:hover, &:focus { - background-color: $dropdown-hover-color; - color: $white-light; - text-decoration: none; - - .avatar { - border-color: $white-light; - } + @extend %filter-dropdown-item-btn-hover; } } @@ -131,6 +135,12 @@ } } +.filter-dropdown-item.dropdown-active { + .btn { + @extend %filter-dropdown-item-btn-hover; + } +} + .hint-dropdown { width: 250px; } -- cgit v1.2.1 From 90058cf4eaa4f1064a25334e1b3540ab12ed2dfa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 20 Jan 2017 12:31:44 +0000 Subject: Changed character match --- app/assets/javascripts/droplab/droplab_filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 9eb7893fe95..173256a0a76 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -3,7 +3,7 @@ /* global droplab */ require('../window')(function(w){ - var whiteListedKeys = [37, 38, 39, 40]; + var charRegex = new RegExp('^.$', 'g'); w.droplabFilter = { keydownWrapper: function(e){ @@ -18,7 +18,7 @@ require('../window')(function(w){ return; } - if (whiteListedKeys.indexOf(e.detail.which) !== -1) { + if (!charRegex.test(e.detail.key)) { return; } -- cgit v1.2.1 From 3680612a5f9b7e457f429c8900214d590d2e73ff Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 20 Jan 2017 14:20:29 +0000 Subject: Fixed currentIndex being shared across dropdowns --- app/assets/javascripts/droplab/droplab.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index c79f0230951..4d10d4e004d 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill'); var utils = require('./utils'); var DropDown = function(list) { + this.currentIndex = 0; this.hidden = true; this.list = list; this.items = []; @@ -576,7 +577,7 @@ require('./window')(function(w){ var isUpArrow = false; var isDownArrow = false; var removeHighlight = function removeHighlight(list) { - var listItems = list.list.querySelectorAll('li'); + var listItems = list.list.querySelectorAll('li:not(.divider)'); for(var i = 0; i < listItems.length; i++) { listItems[i].classList.remove('dropdown-active'); } @@ -589,7 +590,10 @@ require('./window')(function(w){ if(!listItems[currentIndex-1]){ currentIndex = currentIndex-1; } - listItems[currentIndex-1].classList.add('dropdown-active'); + + if (listItems[currentIndex-1]) { + listItems[currentIndex-1].classList.add('dropdown-active'); + } } }; @@ -617,6 +621,8 @@ require('./window')(function(w){ var keydown = function keydown(e){ var typedOn = e.target; + var dropdown = e.detail.hook.list; + currentIndex = dropdown.currentIndex; isUpArrow = false; isDownArrow = false; @@ -649,6 +655,7 @@ require('./window')(function(w){ if(isDownArrow){ currentIndex++; } if(currentIndex < 0){ currentIndex = 0; } setMenuForArrows(e.detail.hook.list); + dropdown.currentIndex = currentIndex; }; w.addEventListener('mousedown.dl', mousedown); -- cgit v1.2.1 From ecf08ae1e65b2c95b71259864354b08e61335af9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 20 Jan 2017 17:57:31 +0000 Subject: Fixed some issues with droplab & keyboard navigation Added specs --- app/assets/javascripts/droplab/droplab.js | 69 +++++++++++++++------- .../javascripts/droplab/droplab_ajax_filter.js | 3 +- app/assets/javascripts/droplab/droplab_filter.js | 1 + 3 files changed, 50 insertions(+), 23 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 4d10d4e004d..2e8c2e6f1e3 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -165,15 +165,21 @@ Object.assign(DropDown.prototype, { }, show: function() { - // debugger - this.list.style.display = 'block'; - this.hidden = false; + if (this.hidden) { + // debugger + this.list.style.display = 'block'; + this.currentIndex = 0; + this.hidden = false; + } }, hide: function() { - // debugger - this.list.style.display = 'none'; - this.hidden = true; + if (!this.hidden) { + // debugger + this.list.style.display = 'none'; + this.currentIndex = 0; + this.hidden = true; + } }, destroy: function() { @@ -479,6 +485,8 @@ Object.assign(HookInput.prototype, { this.input = function input(e) { if(self.hasRemovedEvents) return; + self.list.show(); + var inputEvent = new CustomEvent('input.dl', { detail: { hook: self, @@ -488,7 +496,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(inputEvent); - self.list.show(); } this.keyup = function keyup(e) { @@ -504,6 +511,8 @@ Object.assign(HookInput.prototype, { } function keyEvent(e, keyEventName){ + self.list.show(); + var keyEvent = new CustomEvent(keyEventName, { detail: { hook: self, @@ -515,7 +524,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(keyEvent); - self.list.show(); } this.events = this.events || {}; @@ -573,26 +581,43 @@ require('./window')(function(w){ module.exports = function(){ var currentKey; var currentFocus; - var currentIndex = 0; var isUpArrow = false; var isDownArrow = false; var removeHighlight = function removeHighlight(list) { - var listItems = list.list.querySelectorAll('li:not(.divider)'); + var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); + var listItemsTmp = []; for(var i = 0; i < listItems.length; i++) { - listItems[i].classList.remove('dropdown-active'); + var listItem = listItems[i]; + listItem.classList.remove('dropdown-active'); + + if (listItem.style.display !== 'none') { + listItemsTmp.push(listItem); + } } - return listItems; + return listItemsTmp; }; var setMenuForArrows = function setMenuForArrows(list) { var listItems = removeHighlight(list); - if(currentIndex>0){ - if(!listItems[currentIndex-1]){ - currentIndex = currentIndex-1; + if(list.currentIndex>0){ + if(!listItems[list.currentIndex-1]){ + list.currentIndex = list.currentIndex-1; } - if (listItems[currentIndex-1]) { - listItems[currentIndex-1].classList.add('dropdown-active'); + if (listItems[list.currentIndex-1]) { + var el = listItems[list.currentIndex-1]; + var filterDropdownEl = el.closest('.filter-dropdown'); + el.classList.add('dropdown-active'); + + if (filterDropdownEl) { + var filterDropdownBottom = filterDropdownEl.offsetHeight; + var elOffsetTop = el.offsetTop - 30; + + if (elOffsetTop > filterDropdownBottom) { + filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom; + console.log(filterDropdownEl.scrollTop); + } + } } } }; @@ -601,13 +626,13 @@ require('./window')(function(w){ var list = e.detail.hook.list; removeHighlight(list); list.show(); - currentIndex = 0; + list.currentIndex = 0; isUpArrow = false; isDownArrow = false; }; var selectItem = function selectItem(list) { var listItems = removeHighlight(list); - var currentItem = listItems[currentIndex-1]; + var currentItem = listItems[list.currentIndex-1]; var listEvent = new CustomEvent('click.dl', { detail: { list: list, @@ -621,8 +646,8 @@ require('./window')(function(w){ var keydown = function keydown(e){ var typedOn = e.target; - var dropdown = e.detail.hook.list; - currentIndex = dropdown.currentIndex; + var list = e.detail.hook.list; + var currentIndex = list.currentIndex; isUpArrow = false; isDownArrow = false; @@ -654,8 +679,8 @@ require('./window')(function(w){ if(isUpArrow){ currentIndex--; } if(isDownArrow){ currentIndex++; } if(currentIndex < 0){ currentIndex = 0; } + list.currentIndex = currentIndex; setMenuForArrows(e.detail.hook.list); - dropdown.currentIndex = currentIndex; }; w.addEventListener('mousedown.dl', mousedown); diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index af163f76851..86a08d0d01d 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -93,6 +93,7 @@ require('../window')(function(w){ self.hook.list.setData.call(self.hook.list, data); } self.notLoading(); + self.hook.list.currentIndex = 0; }); }, @@ -142,4 +143,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -}); \ No newline at end of file +}); diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 173256a0a76..b857fc68627 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -36,6 +36,7 @@ require('../window')(function(w){ return filterFunction(o, value); }); list.render(matches); + list.currentIndex = 0; }, init: function init(hookInput) { -- cgit v1.2.1 From 8e7929b874fbd8fe70c869eacde24ee3259e500c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 20 Jan 2017 21:40:50 +0000 Subject: Checks if rendered count has changed rather than relying on key char --- app/assets/javascripts/droplab/droplab.js | 1 - app/assets/javascripts/droplab/droplab_filter.js | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 2e8c2e6f1e3..8b14191395b 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -615,7 +615,6 @@ require('./window')(function(w){ if (elOffsetTop > filterDropdownBottom) { filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom; - console.log(filterDropdownEl.scrollTop); } } } diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index b857fc68627..9b40a3f20a4 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -3,10 +3,11 @@ /* global droplab */ require('../window')(function(w){ - var charRegex = new RegExp('^.$', 'g'); w.droplabFilter = { keydownWrapper: function(e){ + var hiddenCount = 0; + var dataHiddenCount = 0; var list = e.detail.hook.list; var data = list.data; var value = e.detail.hook.trigger.value.toLowerCase(); @@ -18,10 +19,6 @@ require('../window')(function(w){ return; } - if (!charRegex.test(e.detail.key)) { - return; - } - if (config && config.filterFunction && typeof config.filterFunction === 'function') { filterFunction = config.filterFunction; } else { @@ -32,11 +29,22 @@ require('../window')(function(w){ }; } + dataHiddenCount = data.filter(function(o) { + return !o.droplab_hidden; + }).length; + matches = data.map(function(o) { return filterFunction(o, value); }); - list.render(matches); - list.currentIndex = 0; + + hiddenCount = matches.filter(function(o) { + return !o.droplab_hidden; + }).length; + + if (dataHiddenCount !== hiddenCount) { + list.render(matches); + list.currentIndex = 0; + } }, init: function init(hookInput) { -- cgit v1.2.1 From 203db3cf8fa61b9188583cae0ce0b6b4ebc1998c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 23 Jan 2017 09:38:12 +0000 Subject: Fixed replacing with a space after a colon --- app/assets/javascripts/filtered_search/dropdown_utils.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 443ac222f70..eeab10fba17 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -84,7 +84,7 @@ let inputValue = input.value; // Replace all spaces inside quote marks with underscores // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); // Get the right position for the word selected // Regex matches first space -- cgit v1.2.1 From 99d5838fb1fdbba1f494ce8baaf7b8d5d4341712 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 23 Jan 2017 13:50:40 +0000 Subject: Only render the list if it is visible --- app/assets/javascripts/droplab/droplab_ajax.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index f20610b3811..a732525e606 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -58,7 +58,10 @@ require('../window')(function(w){ dataLoadingTemplate.outerHTML = self.listTemplate; } } - hook.list[config.method].call(hook.list, d); + + if (!hook.list.hidden) { + hook.list[config.method].call(hook.list, d); + } }).catch(function(e) { throw new droplabAjaxException(e.message || e); }); @@ -76,4 +79,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -}); \ No newline at end of file +}); -- cgit v1.2.1 From 4eb39e43a855cdf0e60b1d450a99bbae2e963b36 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 13 Jan 2017 11:35:33 -0200 Subject: Backport EE changes on approvals reset for closed MRs --- app/models/project.rb | 2 -- app/services/merge_requests/base_service.rb | 16 +++++++--------- app/services/merge_requests/refresh_service.rb | 22 ++++++++-------------- 3 files changed, 15 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/models/project.rb b/app/models/project.rb index 1630975b0d3..cd35601d76b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -121,8 +121,6 @@ class Project < ActiveRecord::Base # Merge Requests for target project should be removed with it has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' - # Merge requests from source project should be kept when source project was removed - has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest' has_many :issues, dependent: :destroy has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' has_many :services, dependent: :destroy diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 70e25956dc7..5a53b973059 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -38,15 +38,13 @@ module MergeRequests private - def merge_requests_for(branch) - origin_merge_requests = @project.origin_merge_requests - .opened.where(source_branch: branch).to_a - - fork_merge_requests = @project.fork_merge_requests - .opened.where(source_branch: branch).to_a - - (origin_merge_requests + fork_merge_requests) - .uniq.select(&:source_project) + # Returns all origin and fork merge requests from `@project` satisfying passed arguments. + def merge_requests_for(source_branch, mr_states: [:opened]) + MergeRequest + .with_state(mr_states) + .where(source_branch: source_branch, source_project_id: @project.id) + .preload(:source_project) # we don't need a #includes since we're just preloading for the #select + .select(&:source_project) end def pipeline_merge_requests(pipeline) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 51d5d7563fc..b4bfb0e5e8c 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -42,7 +42,7 @@ module MergeRequests commit_ids.include?(merge_request.diff_head_sha) end - merge_requests.uniq.select(&:source_project).each do |merge_request| + filter_merge_requests(merge_requests).each do |merge_request| MergeRequests::PostMergeService. new(merge_request.target_project, @current_user). execute(merge_request) @@ -58,10 +58,13 @@ module MergeRequests def reload_merge_requests merge_requests = @project.merge_requests.opened. by_source_or_target_branch(@branch_name).to_a - merge_requests += fork_merge_requests - merge_requests = filter_merge_requests(merge_requests) - merge_requests.each do |merge_request| + # Fork merge requests + merge_requests += MergeRequest.opened + .where(source_branch: @branch_name, source_project: @project) + .where.not(target_project: @project).to_a + + filter_merge_requests(merge_requests).each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff else @@ -175,16 +178,7 @@ module MergeRequests end def merge_requests_for_source_branch - @source_merge_requests ||= begin - merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += fork_merge_requests - filter_merge_requests(merge_requests) - end - end - - def fork_merge_requests - @fork_merge_requests ||= @project.fork_merge_requests.opened. - where(source_branch: @branch_name).to_a + @source_merge_requests ||= merge_requests_for(@branch_name) end def branch_added? -- cgit v1.2.1 From c566acbd7e885c75e5c9363accb51c7624ea20b5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 12 Jan 2017 12:24:33 -0500 Subject: Improve button accessibility on pipelines page --- .../vue_pipelines_index/pipeline_actions.js.es6 | 47 +++++++++++++--------- .../javascripts/vue_pipelines_index/stage.js.es6 | 7 ++-- app/assets/stylesheets/pages/pipelines.scss | 4 ++ .../projects/ci/pipelines/_pipeline.html.haml | 8 ++-- 4 files changed, 40 insertions(+), 26 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index ad5cb30cc42..b195b0ef3ba 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -22,47 +22,51 @@
- - - - + + +
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 4e85f16ebc5..496df9aaced 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -82,12 +82,13 @@ data-placement="top" data-toggle="dropdown" type="button" + :aria-label='stage.title' > - - + +