From e8221b47ace3c80b8b812c6289c9035ff8e8f5ff Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 11 Apr 2018 12:29:16 +0100 Subject: Fixed IDE diff markers being cached too long --- app/assets/javascripts/ide/lib/common/model.js | 16 +++++++++----- .../javascripts/ide/lib/decorations/controller.js | 9 ++++++++ app/assets/javascripts/ide/lib/diff/controller.js | 25 +++++++++++++++------- 3 files changed, 37 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js index e47adae99ed..b9d5c0be70b 100644 --- a/app/assets/javascripts/ide/lib/common/model.js +++ b/app/assets/javascripts/ide/lib/common/model.js @@ -31,7 +31,7 @@ export default class Model { ); } - this.events = new Map(); + this.events = new Set(); this.updateContent = this.updateContent.bind(this); this.dispose = this.dispose.bind(this); @@ -73,10 +73,11 @@ export default class Model { } onChange(cb) { - this.events.set( - this.path, - this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))), - ); + this.events.add(this.disposable.add(this.model.onDidChangeContent(e => cb(this, e)))); + } + + onDispose(cb) { + this.events.add(cb); } updateContent(content) { @@ -86,6 +87,11 @@ export default class Model { dispose() { this.disposable.dispose(); + + this.events.forEach(cb => { + if (typeof cb === 'function') cb(); + }); + this.events.clear(); eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose); diff --git a/app/assets/javascripts/ide/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js index 42904774747..13d477bb2cf 100644 --- a/app/assets/javascripts/ide/lib/decorations/controller.js +++ b/app/assets/javascripts/ide/lib/decorations/controller.js @@ -38,6 +38,15 @@ export default class DecorationsController { ); } + hasDecorations(model) { + return this.decorations.has(model.url); + } + + removeDecorations(model) { + this.decorations.delete(model.url); + this.editorDecorations.delete(model.url); + } + dispose() { this.decorations.clear(); this.editorDecorations.clear(); diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js index b136545ad11..f579424cf33 100644 --- a/app/assets/javascripts/ide/lib/diff/controller.js +++ b/app/assets/javascripts/ide/lib/diff/controller.js @@ -3,7 +3,7 @@ import { throttle } from 'underscore'; import DirtyDiffWorker from './diff_worker'; import Disposable from '../common/disposable'; -export const getDiffChangeType = (change) => { +export const getDiffChangeType = change => { if (change.modified) { return 'modified'; } else if (change.added) { @@ -16,12 +16,7 @@ export const getDiffChangeType = (change) => { }; export const getDecorator = change => ({ - range: new monaco.Range( - change.lineNumber, - 1, - change.endLineNumber, - 1, - ), + range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1), options: { isWholeLine: true, linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, @@ -31,6 +26,7 @@ export const getDecorator = change => ({ export default class DirtyDiffController { constructor(modelManager, decorationsController) { this.disposable = new Disposable(); + this.models = new Map(); this.editorSimpleWorker = null; this.modelManager = modelManager; this.decorationsController = decorationsController; @@ -42,7 +38,15 @@ export default class DirtyDiffController { } attachModel(model) { + if (this.models.has(model.url)) return; + model.onChange(() => this.throttledComputeDiff(model)); + model.onDispose(() => { + this.decorationsController.removeDecorations(model); + this.models.delete(model.url); + }); + + this.models.set(model.url, model); } computeDiff(model) { @@ -54,7 +58,11 @@ export default class DirtyDiffController { } reDecorate(model) { - this.decorationsController.decorate(model); + if (this.decorationsController.hasDecorations(model)) { + this.decorationsController.decorate(model); + } else { + this.computeDiff(model); + } } decorate({ data }) { @@ -65,6 +73,7 @@ export default class DirtyDiffController { dispose() { this.disposable.dispose(); + this.models.clear(); this.dirtyDiffWorker.removeEventListener('message', this.decorate); this.dirtyDiffWorker.terminate(); -- cgit v1.2.1 From 90716733522691e964680539e231d3677bafbc51 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Thu, 19 Apr 2018 15:42:50 +1100 Subject: [Rails5] Fix `User#manageable_groups` In `arel 7.0` (`7.1.4` version is used for rails5) there were introduced some changes that break our code in the `User#manageable_groups` method. The problem is that `arel_table[:id].in(Arel::Nodes::SqlLiteral)` generates wrong `IN ()` construction. The selection for `IN` is missing: => "\"namespaces\".\"id\" IN (0)" That caused such spec errors for the `rails5` branch: ``` 4) User groups with child groups #manageable_groups does not include duplicates if a membership was added for the subgroup Failure/Error: expect(user.manageable_groups).to contain_exactly(group, subgroup) expected collection contained: [#, #] actual collection contained: [] the missing elements were: [#, #] # ./spec/models/user_spec.rb:699:in `block (5 levels) in ' # ./spec/spec_helper.rb:188:in `block (2 levels) in ' # /var/lib/gems/2.3.0/gems/rspec-retry-0.4.6/lib/rspec/retry.rb:112:in `block in run' # /var/lib/gems/2.3.0/gems/rspec-retry-0.4.6/lib/rspec/retry.rb:101:in `loop' # /var/lib/gems/2.3.0/gems/rspec-retry-0.4.6/lib/rspec/retry.rb:101:in `run' # /var/lib/gems/2.3.0/gems/rspec-retry-0.4.6/lib/rspec_ext/rspec_ext.rb:12:in `run_with_retry' # /var/lib/gems/2.3.0/gems/rspec-retry-0.4.6/lib/rspec/retry.rb:30:in `block (2 levels) in setup' ``` This commit changes `User#manageable_groups` in the way to drop the usage of `Arel::Nodes::SqlLiteral` and adds usage of raw SQL query. This change should be updated when we're migrated to Rails 5.2 because arel was fixed in `9.0.0` (which is used in Rails 5.2). --- app/models/user.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/models/user.rb b/app/models/user.rb index d5c5c0964c5..cddc0b8d2e9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -947,10 +947,13 @@ class User < ActiveRecord::Base end def manageable_groups - union = Gitlab::SQL::Union.new([owned_groups.select(:id), - masters_groups.select(:id)]) - arel_union = Arel::Nodes::SqlLiteral.new(union.to_sql) - owned_and_master_groups = Group.where(Group.arel_table[:id].in(arel_union)) + union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), masters_groups.select(:id)]).to_sql + + # Update this line to not use raw SQL when migrated to Rails 5.2. + # Either ActiveRecord or Arel constructions are fine. + # This was replaced with the raw SQL construction because of bugs in the arel gem. + # Bugs were fixed in arel 9.0.0 (Rails 5.2). + owned_and_master_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection Gitlab::GroupHierarchy.new(owned_and_master_groups).base_and_descendants end -- cgit v1.2.1 From 775211bc7076bba14d6e268fb324391124a2751f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 18 Apr 2018 22:02:04 -0700 Subject: Fix N+1 queries when loading participants for a commit note We saw about 10,000 SQL queries for some commits in the NewNoteWorker, which stalled the Sidekiq queue for other new notes. The notification service took up to 8 minutes to process the commits. Avoiding this N+1 query brings the time down significantly. Closes #45526 --- app/models/commit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/commit.rb b/app/models/commit.rb index de860df4b9c..9750e9298ec 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -248,7 +248,7 @@ class Commit end def notes_with_associations - notes.includes(:author) + notes.includes(:author, :award_emoji) end def merge_requests -- cgit v1.2.1 From 276d4eb80cade01f1d035d849a90009d02ae926d Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 18 Apr 2018 19:11:50 +0100 Subject: Fix specifying a non-default ref when requesting an archive using the legacy URL --- app/controllers/projects/repositories_controller.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 937b0e39cbd..d01f324e6fd 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -28,11 +28,12 @@ class Projects::RepositoriesController < Projects::ApplicationController end def assign_archive_vars - @id = params[:id] - - return unless @id - - @ref, @filename = extract_ref(@id) + if params[:id] + @ref, @filename = extract_ref(params[:id]) + else + @ref = params[:ref] + @filename = nil + end rescue InvalidPathError render_404 end -- cgit v1.2.1 From 5a29a304be5fc86a5bbe61764c47cdd8069e2103 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 5 Apr 2018 17:17:02 +0200 Subject: Shows new branch/mr button even when branch exists --- .../javascripts/create_merge_request_dropdown.js | 33 ++++++++++++++-------- app/controllers/projects/issues_controller.rb | 6 ++-- app/models/issue.rb | 16 +++++++---- 3 files changed, 35 insertions(+), 20 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index fb1fc9cd32e..a88b6971f90 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -84,20 +84,21 @@ export default class CreateMergeRequestDropdown { if (data.can_create_branch) { this.available(); this.enable(); + this.updateBranchName(data.suggested_branch_name); if (!this.droplabInitialized) { this.droplabInitialized = true; this.initDroplab(); this.bindEvents(); } - } else if (data.has_related_branch) { + } else { this.hide(); } }) .catch(() => { this.unavailable(); this.disable(); - Flash('Failed to check if a new branch can be created.'); + Flash(__('Failed to check related branches.')); }); } @@ -409,13 +410,16 @@ export default class CreateMergeRequestDropdown { this.unavailableButton.classList.remove('hide'); } + updateBranchName(suggestedBranchName) { + this.branchInput.value = suggestedBranchName; + this.updateCreatePaths('branch', suggestedBranchName); + } + updateInputState(target, ref, result) { // target - 'branch' or 'ref' - which the input field we are searching a ref for. // ref - string - what a user typed. // result - string - what has been found on backend. - const pathReplacement = `$1${ref}`; - // If a found branch equals exact the same text a user typed, // that means a new branch cannot be created as it already exists. if (ref === result) { @@ -426,18 +430,12 @@ export default class CreateMergeRequestDropdown { this.refIsValid = true; this.refInput.dataset.value = ref; this.showAvailableMessage('ref'); - this.createBranchPath = this.createBranchPath.replace(this.regexps.ref.createBranchPath, - pathReplacement); - this.createMrPath = this.createMrPath.replace(this.regexps.ref.createMrPath, - pathReplacement); + this.updateCreatePaths(target, ref); } } else if (target === 'branch') { this.branchIsValid = true; this.showAvailableMessage('branch'); - this.createBranchPath = this.createBranchPath.replace(this.regexps.branch.createBranchPath, - pathReplacement); - this.createMrPath = this.createMrPath.replace(this.regexps.branch.createMrPath, - pathReplacement); + this.updateCreatePaths(target, ref); } else { this.refIsValid = false; this.refInput.dataset.value = ref; @@ -457,4 +455,15 @@ export default class CreateMergeRequestDropdown { this.disableCreateAction(); } } + + // target - 'branch' or 'ref' + // ref - string - the new value to use as branch or ref + updateCreatePaths(target, ref) { + const pathReplacement = `$1${ref}`; + + this.createBranchPath = this.createBranchPath.replace(this.regexps[target].createBranchPath, + pathReplacement); + this.createMrPath = this.createMrPath.replace(this.regexps[target].createMrPath, + pathReplacement); + } } diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 767e492f566..d69015c8665 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -134,11 +134,11 @@ class Projects::IssuesController < Projects::ApplicationController def can_create_branch can_create = current_user && can?(current_user, :push_code, @project) && - @issue.can_be_worked_on?(current_user) + @issue.can_be_worked_on? respond_to do |format| format.json do - render json: { can_create_branch: can_create, has_related_branch: @issue.has_related_branch? } + render json: { can_create_branch: can_create, suggested_branch_name: @issue.suggested_branch_name } end end end @@ -177,7 +177,7 @@ class Projects::IssuesController < Projects::ApplicationController end def authorize_create_merge_request! - render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) + render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on? end def render_issue_json diff --git a/app/models/issue.rb b/app/models/issue.rb index 7611e83647c..c34c35bcd34 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -194,6 +194,15 @@ class Issue < ActiveRecord::Base branches_with_iid - branches_with_merge_request end + def suggested_branch_name + return to_branch_name unless project.repository.branch_exists?(to_branch_name) + + index = 2 + index += 1 while project.repository.branch_exists?("#{to_branch_name}-#{index}") + + "#{to_branch_name}-#{index}" + end + # Returns boolean if a related branch exists for the current issue # ignores merge requests branchs def has_related_branch? @@ -248,11 +257,8 @@ class Issue < ActiveRecord::Base end end - def can_be_worked_on?(current_user) - !self.closed? && - !self.project.forked? && - self.related_branches(current_user).empty? && - self.closed_by_merge_requests(current_user).empty? + def can_be_worked_on? + !self.closed? && !self.project.forked? end # Returns `true` if the current issue can be viewed by either a logged in User -- cgit v1.2.1 From 6ae3098eb8f01406190942e8952866dd9af81dde Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 19 Apr 2018 08:59:37 +0200 Subject: Uses Uniquify to calculate Issue#suggested_branch_name --- app/models/concerns/uniquify.rb | 7 +++++-- app/models/issue.rb | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb index a7fe5951b6e..db51ed2dbeb 100644 --- a/app/models/concerns/uniquify.rb +++ b/app/models/concerns/uniquify.rb @@ -3,11 +3,14 @@ class Uniquify # by appending a counter to it. Uniqueness is determined by # repeated calls to the passed block. # + # You can pass an initial value for the counter, if not given + # counting starts from 1. + # # If `base` is a function/proc, we expect that calling it with a # candidate counter returns a string to test/return. - def string(base) + def string(base, counter = nil) @base = base - @counter = nil + @counter = counter increment_counter! while yield(base_string) base_string diff --git a/app/models/issue.rb b/app/models/issue.rb index c34c35bcd34..51028a404c2 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -197,10 +197,10 @@ class Issue < ActiveRecord::Base def suggested_branch_name return to_branch_name unless project.repository.branch_exists?(to_branch_name) - index = 2 - index += 1 while project.repository.branch_exists?("#{to_branch_name}-#{index}") - - "#{to_branch_name}-#{index}" + start_counting_from = 2 + Uniquify.new.string(-> (counter) { "#{to_branch_name}-#{counter}" }, start_counting_from) do |suggested_branch_name| + project.repository.branch_exists?(suggested_branch_name) + end end # Returns boolean if a related branch exists for the current issue -- cgit v1.2.1 From 9000626a60c889c76ff1dfc8c6247f15953ca993 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 19 Apr 2018 11:31:01 +0200 Subject: Moves Uniquify counter in the initializer --- app/models/concerns/uniquify.rb | 27 ++++++++++++++++----------- app/models/issue.rb | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb index db51ed2dbeb..549a76da20e 100644 --- a/app/models/concerns/uniquify.rb +++ b/app/models/concerns/uniquify.rb @@ -1,16 +1,21 @@ +# Uniquify +# +# Return a version of the given 'base' string that is unique +# by appending a counter to it. Uniqueness is determined by +# repeated calls to the passed block. +# +# You can pass an initial value for the counter, if not given +# counting starts from 1. +# +# If `base` is a function/proc, we expect that calling it with a +# candidate counter returns a string to test/return. class Uniquify - # Return a version of the given 'base' string that is unique - # by appending a counter to it. Uniqueness is determined by - # repeated calls to the passed block. - # - # You can pass an initial value for the counter, if not given - # counting starts from 1. - # - # If `base` is a function/proc, we expect that calling it with a - # candidate counter returns a string to test/return. - def string(base, counter = nil) - @base = base + def initialize(counter = nil) @counter = counter + end + + def string(base) + @base = base increment_counter! while yield(base_string) base_string diff --git a/app/models/issue.rb b/app/models/issue.rb index 51028a404c2..0332bfa9371 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -198,7 +198,7 @@ class Issue < ActiveRecord::Base return to_branch_name unless project.repository.branch_exists?(to_branch_name) start_counting_from = 2 - Uniquify.new.string(-> (counter) { "#{to_branch_name}-#{counter}" }, start_counting_from) do |suggested_branch_name| + Uniquify.new(start_counting_from).string(-> (counter) { "#{to_branch_name}-#{counter}" }) do |suggested_branch_name| project.repository.branch_exists?(suggested_branch_name) end end -- cgit v1.2.1 From 49b85262c5a1944d7fdb50e43900a4adb13aba6c Mon Sep 17 00:00:00 2001 From: Dennis Tang Date: Thu, 19 Apr 2018 14:43:20 +0000 Subject: Resolve "Improve tooltips of collapsed sidebars" --- app/assets/javascripts/due_date_select.js | 9 ++- app/assets/javascripts/labels_select.js | 5 +- app/assets/javascripts/milestone_select.js | 14 +++- app/assets/javascripts/right_sidebar.js | 12 ++- .../sidebar/components/assignees/assignees.vue | 20 ++++- .../components/assignees/sidebar_assignees.vue | 12 ++- .../confidential/confidential_issue_sidebar.vue | 19 ++++- .../sidebar/components/lock/lock_issue_sidebar.vue | 21 ++++- .../components/participants/participants.vue | 18 ++++- .../components/time_tracking/collapsed_state.vue | 32 +++++++- app/assets/javascripts/sidebar/mount_sidebar.js | 1 + app/assets/javascripts/users_select.js | 7 +- .../components/sidebar/toggle_sidebar.vue | 39 +++++++--- app/assets/stylesheets/framework/variables.scss | 3 + app/assets/stylesheets/pages/issuable.scss | 28 ++++++- app/assets/stylesheets/pages/milestone.scss | 14 +++- app/helpers/issuables_helper.rb | 38 ++++++++- app/helpers/milestones_helper.rb | 91 ++++++++++++++++++---- app/models/concerns/milestoneish.rb | 4 +- app/serializers/entity_date_helper.rb | 27 +++++++ app/views/projects/issues/_issue.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 11 ++- .../shared/issuable/_sidebar_assignees.html.haml | 2 +- app/views/shared/issuable/_sidebar_todo.html.haml | 6 +- .../form/_merge_request_assignee.html.haml | 2 +- app/views/shared/milestones/_sidebar.html.haml | 35 +++++---- 27 files changed, 374 insertions(+), 100 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 842a4255f08..4164149dd06 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,7 +2,9 @@ import $ from 'jquery'; import Pikaday from 'pikaday'; +import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; +import { timeFor } from './lib/utils/datetime_utility'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; class DueDateSelect { @@ -14,6 +16,7 @@ class DueDateSelect { this.$dropdownParent = $dropdownParent; this.$datePicker = $dropdownParent.find('.js-due-date-calendar'); this.$block = $block; + this.$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); this.$selectbox = $dropdown.closest('.selectbox'); this.$value = $block.find('.value'); this.$valueContent = $block.find('.value-content'); @@ -128,7 +131,8 @@ class DueDateSelect { submitSelectedDate(isDropdown) { const selectedDateValue = this.datePayload[this.abilityName].due_date; - const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; + const hasDueDate = this.displayedDate !== 'No due date'; + const displayedDateStyle = hasDueDate ? 'bold' : 'no-value'; this.$loading.removeClass('hidden').fadeIn(); @@ -145,10 +149,13 @@ class DueDateSelect { return axios.put(this.issueUpdateURL, this.datePayload) .then(() => { + const tooltipText = hasDueDate ? `${__('Due date')}
${selectedDateValue} (${timeFor(selectedDateValue)})` : __('Due date'); if (isDropdown) { this.$dropdown.trigger('loaded.gl.dropdown'); this.$dropdown.dropdown('toggle'); } + this.$sidebarCollapsedValue.attr('data-original-title', tooltipText); + return this.$loading.fadeOut(); }); } diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index d0050abb8e9..9b62cfb8206 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -83,7 +83,7 @@ export default class LabelsSelect { $dropdown.trigger('loading.gl.dropdown'); axios.put(issueUpdateURL, data) .then(({ data }) => { - var labelCount, template, labelTooltipTitle, labelTitles; + var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels; $loading.fadeOut(); $dropdown.trigger('loaded.gl.dropdown'); $selectbox.hide(); @@ -115,8 +115,7 @@ export default class LabelsSelect { labelTooltipTitle = labelTitles.join(', '); } else { - labelTooltipTitle = ''; - $sidebarLabelTooltip.tooltip('destroy'); + labelTooltipTitle = __('Labels'); } $sidebarLabelTooltip diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index d0a2b27b0e6..7e9a50a885d 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -4,6 +4,7 @@ import $ from 'jquery'; import _ from 'underscore'; +import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; import { timeFor } from './lib/utils/datetime_utility'; import ModalStore from './boards/stores/modal_store'; @@ -25,7 +26,7 @@ export default class MilestoneSelect { } $els.each((i, dropdown) => { - let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; + let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; const $dropdown = $(dropdown); const projectId = $dropdown.data('projectId'); const milestonesUrl = $dropdown.data('milestones'); @@ -52,7 +53,6 @@ export default class MilestoneSelect { if (issueUpdateURL) { milestoneLinkTemplate = _.template('<%- title %>'); milestoneLinkNoneTemplate = 'None'; - collapsedSidebarLabelTemplate = _.template(' <%- title %> '); } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, @@ -214,10 +214,16 @@ export default class MilestoneSelect { data.milestone.remaining = timeFor(data.milestone.due_date); data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); - return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); + return $sidebarCollapsedValue + .attr('data-original-title', `${data.milestone.name}
${data.milestone.remaining}`) + .find('span') + .text(data.milestone.title); } else { $value.html(milestoneLinkNoneTemplate); - return $sidebarCollapsedValue.find('span').text('No'); + return $sidebarCollapsedValue + .attr('data-original-title', __('Milestone')) + .find('span') + .text(__('None')); } }) .catch(() => { diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 2088a49590a..6eb0b62fa1c 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import Cookies from 'js-cookie'; import flash from './flash'; import axios from './lib/utils/axios_utils'; +import { __ } from './locale'; function Sidebar(currentUser) { this.toggleTodo = this.toggleTodo.bind(this); @@ -41,12 +42,14 @@ Sidebar.prototype.addEventListeners = function() { }; Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { - var $allGutterToggleIcons, $this, $thisIcon; + var $allGutterToggleIcons, $this, isExpanded, tooltipLabel; e.preventDefault(); $this = $(this); - $thisIcon = $this.find('i'); + isExpanded = $this.find('i').hasClass('fa-angle-double-right'); + tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar'); $allGutterToggleIcons = $('.js-sidebar-toggle i'); - if ($thisIcon.hasClass('fa-angle-double-right')) { + + if (isExpanded) { $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left'); $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); $('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); @@ -57,6 +60,9 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { if (gl.lazyLoader) gl.lazyLoader.loadCheck(); } + + $this.attr('data-original-title', tooltipLabel); + if (!triggered) { Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); } diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index 1e7f46454bf..2d00e8ac7e0 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -1,6 +1,12 @@