Commit 95414b83 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 40468-empty-states

* master: (54 commits)
  Updated image for diff navigation docs
  Milestone sidebar docs
  Use app host instead of asset host when rendering image blob or diff
  ActiveRecord::StaleObjectError: Attempted to update a stale object: Ci::Build
  Use shared interceptor in note specs
  Ignore temporary table in schema
  Check for null in job tooltip title
  Clarify casing of I18N placeholder names
  fixes some markup issues in the js style guide docs
  Fix migration that removes issues.branch_name
  small change to make less conflict with EE version
  Add cop for use of remove_column
  Resolve merge conflicts with dev.gitlab.org/master after security release
  add index for doc/administration/operations/
  Remove RubySampler#sample_objects for performance as well
  Bugfix: User can't change the access level of an access requester
  Add spec for removing issues.assignee_id
  updated imports
  Keep track of storage check timings
  Remove a header level in the new 'Automatic CE->EE merge' doc
  ...
parents a414f548 2ef39a80
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.2.4 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.2.3 (2017-11-30) ## 10.2.3 (2017-11-30)
### Fixed (7 changes) ### Fixed (7 changes)
...@@ -237,6 +248,17 @@ entry. ...@@ -237,6 +248,17 @@ entry.
- Add Gitaly metrics to the performance bar. - Add Gitaly metrics to the performance bar.
## 10.1.5 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.1.4 (2017-11-14) ## 10.1.4 (2017-11-14)
### Fixed (4 changes) ### Fixed (4 changes)
...@@ -485,6 +507,17 @@ entry. ...@@ -485,6 +507,17 @@ entry.
- creation of keys moved to services. !13331 (haseebeqx) - creation of keys moved to services. !13331 (haseebeqx)
- Add username as GL_USERNAME in hooks. - Add username as GL_USERNAME in hooks.
## 10.0.7 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.0.5 (2017-11-03) ## 10.0.5 (2017-11-03)
- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258 - [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258
......
...@@ -283,7 +283,7 @@ group :metrics do ...@@ -283,7 +283,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~> 0.7.0.beta39' gem 'prometheus-client-mmap', '~> 0.7.0.beta43'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
......
...@@ -1113,7 +1113,7 @@ DEPENDENCIES ...@@ -1113,7 +1113,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta39) prometheus-client-mmap (~> 0.7.0.beta43)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
......
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
{"iconCount":180,"spriteSize":82176,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
import { refreshCurrentPage } from './lib/utils/url_utility';
window.Admin = (function() { window.Admin = (function() {
function Admin() { function Admin() {
...@@ -40,10 +41,10 @@ window.Admin = (function() { ...@@ -40,10 +41,10 @@ window.Admin = (function() {
return $('.change-owner-link').show(); return $('.change-owner-link').show();
}); });
$('li.project_member').bind('ajax:success', function() { $('li.project_member').bind('ajax:success', function() {
return gl.utils.refreshCurrentPage(); return refreshCurrentPage();
}); });
$('li.group_member').bind('ajax:success', function() { $('li.group_member').bind('ajax:success', function() {
return gl.utils.refreshCurrentPage(); return refreshCurrentPage();
}); });
showBlacklistType = function() { showBlacklistType = function() {
if ($("input[name='blacklist_type']:checked").val() === 'file') { if ($("input[name='blacklist_type']:checked").val() === 'file') {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// %button.js-toggle-button // %button.js-toggle-button
// %div.js-toggle-content // %div.js-toggle-content
// //
import { getLocationHash } from '../lib/utils/url_utility';
$(() => { $(() => {
function toggleContainer(container, toggleState) { function toggleContainer(container, toggleState) {
...@@ -32,7 +33,7 @@ $(() => { ...@@ -32,7 +33,7 @@ $(() => {
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
// closed js-toggle-container! // closed js-toggle-container!
const hash = window.gl.utils.getLocationHash(); const hash = getLocationHash();
const anchor = hash && document.getElementById(hash); const anchor = hash && document.getElementById(hash);
const container = anchor && $(anchor).closest('.js-toggle-container'); const container = anchor && $(anchor).closest('.js-toggle-container');
......
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
import Dropzone from 'dropzone'; import Dropzone from 'dropzone';
import '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf'; import csrf from '../lib/utils/csrf';
...@@ -49,7 +49,7 @@ export default class BlobFileDropzone { ...@@ -49,7 +49,7 @@ export default class BlobFileDropzone {
}); });
this.on('success', function (header, response) { this.on('success', function (header, response) {
$('#modal-upload-blob').modal('hide'); $('#modal-upload-blob').modal('hide');
window.gl.utils.visitUrl(response.filePath); visitUrl(response.filePath);
}); });
this.on('maxfilesexceeded', function (file) { this.on('maxfilesexceeded', function (file) {
dropzoneMessage.addClass(HIDDEN_CLASS); dropzoneMessage.addClass(HIDDEN_CLASS);
......
import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/; const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => { const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash(); const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) { if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`; const hashUrlString = `#${hash}`;
......
...@@ -28,7 +28,7 @@ export default class ContextualSidebar { ...@@ -28,7 +28,7 @@ export default class ContextualSidebar {
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false)); this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => { this.$sidebarToggle.on('click', () => {
const value = !this.$sidebar.hasClass('sidebar-icons-only'); const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
this.toggleCollapsedSidebar(value); this.toggleCollapsedSidebar(value);
}); });
...@@ -43,16 +43,16 @@ export default class ContextualSidebar { ...@@ -43,16 +43,16 @@ export default class ContextualSidebar {
} }
toggleSidebarNav(show) { toggleSidebarNav(show) {
this.$sidebar.toggleClass('nav-sidebar-expanded', show); this.$sidebar.toggleClass('sidebar-expanded-mobile', show);
this.$overlay.toggleClass('mobile-nav-open', show); this.$overlay.toggleClass('mobile-nav-open', show);
this.$sidebar.removeClass('sidebar-icons-only'); this.$sidebar.removeClass('sidebar-collapsed-desktop');
} }
toggleCollapsedSidebar(collapsed) { toggleCollapsedSidebar(collapsed) {
const breakpoint = bp.getBreakpointSize(); const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) { if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed); this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
ContextualSidebar.setCollapsedCookie(collapsed); ContextualSidebar.setCollapsedCookie(collapsed);
......
import './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff'; import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index'; import imageDiffHelper from './image_diff/helpers/index';
...@@ -31,7 +31,7 @@ export default class Diff { ...@@ -31,7 +31,7 @@ export default class Diff {
isBound = true; isBound = true;
} }
if (gl.utils.getLocationHash()) { if (getLocationHash()) {
this.highlightSelectedLine(); this.highlightSelectedLine();
} }
...@@ -73,7 +73,7 @@ export default class Diff { ...@@ -73,7 +73,7 @@ export default class Diff {
} }
openAnchoredDiff(cb) { openAnchoredDiff(cb) {
const locationHash = gl.utils.getLocationHash(); const locationHash = getLocationHash();
const anchoredDiff = locationHash && locationHash.split('_')[0]; const anchoredDiff = locationHash && locationHash.split('_')[0];
if (!anchoredDiff) return; if (!anchoredDiff) return;
...@@ -128,7 +128,7 @@ export default class Diff { ...@@ -128,7 +128,7 @@ export default class Diff {
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
highlightSelectedLine() { highlightSelectedLine() {
const hash = gl.utils.getLocationHash(); const hash = getLocationHash();
const $diffFiles = $('.diff-file'); const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll'); $diffFiles.find('.hll').removeClass('hll');
......
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash'; import Flash from '../flash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root'; import RecentSearchesRoot from './recent_searches_root';
...@@ -566,7 +567,7 @@ class FilteredSearchManager { ...@@ -566,7 +567,7 @@ class FilteredSearchManager {
if (this.updateObject) { if (this.updateObject) {
this.updateObject(parameterizedUrl); this.updateObject(parameterizedUrl);
} else { } else {
gl.utils.visitUrl(parameterizedUrl); visitUrl(parameterizedUrl);
} }
} }
......
...@@ -21,7 +21,7 @@ let headerHeight = 50; ...@@ -21,7 +21,7 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight; export const getHeaderHeight = () => headerHeight;
export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only'); export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
export const canShowActiveSubItems = (el) => { export const canShowActiveSubItems = (el) => {
if (el.classList.contains('active') && !isSidebarCollapsed()) { if (el.classList.contains('active') && !isSidebarCollapsed()) {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global fuzzaldrinPlus */ /* global fuzzaldrinPlus */
import _ from 'underscore'; import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility'; import { isObject } from './lib/utils/type_utility';
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
...@@ -852,7 +853,7 @@ GitLabDropdown = (function() { ...@@ -852,7 +853,7 @@ GitLabDropdown = (function() {
if ($el.length) { if ($el.length) {
var href = $el.attr('href'); var href = $el.attr('href');
if (href && href !== '#') { if (href && href !== '#') {
gl.utils.visitUrl(href); visitUrl(href);
} else { } else {
$el.trigger('click'); $el.trigger('click');
} }
......
...@@ -5,7 +5,7 @@ import eventHub from '../event_hub'; ...@@ -5,7 +5,7 @@ import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils'; import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue'; import groupsComponent from './groups.vue';
export default { export default {
...@@ -93,7 +93,7 @@ export default { ...@@ -93,7 +93,7 @@ export default {
this.isLoading = false; this.isLoading = false;
$.scrollTo(0); $.scrollTo(0);
const currentPath = gl.utils.mergeUrlParams({ page }, window.location.href); const currentPath = mergeUrlParams({ page }, window.location.href);
window.history.replaceState({ window.history.replaceState({
page: currentPath, page: currentPath,
}, document.title, currentPath); }, document.title, currentPath);
......
<script> <script>
import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue'; import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -60,7 +61,7 @@ export default { ...@@ -60,7 +61,7 @@ export default {
if (this.hasChildren) { if (this.hasChildren) {
eventHub.$emit('toggleChildren', this.group); eventHub.$emit('toggleChildren', this.group);
} else { } else {
gl.utils.visitUrl(this.group.relativePath); visitUrl(this.group.relativePath);
} }
} }
}, },
......
import { visitUrl } from '../lib/utils/url_utility';
import DropLab from '../droplab/drop_lab'; import DropLab from '../droplab/drop_lab';
import ISetter from '../droplab/plugins/input_setter'; import ISetter from '../droplab/plugins/input_setter';
...@@ -54,9 +55,9 @@ export default class NewGroupChild { ...@@ -54,9 +55,9 @@ export default class NewGroupChild {
onClickNewGroupChildButton(e) { onClickNewGroupChildButton(e) {
if (e.target.dataset.action === NEW_PROJECT) { if (e.target.dataset.action === NEW_PROJECT) {
gl.utils.visitUrl(this.newGroupPath); visitUrl(this.newGroupPath);
} else if (e.target.dataset.action === NEW_SUBGROUP) { } else if (e.target.dataset.action === NEW_SUBGROUP) {
gl.utils.visitUrl(this.subgroupPath); visitUrl(this.subgroupPath);
} }
} }
} }
...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { ...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
} }
export function addImageCommentBadge(containerEl, { coordinate, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']); const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']);
const iconEl = document.createElement('i'); buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl); containerEl.appendChild(buttonEl);
} }
......
<script> <script>
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import Service from '../services/index'; import Service from '../services/index';
...@@ -8,7 +9,6 @@ import titleComponent from './title.vue'; ...@@ -8,7 +9,6 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue'; import descriptionComponent from './description.vue';
import editedComponent from './edited.vue'; import editedComponent from './edited.vue';
import formComponent from './form.vue'; import formComponent from './form.vue';
import '../../lib/utils/url_utility';
import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor'; import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
export default { export default {
...@@ -177,7 +177,7 @@ export default { ...@@ -177,7 +177,7 @@ export default {
.then(data => this.checkForSpam(data)) .then(data => this.checkForSpam(data))
.then((data) => { .then((data) => {
if (location.pathname !== data.web_url) { if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url); visitUrl(data.web_url);
} }
return this.service.getData(); return this.service.getData();
...@@ -212,7 +212,7 @@ export default { ...@@ -212,7 +212,7 @@ export default {
// Stop the poll so we don't get 404's with the issuable not existing // Stop the poll so we don't get 404's with the issuable not existing
this.poll.stop(); this.poll.stop();
gl.utils.visitUrl(data.web_url); visitUrl(data.web_url);
}) })
.catch(() => { .catch(() => {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
......
...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"')); const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => { $('.js-issuable-edit').on('click', (e) => {
e.preventDefault(); e.preventDefault();
eventHub.$emit('open.form'); eventHub.$emit('open.form');
......
import _ from 'underscore'; import _ from 'underscore';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints'; import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils'; import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils';
...@@ -209,7 +210,7 @@ export default class Job { ...@@ -209,7 +210,7 @@ export default class Job {
} }
if (log.status !== this.buildStatus) { if (log.status !== this.buildStatus) {
gl.utils.visitUrl(this.pagePath); visitUrl(this.pagePath);
} }
}) })
.fail(() => { .fail(() => {
......
import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
...@@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa ...@@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
// automatically adjust scroll position for hash urls taking the height of the navbar into account // automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768 // https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => { export const handleLocationHash = () => {
let hash = window.gl.utils.getLocationHash(); let hash = getLocationHash();
if (!hash) return; if (!hash) return;
// This is required to handle non-unicode characters in hash // This is required to handle non-unicode characters in hash
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
var base;
var w = window;
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
// Returns an array containing the value(s) of the // Returns an array containing the value(s) of the
// of the key passed as an argument // of the key passed as an argument
w.gl.utils.getParameterValues = function(sParam) { export function getParameterValues(sParam) {
var i, sPageURL, sParameterName, sURLVariables, values; const sPageURL = decodeURIComponent(window.location.search.substring(1));
sPageURL = decodeURIComponent(window.location.search.substring(1));
sURLVariables = sPageURL.split('&'); return sPageURL.split('&').reduce((acc, urlParam) => {
sParameterName = void 0; const sParameterName = urlParam.split('=');
values = [];
i = 0;
while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) { if (sParameterName[0] === sParam) {
values.push(sParameterName[1].replace(/\+/g, ' ')); acc.push(sParameterName[1].replace(/\+/g, ' '));
} }
i += 1;
} return acc;
return values; }, []);
}; }
// @param {Object} params - url keys and value to merge // @param {Object} params - url keys and value to merge
// @param {String} url // @param {String} url
w.gl.utils.mergeUrlParams = function(params, url) { export function mergeUrlParams(params, url) {
var lastChar, newUrl, paramName, paramValue, pattern; let newUrl = Object.keys(params).reduce((acc, paramName) => {
newUrl = decodeURIComponent(url); const paramValue = params[paramName];
for (paramName in params) { const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`);
paramValue = params[paramName];
pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)"); if (paramValue === null) {
if (paramValue == null) { return acc.replace(pattern, '');
newUrl = newUrl.replace(pattern, '');
} else if (url.search(pattern) !== -1) { } else if (url.search(pattern) !== -1) {
newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2"); return acc.replace(pattern, `$1${paramValue}$2`);
} else {
newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
} }
}
return `${acc}${acc.indexOf('?') > 0 ? '&' : '?'}${paramName}=${paramValue}`;
}, decodeURIComponent(url));
// Remove a trailing ampersand // Remove a trailing ampersand
lastChar = newUrl[newUrl.length - 1]; const lastChar = newUrl[newUrl.length - 1];
if (lastChar === '&') { if (lastChar === '&') {
newUrl = newUrl.slice(0, -1); newUrl = newUrl.slice(0, -1);
} }
return newUrl; return newUrl;
}; }
// removes parameter query string from url. returns the modified url
w.gl.utils.removeParamQueryString = function(url, param) { export function removeParamQueryString(url, param) {
var urlVariables, variables; const decodedUrl = decodeURIComponent(url);
url = decodeURIComponent(url); const urlVariables = decodedUrl.split('&');
urlVariables = url.split('&');
return ((function() { return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
var j, len, results; }
results = [];
for (j = 0, len = urlVariables.length; j < len; j += 1) { export function removeParams(params) {
variables = urlVariables[j];
if (variables.indexOf(param) === -1) {
results.push(variables);
}
}
return results;
})()).join('&');
};
w.gl.utils.removeParams = (params) => {
const url = document.createElement('a'); const url = document.createElement('a');
url.href = window.location.href; url.href = window.location.href;
params.forEach((param) => { params.forEach((param) => {
url.search = w.gl.utils.removeParamQueryString(url.search, param); url.search = removeParamQueryString(url.search, param);
}); });
return url.href; return url.href;
}; }
w.gl.utils.getLocationHash = function(url) {
var hashIndex;
if (typeof url === 'undefined') {
// Note: We can't use window.location.hash here because it's
// not consistent across browsers - Firefox will pre-decode it
url = window.location.href;
}
hashIndex = url.indexOf('#');
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
};
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href); export function getLocationHash(url = window.location.href) {
const hashIndex = url.indexOf('#');
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
}
// eslint-disable-next-line import/prefer-default-export
export function visitUrl(url, external = false) { export function visitUrl(url, external = false) {
if (external) { if (external) {
// Simulate `target="blank" rel="noopener noreferrer"` // Simulate `target="blank" rel="noopener noreferrer"`
...@@ -100,12 +76,10 @@ export function visitUrl(url, external = false) { ...@@ -100,12 +76,10 @@ export function visitUrl(url, external = false) {
} }
} }
export function refreshCurrentPage() {
visitUrl(window.location.href);
}
export function redirectTo(url) { export function redirectTo(url) {
return window.location.assign(url); return window.location.assign(url);
} }
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
visitUrl,
};
...@@ -29,7 +29,7 @@ import './commit/image_file'; ...@@ -29,7 +29,7 @@ import './commit/image_file';
// lib/utils // lib/utils
import { handleLocationHash } from './lib/utils/common_utils'; import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
import './lib/utils/url_utility'; import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors // behaviors
import './behaviors/'; import './behaviors/';
...@@ -119,7 +119,7 @@ $(function () { ...@@ -119,7 +119,7 @@ $(function () {
// `hashchange` is not triggered when link target is already in window.location // `hashchange` is not triggered when link target is already in window.location
$body.on('click', 'a[href^="#"]', function() { $body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href'); var href = this.getAttribute('href');
if (href.substr(1) === gl.utils.getLocationHash()) { if (href.substr(1) === getLocationHash()) {
setTimeout(handleLocationHash, 1); setTimeout(handleLocationHash, 1);
} }
}); });
...@@ -291,7 +291,7 @@ $(function () { ...@@ -291,7 +291,7 @@ $(function () {
const action = `${this.action}${link.search === '' ? '?' : '&'}`; const action = `${this.action}${link.search === '' ? '?' : '&'}`;
event.preventDefault(); event.preventDefault();
gl.utils.visitUrl(`${action}${$(this).serialize()}`); visitUrl(`${action}${$(this).serialize()}`);
}); });
const flashContainer = document.querySelector('.flash-container'); const flashContainer = document.querySelector('.flash-container');
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
handleLocationHash, handleLocationHash,
isMetaClick, isMetaClick,
} from './lib/utils/common_utils'; } from './lib/utils/common_utils';
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab'; import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff'; import Diff from './diff';
...@@ -317,7 +318,7 @@ import Diff from './diff'; ...@@ -317,7 +318,7 @@ import Diff from './diff';
// Scroll any linked note into view // Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab // Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash(); const hash = getLocationHash();
const anchor = hash && $container.find(`.note[id="${hash}"]`); const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) { if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content'); const notesContent = anchor.closest('.notes_content');
......
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api'; import Api from './api';
import './lib/utils/url_utility'; import { mergeUrlParams } from './lib/utils/url_utility';
export default class NamespaceSelect { export default class NamespaceSelect {
constructor(opts) { constructor(opts) {
...@@ -50,7 +50,7 @@ export default class NamespaceSelect { ...@@ -50,7 +50,7 @@ export default class NamespaceSelect {
} }
}, },
url(namespace) { url(namespace) {
return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href); return mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
}, },
}); });
} }
......
...@@ -16,6 +16,7 @@ import Autosize from 'autosize'; ...@@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho'; import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash'; import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form'; import GLForm from './gl_form';
...@@ -330,7 +331,7 @@ export default class Notes { ...@@ -330,7 +331,7 @@ export default class Notes {
} }
static updateNoteTargetSelector($note) { static updateNoteTargetSelector($note) {
const hash = gl.utils.getLocationHash(); const hash = getLocationHash();
// Needs to be an explicit true/false for the jQuery `toggleClass(force)` // Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0); const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass); $note.toggleClass('target', addTargetClass);
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueCommentForm', name: 'commentForm',
data() { data() {
return { return {
note: '', note: '',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
noteEditedText, noteEditedText,
noteAwardsList, noteAwardsList,
noteAttachment, noteAttachment,
issueNoteForm, noteForm,
}, },
computed: { computed: {
noteBody() { noteBody() {
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<div <div
v-html="note.note_html" v-html="note.note_html"
class="note-text md"></div> class="note-text md"></div>
<issue-note-form <note-form
v-if="isEditing" v-if="isEditing"
ref="noteForm" ref="noteForm"
@handleFormUpdate="handleFormUpdate" @handleFormUpdate="handleFormUpdate"
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteSignedOutWidget, noteSignedOutWidget,
noteEditedText, noteEditedText,
issueNoteForm, noteForm,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
}, },
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
return placeholderNote; return placeholderNote;
} }
return issueNote; return noteableNote;
}, },
componentData(note) { componentData(note) {
return note.isPlaceholderNote ? note.notes[0] : note; return note.isPlaceholderNote ? note.notes[0] : note;
...@@ -209,7 +209,7 @@ ...@@ -209,7 +209,7 @@
type="button" type="button"
class="js-vue-discussion-reply btn btn-text-field" class="js-vue-discussion-reply btn btn-text-field"
title="Add a reply">Reply...</button> title="Add a reply">Reply...</button>
<issue-note-form <note-form
v-if="isReplying" v-if="isReplying"
save-button-title="Comment" save-button-title="Comment"
:discussion="note" :discussion="note"
......
<script> <script>
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
import Flash from '../../flash'; import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue'; import noteActions from './note_actions.vue';
import issueNoteBody from './issue_note_body.vue'; import noteBody from './note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
...@@ -25,7 +26,7 @@ ...@@ -25,7 +26,7 @@
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteActions, noteActions,
issueNoteBody, noteBody,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -85,7 +86,7 @@ ...@@ -85,7 +86,7 @@
}; };
this.isRequesting = true; this.isRequesting = true;
this.oldContent = this.note.note_html; this.oldContent = this.note.note_html;
this.note.note_html = noteText; this.note.note_html = escape(noteText);
this.updateNote(data) this.updateNote(data)
.then(() => { .then(() => {
...@@ -122,9 +123,7 @@ ...@@ -122,9 +123,7 @@
// we need to do this to prevent noteForm inconsistent content warning // we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content // this is something we intentionally do so we need to recover the content
this.note.note = noteText; this.note.note = noteText;
if (this.$refs.noteBody) { this.$refs.noteBody.$refs.noteForm.note = noteText;
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
}
}, },
}, },
created() { created() {
...@@ -173,7 +172,7 @@ ...@@ -173,7 +172,7 @@
@handleDelete="deleteHandler" @handleDelete="deleteHandler"
/> />
</div> </div>
<issue-note-body <note-body
:note="note" :note="note"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:is-editing="isEditing" :is-editing="isEditing"
......
<script> <script>
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash'; import Flash from '../../flash';
import store from '../stores/'; import store from '../stores/';
import * as constants from '../constants'; import * as constants from '../constants';
import issueNote from './issue_note.vue'; import noteableNote from './noteable_note.vue';
import issueDiscussion from './issue_discussion.vue'; import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue'; import systemNote from '../../vue_shared/components/notes/system_note.vue';
import issueCommentForm from './issue_comment_form.vue'; import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'issueNotesApp', name: 'notesApp',
props: { props: {
noteableData: { noteableData: {
type: Object, type: Object,
...@@ -35,10 +36,10 @@ ...@@ -35,10 +36,10 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
issueDiscussion, noteableDiscussion,
systemNote, systemNote,
issueCommentForm, commentForm,
loadingIcon, loadingIcon,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
...@@ -68,10 +69,10 @@ ...@@ -68,10 +69,10 @@
} }
return placeholderNote; return placeholderNote;
} else if (note.individual_note) { } else if (note.individual_note) {
return note.notes[0].system ? systemNote : issueNote; return note.notes[0].system ? systemNote : noteableNote;
} }
return issueDiscussion; return noteableDiscussion;
}, },
getComponentData(note) { getComponentData(note) {
return note.individual_note ? note.notes[0] : note; return note.individual_note ? note.notes[0] : note;
...@@ -86,7 +87,7 @@ ...@@ -86,7 +87,7 @@
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
Flash('Something went wrong while fetching issue comments. Please try again.'); Flash('Something went wrong while fetching comments. Please try again.');
}); });
}, },
initPolling() { initPolling() {
...@@ -95,7 +96,7 @@ ...@@ -95,7 +96,7 @@
this.poll(); this.poll();
}, },
checkLocationHash() { checkLocationHash() {
const hash = gl.utils.getLocationHash(); const hash = getLocationHash();
const element = document.getElementById(hash); const element = document.getElementById(hash);
if (hash && element) { if (hash && element) {
...@@ -146,6 +147,6 @@ ...@@ -146,6 +147,6 @@
/> />
</ul> </ul>
<issue-comment-form /> <comment-form />
</div> </div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import issueNotesApp from './components/issue_notes_app.vue'; import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes', el: '#js-vue-notes',
components: { components: {
issueNotesApp, notesApp,
}, },
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}; };
}, },
render(createElement) { render(createElement) {
return createElement('issue-notes-app', { return createElement('notes-app', {
props: { props: {
noteableData: this.noteableData, noteableData: this.noteableData,
notesData: this.notesData, notesData: this.notesData,
......
import { getParameterByName } from '~/lib/utils/common_utils'; import { getParameterByName } from '~/lib/utils/common_utils';
import '~/lib/utils/url_utility'; import { removeParams } from './lib/utils/url_utility';
(() => { (() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400; const ENDLESS_SCROLL_BOTTOM_PX = 400;
...@@ -7,7 +7,7 @@ import '~/lib/utils/url_utility'; ...@@ -7,7 +7,7 @@ import '~/lib/utils/url_utility';
const Pager = { const Pager = {
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) { init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']); this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit; this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit; this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable; this.disable = disable;
......
import 'vendor/peek'; import 'vendor/peek';
import 'vendor/peek.performance_bar'; import 'vendor/peek.performance_bar';
import { getParameterValues } from './lib/utils/url_utility';
export default class PerformanceBar { export default class PerformanceBar {
constructor(opts) { constructor(opts) {
...@@ -39,7 +40,7 @@ export default class PerformanceBar { ...@@ -39,7 +40,7 @@ export default class PerformanceBar {
} }
handleLineProfileLink(e) { handleLineProfileLink(e) {
const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler'); const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`); const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
const shouldToggleModal = lineProfilerParameter.length > 0 && const shouldToggleModal = lineProfilerParameter.length > 0 &&
lineProfilerParameterRegex.test(e.currentTarget.href); lineProfilerParameterRegex.test(e.currentTarget.href);
......
...@@ -59,8 +59,26 @@ ...@@ -59,8 +59,26 @@
}, },
computed: { computed: {
status() {
return this.job && this.job.status ? this.job.status : {};
},
tooltipText() { tooltipText() {
return `${this.job.name} - ${this.job.status.label}`; const textBuilder = [];
if (this.job.name) {
textBuilder.push(this.job.name);
}
if (this.job.name && this.status.label) {
textBuilder.push('-');
}
if (this.status.label) {
textBuilder.push(`${this.job.status.label}`);
}
return textBuilder.join(' ');
}, },
/** /**
...@@ -78,8 +96,8 @@ ...@@ -78,8 +96,8 @@
<div class="ci-job-component"> <div class="ci-job-component">
<a <a
v-tooltip v-tooltip
v-if="job.status.has_details" v-if="status.has_details"
:href="job.status.details_path" :href="status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -95,6 +113,7 @@ ...@@ -95,6 +113,7 @@
<div <div
v-else v-else
v-tooltip v-tooltip
class="js-job-component-tooltip"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -108,18 +127,18 @@ ...@@ -108,18 +127,18 @@
<action-component <action-component
v-if="hasAction && !isDropdown" v-if="hasAction && !isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
<dropdown-action-component <dropdown-action-component
v-if="hasAction && isDropdown" v-if="hasAction && isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
</div> </div>
</template> </template>
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { visitUrl } from './lib/utils/url_utility';
import projectSelect from './project_select'; import projectSelect from './project_select';
export default class Project { export default class Project {
...@@ -122,7 +123,7 @@ export default class Project { ...@@ -122,7 +123,7 @@ export default class Project {
var action = $form.attr('action'); var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&'; var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) { if (shouldVisit) {
gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`); visitUrl(`${action}${divider}${$form.serialize()}`);
} }
} }
}, },
......
import '../lib/utils/url_utility'; import { getParameterValues } from '../lib/utils/url_utility';
const bindEvents = () => { const bindEvents = () => {
const path = gl.utils.getParameterValues('path')[0]; const path = getParameterValues('path')[0];
// get the path url and append it in the inputS // get the path url and append it in the inputS
$('.js-path-name').val(path); $('.js-path-name').val(path);
......
import Vue from 'vue'; import Vue from 'vue';
import { visitUrl } from '../../lib/utils/url_utility';
import flash from '../../flash'; import flash from '../../flash';
import service from '../services'; import service from '../services';
import * as types from './mutation_types'; import * as types from './mutation_types';
export const redirectToUrl = (_, url) => gl.utils.visitUrl(url); export const redirectToUrl = (_, url) => visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
......
import { visitUrl } from '../../../lib/utils/url_utility';
import { normalizeHeaders } from '../../../lib/utils/common_utils'; import { normalizeHeaders } from '../../../lib/utils/common_utils';
import flash from '../../../flash'; import flash from '../../../flash';
import service from '../../services'; import service from '../../services';
...@@ -73,7 +74,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => { ...@@ -73,7 +74,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => {
} else if (row.type === 'submodule') { } else if (row.type === 'submodule') {
commit(types.TOGGLE_LOADING, row); commit(types.TOGGLE_LOADING, row);
gl.utils.visitUrl(row.url); visitUrl(row.url);
} else if (row.type === 'blob' && row.opened) { } else if (row.type === 'blob' && row.opened) {
dispatch('setFileActive', row); dispatch('setFileActive', row);
} else { } else {
......
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
const defaultStopCallback = Mousetrap.stopCallback; const defaultStopCallback = Mousetrap.stopCallback;
...@@ -38,7 +39,7 @@ export default class Shortcuts { ...@@ -38,7 +39,7 @@ export default class Shortcuts {
if (typeof findFileURL !== 'undefined' && findFileURL !== null) { if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
Mousetrap.bind('t', () => { Mousetrap.bind('t', () => {
gl.utils.visitUrl(findFileURL); visitUrl(findFileURL);
}); });
} }
...@@ -62,7 +63,7 @@ export default class Shortcuts { ...@@ -62,7 +63,7 @@ export default class Shortcuts {
} else { } else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' }); Cookies.set(performanceBarCookieName, 'true', { path: '/' });
} }
gl.utils.refreshCurrentPage(); refreshCurrentPage();
} }
static toggleMarkdownPreview(e) { static toggleMarkdownPreview(e) {
......
/* global Mousetrap */ /* global Mousetrap */
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
const defaults = { const defaults = {
...@@ -18,9 +18,9 @@ export default class ShortcutsBlob extends Shortcuts { ...@@ -18,9 +18,9 @@ export default class ShortcutsBlob extends Shortcuts {
moveToFilePermalink() { moveToFilePermalink() {
if (this.options.fileBlobPermalinkUrl) { if (this.options.fileBlobPermalinkUrl) {
const hash = gl.utils.getLocationHash(); const hash = getLocationHash();
const hashUrlString = hash ? `#${hash}` : ''; const hashUrlString = hash ? `#${hash}` : '';
gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`); visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
} }
} }
} }
...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation { ...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
super(); super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form'); this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
this.editBtn = document.querySelector('.issuable-edit'); this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone')); Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
......
import Flash from '../../../flash'; import Flash from '../../../flash';
import AssigneeTitle from './assignee_title'; import AssigneeTitle from './assignee_title';
import Assignees from './assignees'; import Assignees from './assignees';
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'SidebarAssignees', name: 'SidebarAssignees',
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
loading: false, loading: false,
field: '',
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
field: {
type: String,
required: true,
},
signedIn: {
type: Boolean,
required: false,
default: false,
},
},
components: { components: {
'assignee-title': AssigneeTitle, 'assignee-title': AssigneeTitle,
assignees: Assignees, assignees: Assignees,
...@@ -61,10 +71,6 @@ export default { ...@@ -61,10 +71,6 @@ export default {
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
}, },
beforeMount() {
this.field = this.$el.dataset.field;
this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
},
template: ` template: `
<div> <div>
<assignee-title <assignee-title
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import participants from './participants.vue'; import participants from './participants.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
participants, participants,
}, },
...@@ -21,6 +25,7 @@ export default { ...@@ -21,6 +25,7 @@ export default {
<participants <participants
:loading="store.isFetching.participants" :loading="store.isFetching.participants"
:participants="store.participants" :participants="store.participants"
:number-of-less-participants="7" /> :number-of-less-participants="7"
/>
</div> </div>
</template> </template>
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue'; ...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
subscriptions, subscriptions,
}, },
......
...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate'; ...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
function mountAssigneesComponent(mediator) {
const el = document.getElementById('js-vue-sidebar-assignees');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarAssignees,
},
render: createElement => createElement('sidebar-assignees', {
props: {
mediator,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
},
}),
});
}
function mountConfidentialComponent(mediator) { function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) { ...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) {
}).$mount(el); }).$mount(el);
} }
function mountParticipantsComponent() { function mountParticipantsComponent(mediator) {
const el = document.querySelector('.js-sidebar-participants-entry-point'); const el = document.querySelector('.js-sidebar-participants-entry-point');
// eslint-disable-next-line no-new
if (!el) return; if (!el) return;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -60,11 +82,15 @@ function mountParticipantsComponent() { ...@@ -60,11 +82,15 @@ function mountParticipantsComponent() {
components: { components: {
sidebarParticipants, sidebarParticipants,
}, },
render: createElement => createElement('sidebar-participants', {}), render: createElement => createElement('sidebar-participants', {
props: {
mediator,
},
}),
}); });
} }
function mountSubscriptionsComponent() { function mountSubscriptionsComponent(mediator) {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point'); const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return; if (!el) return;
...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() { ...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() {
components: { components: {
sidebarSubscriptions, sidebarSubscriptions,
}, },
render: createElement => createElement('sidebar-subscriptions', {}), render: createElement => createElement('sidebar-subscriptions', {
props: {
mediator,
},
}),
}); });
} }
function mount(mediator) { function mountTimeTrackingComponent() {
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); const el = document.getElementById('issuable-time-tracker');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
}
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarTimeTracking,
},
render: createElement => createElement('sidebar-time-tracking', {}),
});
}
export function mountSidebar(mediator) {
mountAssigneesComponent(mediator);
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountLockComponent(mediator); mountLockComponent(mediator);
mountParticipantsComponent(); mountParticipantsComponent(mediator);
mountSubscriptionsComponent(); mountSubscriptionsComponent(mediator);
new SidebarMoveIssue( new SidebarMoveIssue(
mediator, mediator,
...@@ -98,7 +137,9 @@ function mount(mediator) { ...@@ -98,7 +137,9 @@ function mount(mediator) {
$('.js-move-issue-confirmation-button'), $('.js-move-issue-confirmation-button'),
).init(); ).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker'); mountTimeTrackingComponent();
} }
export default mount; export function getSidebarOptions() {
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
}
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar'; import { mountSidebar, getSidebarOptions } from './mount_sidebar';
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const mediator = new Mediator(getSidebarOptions());
const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
mountSidebar(mediator); mountSidebar(mediator);
......
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash'; import Flash from '../flash';
import Service from './services/sidebar_service'; import Service from './services/sidebar_service';
import Store from './stores/sidebar_store'; import Store from './stores/sidebar_store';
...@@ -7,7 +8,6 @@ export default class SidebarMediator { ...@@ -7,7 +8,6 @@ export default class SidebarMediator {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.initSingleton(options); this.initSingleton(options);
} }
return SidebarMediator.singleton; return SidebarMediator.singleton;
} }
...@@ -81,7 +81,7 @@ export default class SidebarMediator { ...@@ -81,7 +81,7 @@ export default class SidebarMediator {
.then(response => response.json()) .then(response => response.json())
.then((data) => { .then((data) => {
if (location.pathname !== data.web_url) { if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url); visitUrl(data.web_url);
} }
}); });
} }
......
export default class SidebarStore { export default class SidebarStore {
constructor(store) { constructor(options) {
if (!SidebarStore.singleton) { if (!SidebarStore.singleton) {
const { currentUser, rootPath, editable } = store; this.initSingleton(options);
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
} }
return SidebarStore.singleton; return SidebarStore.singleton;
} }
initSingleton(options) {
const { currentUser, rootPath, editable } = options;
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
}
setAssigneeData(data) { setAssigneeData(data) {
this.isFetching.assignees = false; this.isFetching.assignees = false;
if (data.assignees) { if (data.assignees) {
......
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */ /* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
import { visitUrl } from './lib/utils/url_utility';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import { isMetaClick } from './lib/utils/common_utils'; import { isMetaClick } from './lib/utils/common_utils';
...@@ -150,7 +150,7 @@ export default class Todos { ...@@ -150,7 +150,7 @@ export default class Todos {
window.open(todoLink, windowTarget); window.open(todoLink, windowTarget);
} else { } else {
gl.utils.visitUrl(todoLink); visitUrl(todoLink);
} }
} }
} }
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
import { visitUrl } from './lib/utils/url_utility';
export default class TreeView { export default class TreeView {
constructor() { constructor() {
...@@ -14,7 +15,7 @@ export default class TreeView { ...@@ -14,7 +15,7 @@ export default class TreeView {
e.preventDefault(); e.preventDefault();
return window.open(path, '_blank'); return window.open(path, '_blank');
} else { } else {
return gl.utils.visitUrl(path); return visitUrl(path);
} }
} }
}); });
...@@ -56,7 +57,7 @@ export default class TreeView { ...@@ -56,7 +57,7 @@ export default class TreeView {
} else if (e.which === 13) { } else if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href'); path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) { if (path) {
return gl.utils.visitUrl(path); return visitUrl(path);
} }
} }
}); });
......
import '~/lib/utils/datetime_utility'; import '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash'; import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage'; import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon'; import StatusIcon from './mr_widget_status_icon';
...@@ -36,7 +37,7 @@ export default { ...@@ -36,7 +37,7 @@ export default {
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
if (res.redirect_url) { if (res.redirect_url) {
gl.utils.visitUrl(res.redirect_url); visitUrl(res.redirect_url);
} }
}) })
.catch(() => { .catch(() => {
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
padding: 1px 5px; padding: 1px 5px;
font-size: 12px; font-size: 12px;
color: $blue-500; color: $blue-500;
width: 23px; width: 24px;
height: 23px; height: 24px;
border: 1px solid $blue-500; border: 1px solid $blue-500;
&:hover, &:hover,
......
.page-with-contextual-sidebar { .page-with-contextual-sidebar {
transition: padding-left $sidebar-transition-duration;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-left: $contextual-sidebar-collapsed-width; padding-left: $contextual-sidebar-collapsed-width;
} }
...@@ -27,8 +29,10 @@ ...@@ -27,8 +29,10 @@
.context-header { .context-header {
position: relative; position: relative;
margin-right: 2px; margin-right: 2px;
width: $contextual-sidebar-width;
a { a {
transition: padding $sidebar-transition-duration;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -63,10 +67,10 @@ ...@@ -63,10 +67,10 @@
} }
.nav-sidebar { .nav-sidebar {
transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
position: fixed; position: fixed;
z-index: 400; z-index: 400;
width: $contextual-sidebar-width; width: $contextual-sidebar-width;
transition: left $sidebar-transition-duration;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
...@@ -74,16 +78,15 @@ ...@@ -74,16 +78,15 @@
box-shadow: inset -2px 0 0 $border-color; box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
&:not(.sidebar-icons-only) { &:not(.sidebar-collapsed-desktop) {
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
box-shadow: inset -2px 0 0 $border-color, box-shadow: inset -2px 0 0 $border-color,
2px 1px 3px $dropdown-shadow-color; 2px 1px 3px $dropdown-shadow-color;
} }
} }
&.sidebar-icons-only { &.sidebar-collapsed-desktop {
width: auto; width: $contextual-sidebar-collapsed-width;
min-width: $contextual-sidebar-collapsed-width;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
overflow-x: hidden; overflow-x: hidden;
...@@ -108,12 +111,11 @@ ...@@ -108,12 +111,11 @@
} }
} }
&.nav-sidebar-expanded { &.sidebar-expanded-mobile {
left: 0; left: 0;
} }
a { a {
transition: none;
text-decoration: none; text-decoration: none;
} }
...@@ -126,9 +128,10 @@ ...@@ -126,9 +128,10 @@
white-space: nowrap; white-space: nowrap;
a { a {
transition: padding $sidebar-transition-duration;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 15px;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
...@@ -288,7 +291,8 @@ ...@@ -288,7 +291,8 @@
> a { > a {
margin-left: 4px; margin-left: 4px;
padding-left: 12px; // Subtract width of left border on active element
padding-left: 11px;
} }
.badge { .badge {
...@@ -313,6 +317,7 @@ ...@@ -313,6 +317,7 @@
.toggle-sidebar-button, .toggle-sidebar-button,
.close-nav-button { .close-nav-button {
width: $contextual-sidebar-width - 2px; width: $contextual-sidebar-width - 2px;
transition: width $sidebar-transition-duration;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
padding: 16px; padding: 16px;
...@@ -343,20 +348,21 @@ ...@@ -343,20 +348,21 @@
} }
} }
.collapse-text {
white-space: nowrap;
overflow: hidden;
}
.sidebar-icons-only { .sidebar-collapsed-desktop {
.context-header { .context-header {
height: 61px; height: 60px;
width: $contextual-sidebar-collapsed-width;
a { a {
padding: 10px 4px; padding: 10px 4px;
} }
} }
li a {
padding: 12px 15px;
}
.sidebar-top-level-items > li { .sidebar-top-level-items > li {
&.active a { &.active a {
padding-left: 12px; padding-left: 12px;
...@@ -374,8 +380,8 @@ ...@@ -374,8 +380,8 @@
} }
.toggle-sidebar-button { .toggle-sidebar-button {
width: $contextual-sidebar-collapsed-width - 2px;
padding: 16px; padding: 16px;
width: $contextual-sidebar-collapsed-width - 2px;
.collapse-text, .collapse-text,
.icon-angle-double-left { .icon-angle-double-left {
......
...@@ -50,6 +50,11 @@ ...@@ -50,6 +50,11 @@
&:not(.disabled) { &:not(.disabled) {
cursor: pointer; cursor: pointer;
} }
svg {
width: $gl-padding;
height: $gl-padding;
}
} }
} }
......
...@@ -5,10 +5,9 @@ $grid-size: 8px; ...@@ -5,10 +5,9 @@ $grid-size: 8px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 250px; $gutter_inner_width: 250px;
$sidebar-transition-duration: .15s; $sidebar-transition-duration: .3s;
$sidebar-breakpoint: 1024px; $sidebar-breakpoint: 1024px;
$default-transition-duration: .15s; $default-transition-duration: .15s;
$right-sidebar-transition-duration: .3s;
$contextual-sidebar-width: 220px; $contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px; $contextual-sidebar-collapsed-width: 50px;
...@@ -722,7 +721,7 @@ $issuable-warning-icon-margin: 4px; ...@@ -722,7 +721,7 @@ $issuable-warning-icon-margin: 4px;
Image Commenting cursor Image Commenting cursor
*/ */
$image-comment-cursor-left-offset: 12; $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 30; $image-comment-cursor-top-offset: 12;
/* /*
Popup Popup
......
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
position: relative; position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
transition: width $right-sidebar-transition-duration; transition: width $sidebar-transition-duration;
width: 100%; width: 100%;
&.is-compact { &.is-compact {
...@@ -453,8 +453,8 @@ ...@@ -453,8 +453,8 @@
.right-sidebar.right-sidebar-expanded { .right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active, &.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active { &.boards-sidebar-slide-leave-active {
transition: width $right-sidebar-transition-duration, transition: width $sidebar-transition-duration,
padding $right-sidebar-transition-duration; padding $sidebar-transition-duration;
} }
&.boards-sidebar-slide-enter, &.boards-sidebar-slide-enter,
......
...@@ -732,18 +732,18 @@ ...@@ -732,18 +732,18 @@
.frame.click-to-comment { .frame.click-to-comment {
position: relative; position: relative;
cursor: image-url('icon_image_comment.svg') cursor: image-url('illustrations/image_comment_light_cursor.svg')
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor // Retina cursor
cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x) cursor: -webkit-image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x, image-url('illustrations/image_comment_light_cursor@2x.svg') 2x)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
.comment-indicator { .comment-indicator {
position: absolute; position: absolute;
padding: 0; padding: 0;
width: (2px * $image-comment-cursor-left-offset); width: (2px * $image-comment-cursor-left-offset);
height: (1px * $image-comment-cursor-top-offset); height: (2px * $image-comment-cursor-top-offset);
// center the indicator to match the top left click region // center the indicator to match the top left click region
margin-top: (-1px * $image-comment-cursor-top-offset) + 2; margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
margin-left: (-1px * $image-comment-cursor-left-offset) + 1; margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
...@@ -778,15 +778,20 @@ ...@@ -778,15 +778,20 @@
.frame .badge, .frame .badge,
.frame .image-comment-badge { .frame .image-comment-badge {
// Center align badges on the frame // Center align badges on the frame
transform: translate3d(-50%, -50%, 0); transform: translate(-50%, -50%);
} }
.image-comment-badge { .image-comment-badge {
@include btn-comment-icon;
position: absolute; position: absolute;
width: 24px;
height: 24px;
padding: 0;
background: none;
border: 0;
&.inverted { > svg {
border-color: $white-light; width: 100%;
height: 100%;
} }
} }
......
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
right: 0; right: 0;
transition: width $right-sidebar-transition-duration; transition: width $sidebar-transition-duration;
background: $gray-light; background: $gray-light;
z-index: 200; z-index: 200;
overflow: hidden; overflow: hidden;
...@@ -470,7 +470,8 @@ ...@@ -470,7 +470,8 @@
} }
} }
.milestone-title span { .milestone-title span,
.collapse-truncated-title {
@include str-truncated(100%); @include str-truncated(100%);
display: block; display: block;
margin: 0 4px; margin: 0 4px;
......
...@@ -21,11 +21,11 @@ module IssuableActions ...@@ -21,11 +21,11 @@ module IssuableActions
respond_to do |format| respond_to do |format|
format.html do format.html do
recaptcha_check_with_fallback { render :edit } recaptcha_check_if_spammable { render :edit }
end end
format.json do format.json do
recaptcha_check_with_fallback(false) { render_entity_json } recaptcha_check_if_spammable(false) { render_entity_json }
end end
end end
...@@ -80,6 +80,12 @@ module IssuableActions ...@@ -80,6 +80,12 @@ module IssuableActions
private private
def recaptcha_check_if_spammable(should_redirect = true, &block)
return yield unless @issuable.is_a? Spammable
recaptcha_check_with_fallback(should_redirect, &block)
end
def render_conflict_response def render_conflict_response
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -22,7 +22,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -22,7 +22,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def update def update
@group_member = @group.group_members.find(params[:id]) @group_member = @group.members_and_requesters.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @group_member) return render_403 unless can?(current_user, :update_group_member, @group_member)
......
...@@ -26,7 +26,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -26,7 +26,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def update def update
@project_member = @project.project_members.find(params[:id]) @project_member = @project.members_and_requesters.find(params[:id])
return render_403 unless can?(current_user, :update_project_member, @project_member) return render_403 unless can?(current_user, :update_project_member, @project_member)
......
...@@ -272,7 +272,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -272,7 +272,7 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo? render 'projects/empty' if @project.empty_repo?
else else
if @project.wiki_enabled? if can?(current_user, :read_wiki, @project)
@project_wiki = @project.wiki @project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id]) @wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user) elsif @project.feature_available?(:issues, current_user)
......
...@@ -118,20 +118,24 @@ module BlobHelper ...@@ -118,20 +118,24 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_raw_path def blob_raw_url(only_path: false)
if @build && @entry if @build && @entry
raw_project_job_artifacts_path(@project, @build, path: @entry.path) raw_project_job_artifacts_url(@project, @build, path: @entry.path, only_path: only_path)
elsif @snippet elsif @snippet
if @snippet.project_id if @snippet.project_id
raw_project_snippet_path(@project, @snippet) raw_project_snippet_url(@project, @snippet, only_path: only_path)
else else
raw_snippet_path(@snippet) raw_snippet_url(@snippet, only_path: only_path)
end end
elsif @blob elsif @blob
project_raw_path(@project, @id) project_raw_url(@project, @id, only_path: only_path)
end end
end end
def blob_raw_path
blob_raw_url(only_path: true)
end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete # elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements. # and may omit some elements.
......
...@@ -104,15 +104,23 @@ module DiffHelper ...@@ -104,15 +104,23 @@ module DiffHelper
].join(' ').html_safe ].join(' ').html_safe
end end
def diff_file_blob_raw_path(diff_file) def diff_file_blob_raw_url(diff_file, only_path: false)
project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) project_raw_url(@project, tree_join(diff_file.content_sha, diff_file.file_path), only_path: only_path)
end end
def diff_file_old_blob_raw_path(diff_file) def diff_file_old_blob_raw_url(diff_file, only_path: false)
sha = diff_file.old_content_sha sha = diff_file.old_content_sha
return unless sha return unless sha
project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) project_raw_url(@project, tree_join(diff_file.old_content_sha, diff_file.old_path), only_path: only_path)
end
def diff_file_blob_raw_path(diff_file)
diff_file_blob_raw_url(diff_file, only_path: true)
end
def diff_file_old_blob_raw_path(diff_file)
diff_file_old_blob_raw_url(diff_file, only_path: true)
end end
def diff_file_html_data(project, diff_file_path, diff_commit_id) def diff_file_html_data(project, diff_file_path, diff_commit_id)
......
...@@ -58,7 +58,7 @@ module PreferencesHelper ...@@ -58,7 +58,7 @@ module PreferencesHelper
user_view user_view
elsif user_view == "activity" elsif user_view == "activity"
"activity" "activity"
elsif @project.wiki_enabled? elsif can?(current_user, :read_wiki, @project)
"wiki" "wiki"
elsif @project.feature_available?(:issues, current_user) elsif @project.feature_available?(:issues, current_user)
"projects/issues/issues" "projects/issues/issues"
......
...@@ -10,6 +10,9 @@ class Issue < ActiveRecord::Base ...@@ -10,6 +10,9 @@ class Issue < ActiveRecord::Base
include RelativePositioning include RelativePositioning
include TimeTrackable include TimeTrackable
include ThrottledTouch include ThrottledTouch
include IgnorableColumn
ignore_column :assignee_id, :branch_name
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -971,8 +971,7 @@ class Repository ...@@ -971,8 +971,7 @@ class Repository
tmp_remote_name = true tmp_remote_name = true
end end
add_remote(remote_name, url) add_remote(remote_name, url, mirror_refmap: refmap)
set_remote_as_mirror(remote_name, refmap: refmap)
fetch_remote(remote_name, forced: forced) fetch_remote(remote_name, forced: forced)
ensure ensure
remove_remote(remote_name) if tmp_remote_name remove_remote(remote_name) if tmp_remote_name
......
...@@ -315,6 +315,8 @@ class User < ActiveRecord::Base ...@@ -315,6 +315,8 @@ class User < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
query = query.downcase
order = <<~SQL order = <<~SQL
CASE CASE
WHEN users.name = %{query} THEN 0 WHEN users.name = %{query} THEN 0
...@@ -324,8 +326,11 @@ class User < ActiveRecord::Base ...@@ -324,8 +326,11 @@ class User < ActiveRecord::Base
END END
SQL SQL
fuzzy_search(query, [:name, :email, :username]) where(
.reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) fuzzy_arel_match(:name, query)
.or(fuzzy_arel_match(:username, query))
.or(arel_table[:email].eq(query))
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end end
# searches user by given pattern # searches user by given pattern
...@@ -333,15 +338,17 @@ class User < ActiveRecord::Base ...@@ -333,15 +338,17 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query) def search_with_secondary_emails(query)
query = query.downcase
email_table = Email.arel_table email_table = Email.arel_table
matched_by_emails_user_ids = email_table matched_by_emails_user_ids = email_table
.project(email_table[:user_id]) .project(email_table[:user_id])
.where(Email.fuzzy_arel_match(:email, query)) .where(email_table[:email].eq(query))
where( where(
fuzzy_arel_match(:name, query) fuzzy_arel_match(:name, query)
.or(fuzzy_arel_match(:email, query))
.or(fuzzy_arel_match(:username, query)) .or(fuzzy_arel_match(:username, query))
.or(arel_table[:email].eq(query))
.or(arel_table[:id].in(matched_by_emails_user_ids)) .or(arel_table[:id].in(matched_by_emails_user_ids))
) )
end end
...@@ -1054,13 +1061,13 @@ class User < ActiveRecord::Base ...@@ -1054,13 +1061,13 @@ class User < ActiveRecord::Base
end end
def todos_done_count(force: false) def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :done).execute.count TodosFinder.new(self, state: :done).execute.count
end end
end end
def todos_pending_count(force: false) def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :pending).execute.count TodosFinder.new(self, state: :pending).execute.count
end end
end end
......
...@@ -38,11 +38,15 @@ module Ci ...@@ -38,11 +38,15 @@ module Ci
begin begin
# In case when 2 runners try to assign the same build, second runner will be declined # 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. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
build.runner_id = runner.id begin
build.run! build.runner_id = runner.id
register_success(build) build.run!
register_success(build)
return Result.new(build, true)
return Result.new(build, true)
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
end
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
# We are looping to find another build that is not conflicting # We are looping to find another build that is not conflicting
# It also indicates that this build can be picked and passed to runner. # It also indicates that this build can be picked and passed to runner.
...@@ -54,9 +58,6 @@ module Ci ...@@ -54,9 +58,6 @@ module Ci
# we still have to return 409 in the end, # we still have to return 409 in the end,
# to make sure that this is properly handled by runner. # to make sure that this is properly handled by runner.
valid = false valid = false
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
valid = false
end end
end end
......
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
.context-header .context-header
= link_to admin_root_path, title: 'Admin Overview' do = link_to admin_root_path, title: 'Admin Overview' do
......
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
.context-header .context-header
= link_to group_path(@group), title: @group.name do = link_to group_path(@group), title: @group.name do
......
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
.context-header .context-header
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
......
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
- can_edit = can?(current_user, :admin_project, @project) - can_edit = can?(current_user, :admin_project, @project)
.context-header .context-header
......
.file-content.image_file .file-content.image_file
= image_tag(blob_raw_path, alt: viewer.blob.name) -# Uses the full URL rather than the path, to prevent it from getting prefixed with the asset host.
= image_tag(blob_raw_url, alt: viewer.blob.name)
- blob = diff_file.blob - blob = diff_file.blob
- old_blob = diff_file.old_blob - old_blob = diff_file.old_blob
- blob_raw_path = diff_file_blob_raw_path(diff_file) - blob_raw_url = diff_file_blob_raw_url(diff_file)
- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) - old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true) - click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '') - diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = '' - class_name = ''
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.two-up.view .two-up.view
.wrap .wrap
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size) %span.meta-filesize= number_to_human_size(old_blob.size)
| |
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%strong H: %strong H:
%span.meta-height %span.meta-height
.wrap .wrap
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size) %span.meta-filesize= number_to_human_size(blob.size)
| |
...@@ -36,9 +36,9 @@ ...@@ -36,9 +36,9 @@
.swipe.view.hide .swipe.view.hide
.swipe-frame .swipe-frame
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
.swipe-wrap .swipe-wrap
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
%span.swipe-bar %span.swipe-bar
%span.top-handle %span.top-handle
%span.bottom-handle %span.bottom-handle
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
.onion-skin.view.hide .onion-skin.view.hide
.onion-skin-frame .onion-skin-frame
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
.controls .controls
.transparent .transparent
.drag-track .drag-track
......
- blob = diff_file.blob - blob = diff_file.blob
- old_blob = diff_file.old_blob - old_blob = diff_file.old_blob
- blob_raw_path = diff_file_blob_raw_path(diff_file) - blob_raw_url = diff_file_blob_raw_url(diff_file)
- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) - old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true) - click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '') - diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = '' - class_name = ''
...@@ -12,5 +12,5 @@ ...@@ -12,5 +12,5 @@
.image.js-single-image{ data: diff_view_data } .image.js-single-image{ data: diff_view_data }
.wrap .wrap
- single_class_name = diff_file.deleted_file? ? 'deleted' : 'added' - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added'
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.file_path }
%p.image-info= number_to_human_size(blob.size) %p.image-info= number_to_human_size(blob.size)
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul %ul
- if can_update_issue - if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit' %li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'js-issuable-edit'
- unless current_user == @issue.author - unless current_user == @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue - if can_update_issue
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue - if can_update_issue
= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped js-issuable-edit'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul %ul
- if can_update_merge_request - if can_update_merge_request
%li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'js-issuable-edit'
- unless current_user == @merge_request.author - unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request - if can_update_merge_request
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request - if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped js-issuable-edit"
= render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
- if note.is_a?(DiffNote) && note.on_image? - if note.is_a?(DiffNote) && note.on_image?
- if show_image_comment_badge && note_counter == 0 - if show_image_comment_badge && note_counter == 0
-# Only show this for the first comment in the discussion -# Only show this for the first comment in the discussion
%span.image-comment-badge.inverted %span.image-comment-badge
= icon('comment-o') = sprite_icon('image-comment-dark')
- elsif note_counter == 0 - elsif note_counter == 0
- counter = badge_counter if local_assigns[:badge_counter] - counter = badge_counter if local_assigns[:badge_counter]
- badge_class = "hidden" if @fresh_discussion || counter.nil? - badge_class = "hidden" if @fresh_discussion || counter.nil?
......
---
title: Fix error that was preventing users to change the access level of access requests for Groups or Projects
merge_request: 15832
author:
type: fixed
---
title: Animate contextual sidebar on collapse/expand
merge_request:
author:
type: changed
---
title: Update comment on image cursor and icons
merge_request: 15760
author:
type: fixed
---
title: Use app host instead of asset host when rendering image blob or diff
merge_request:
author:
type: fixed
---
title: Fix error during schema dump.
merge_request: 15866
author:
type: fixed
---
title: Remove allocation tracking code from InfluxDB sampler for performance
merge_request:
author:
type: performance
# Ignore table used temporarily in background migration
ActiveRecord::SchemaDumper.ignore_tables = ["untracked_files_for_uploads"]
# rubocop:disable Migration/RemoveColumn
class RemoveDeprecatedIssuesTrackerColumnsFromProjects < ActiveRecord::Migration class RemoveDeprecatedIssuesTrackerColumnsFromProjects < ActiveRecord::Migration
def change def change
remove_column :projects, :issues_tracker, :string, default: 'gitlab', null: false remove_column :projects, :issues_tracker, :string, default: 'gitlab', null: false
......
# rubocop:disable Migration/RemoveColumn
class RemoveNotificationLevelFromUsers < ActiveRecord::Migration class RemoveNotificationLevelFromUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/RemoveColumn
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
# rubocop:disable Migration/RemoveColumn
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
# rubocop:disable Migration/RemoveColumn
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
# rubocop:disable Migration/RemoveColumn
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
# rubocop:disable Migration/RemoveColumn
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment