Commit d072c18d authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into ce-to-ee-2017-12-16

parents 458f54dc 0a395a08
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global MilestoneSelect */
/* global Sidebar */
import Vue from 'vue';
import weight from 'ee/sidebar/components/weight/weight.vue';
import Flash from '../../flash';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import assignees from '../../sidebar/components/assignees/assignees';
......
......@@ -25,7 +25,6 @@ class ListIssue {
this.isLoading = {
weight: false,
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
......
/* global CommentsStore */
/* global notes */
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
import Notes from '../../notes';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const DiffNoteAvatars = Vue.extend({
......@@ -129,7 +129,7 @@ const DiffNoteAvatars = Vue.extend({
},
methods: {
clickedAvatar(e) {
notes.onAddDiffNote(e);
Notes.instance.onAddDiffNote(e);
// Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState();
......
......@@ -11,7 +11,7 @@ import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
/* global LineHighlighter */
import LineHighlighter from './line_highlighter';
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
......@@ -21,7 +21,7 @@ import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
/* global MergeRequest */
import MergeRequest from './merge_request';
import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete';
/* global PathLocks */
......@@ -30,7 +30,7 @@ import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
/* global Sidebar */
import Sidebar from './right_sidebar';
/* global WeightSelect */
/* global AdminEmailSelect */
......
......@@ -3,7 +3,7 @@
/* global WeightSelect */
import LabelsSelect from './labels_select';
import IssuableContext from './issuable_context';
/* global Sidebar */
import Sidebar from './right_sidebar';
import DueDateSelectors from './due_date_select';
......@@ -17,5 +17,5 @@ export default () => {
new WeightSelect();
new IssuableContext(sidebarOptions.currentUser);
new DueDateSelectors();
window.sidebar = new Sidebar();
Sidebar.initialize();
};
/* global Notes */
import Notes from './notes';
export default () => {
const dataEl = document.querySelector('.js-notes-data');
......@@ -10,5 +10,7 @@ export default () => {
autocomplete,
} = JSON.parse(dataEl.innerHTML);
window.notes = new Notes(notesUrl, notesIds, now, diffView, autocomplete);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
Notes.initialize(notesUrl, notesIds, now, diffView, autocomplete);
};
......@@ -175,4 +175,4 @@ LineHighlighter.prototype.__setLocationHash__ = function(value) {
}, document.title, value);
};
window.LineHighlighter = LineHighlighter;
export default LineHighlighter;
......@@ -45,14 +45,10 @@ import './layout_nav';
import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
import './merge_request';
import './merge_request_tabs';
import './milestone_select';
import './notes';
import './preview_markdown';
import './projects_dropdown';
import './render_gfm';
import './right_sidebar';
import initBreadcrumbs from './breadcrumb';
// EE-only scripts
......
......@@ -7,142 +7,138 @@ import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility';
(function() {
this.MergeRequest = (function() {
function MergeRequest(opts) {
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) {
return function() {
return _this.showAllCommits();
};
})(this));
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
}
}
// Local jQuery finder
MergeRequest.prototype.$ = function(selector) {
return this.$el.find(selector);
function MergeRequest(opts) {
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) {
return function() {
return _this.showAllCommits();
};
MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) {
window.mrTabs.unbindEvents();
})(this));
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
window.mrTabs = new MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide');
};
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
var $this, shouldSubmit;
$this = $(this);
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit && $this.data('submitted')) {
return;
}
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
_this.submitNoteForm($this.closest('form'), $this);
}
}
});
};
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
return $button.trigger('click');
}
};
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
});
}
}
// Local jQuery finder
MergeRequest.prototype.$ = function(selector) {
return this.$el.find(selector);
};
MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) {
window.mrTabs.unbindEvents();
}
window.mrTabs = new MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide');
};
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
var $this, shouldSubmit;
$this = $(this);
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit && $this.data('submitted')) {
return;
}
textarea.val(textarea.data('messageWithDescription'));
$('.js-with-description-hint').hide();
$('.js-without-description-hint').show();
});
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
$(document).on('click', 'a.js-without-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
textarea.val(textarea.data('messageWithoutDescription'));
$('.js-with-description-hint').show();
$('.js-without-description-hint').hide();
});
};
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box')
.removeClass(classToRemove)
.addClass(classToAdd)
.find('span')
.text(newStatusText);
};
MergeRequest.prototype.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
_this.submitNoteForm($this.closest('form'), $this);
}
// Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden');
};
return MergeRequest;
})();
}).call(window);
}
});
};
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
return $button.trigger('click');
}
};
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
$('.js-with-description-hint').hide();
$('.js-without-description-hint').show();
});
$(document).on('click', 'a.js-without-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
$('.js-with-description-hint').show();
$('.js-without-description-hint').hide();
});
};
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box')
.removeClass(classToRemove)
.addClass(classToAdd)
.find('span')
.text(newStatusText);
};
MergeRequest.prototype.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
}
// Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden');
};
export default MergeRequest;
/* eslint-disable no-new, class-methods-use-this */
/* global notes */
import Cookies from 'js-cookie';
import Flash from './flash';
......@@ -16,6 +15,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
import Notes from './notes';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -324,7 +324,7 @@ export default class MergeRequestTabs {
if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({
Notes.instance.toggleDiffNote({
target: anchor,
lineType,
forceShow: true,
......
......@@ -37,6 +37,12 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes {
static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
if (!this.instance) {
this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
}
}
constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
......
<script>
/* global LineHighlighter */
import { mapGetters } from 'vuex';
import LineHighlighter from '../../line_highlighter';
import syntaxHighlight from '../../syntax_highlight';
export default {
......
......@@ -3,226 +3,228 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
(function() {
this.Sidebar = (function() {
function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside');
this.removeListeners();
this.addEventListeners();
function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside');
this.removeListeners();
this.addEventListeners();
}
Sidebar.initialize = function(currentUser) {
if (!this.instance) {
this.instance = new Sidebar(currentUser);
}
};
Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon');
this.sidebar.off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle');
};
Sidebar.prototype.addEventListeners = function() {
const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
$thisIcon = $this.find('i');
$allGutterToggleIcons = $('.js-sidebar-toggle i');
if ($thisIcon.hasClass('fa-angle-double-right')) {
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
$('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
} else {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
if (!triggered) {
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
}
};
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
if ($this.attr('data-delete-path')) {
url = "" + ($this.attr('data-delete-path'));
} else {
url = "" + ($this.data('url'));
}
$this.tooltip('hide');
return $.ajax({
url: url,
type: ajaxType,
dataType: 'json',
data: {
issuable_id: $this.data('issuable-id'),
issuable_type: $this.data('issuable-type')
},
beforeSend: (function(_this) {
return function() {
$('.js-issuable-todo').disable()
.addClass('is-loading');
};
})(this)
}).done((function(_this) {
return function(data) {
return _this.todoUpdateDone(data);
};
})(this));
};
Sidebar.prototype.todoUpdateDone = function(data) {
const deletePath = data.delete_path ? data.delete_path : null;
const attrPrefix = deletePath ? 'mark' : 'todo';
const $todoBtns = $('.js-issuable-todo');
$(document).trigger('todo:toggle', data.count);
$todoBtns.each((i, el) => {
const $el = $(el);
const $elText = $el.find('.js-issuable-todo-inner');
$el.removeClass('is-loading')
.enable()
.attr('aria-label', $el.data(`${attrPrefix}-text`))
.attr('data-delete-path', deletePath)
.attr('title', $el.data(`${attrPrefix}-text`));
if ($el.hasClass('has-tooltip')) {
$el.tooltip('fixTitle');
}
Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon');
this.sidebar.off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle');
};
Sidebar.prototype.addEventListeners = function() {
const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
$thisIcon = $this.find('i');
$allGutterToggleIcons = $('.js-sidebar-toggle i');
if ($thisIcon.hasClass('fa-angle-double-right')) {
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
$('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
} else {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
if (!triggered) {
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
}
};
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
if ($this.attr('data-delete-path')) {
url = "" + ($this.attr('data-delete-path'));
} else {
url = "" + ($this.data('url'));
}
$this.tooltip('hide');
return $.ajax({
url: url,
type: ajaxType,
dataType: 'json',
data: {
issuable_id: $this.data('issuable-id'),
issuable_type: $this.data('issuable-type')
},
beforeSend: (function(_this) {
return function() {
$('.js-issuable-todo').disable()
.addClass('is-loading');
};
})(this)
}).done((function(_this) {
return function(data) {
return _this.todoUpdateDone(data);
};
})(this));
};
Sidebar.prototype.todoUpdateDone = function(data) {
const deletePath = data.delete_path ? data.delete_path : null;
const attrPrefix = deletePath ? 'mark' : 'todo';
const $todoBtns = $('.js-issuable-todo');
$(document).trigger('todo:toggle', data.count);
$todoBtns.each((i, el) => {
const $el = $(el);
const $elText = $el.find('.js-issuable-todo-inner');
$el.removeClass('is-loading')
.enable()
.attr('aria-label', $el.data(`${attrPrefix}-text`))
.attr('data-delete-path', deletePath)
.attr('title', $el.data(`${attrPrefix}-text`));
if ($el.hasClass('has-tooltip')) {
$el.tooltip('fixTitle');
}
if ($el.data(`${attrPrefix}-icon`)) {
$elText.html($el.data(`${attrPrefix}-icon`));
} else {
$elText.text($el.data(`${attrPrefix}-text`));
}
});
};
Sidebar.prototype.sidebarDropdownLoading = function(e) {
var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
i = $sidebarCollapsedIcon.find('i');
$loading = $('<i class="fa fa-spinner fa-spin"></i>');
if (img.length) {
img.before($loading);
return img.hide();
} else if (i.length) {
i.before($loading);
return i.hide();
}
};
Sidebar.prototype.sidebarDropdownLoaded = function(e) {
var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
$sidebarCollapsedIcon.find('i.fa-spin').remove();
i = $sidebarCollapsedIcon.find('i');
if (img.length) {
return img.show();
} else {
return i.show();
}
};
Sidebar.prototype.sidebarCollapseClicked = function(e) {
var $block, sidebar;
if ($(e.currentTarget).hasClass('dont-change-state')) {
return;
}
sidebar = e.data;
e.preventDefault();
$block = $(this).closest('.block');
return sidebar.openDropdown($block);
};
Sidebar.prototype.openDropdown = function(blockOrName) {
var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
this.toggleSidebar('open');
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
});
};
Sidebar.prototype.setCollapseAfterUpdate = function($block) {
$block.addClass('collapse-after-update');
return $('.layout-page').addClass('with-overlay');
};
Sidebar.prototype.onSidebarDropdownHidden = function(e) {
var $block, sidebar;
sidebar = e.data;
e.preventDefault();
$block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block);
};
Sidebar.prototype.sidebarDropdownHidden = function($block) {
if ($block.hasClass('collapse-after-update')) {
$block.removeClass('collapse-after-update');
$('.layout-page').removeClass('with-overlay');
return this.toggleSidebar('hide');
}
};
Sidebar.prototype.triggerOpenSidebar = function() {
return this.sidebar.find('.js-sidebar-toggle').trigger('click');
};
Sidebar.prototype.toggleSidebar = function(action) {
if (action == null) {
action = 'toggle';
}
if (action === 'toggle') {
this.triggerOpenSidebar();
}
if (action === 'open') {
if (!this.isOpen()) {
this.triggerOpenSidebar();
}
}
if (action === 'hide') {
if (this.isOpen()) {
return this.triggerOpenSidebar();
}
}
};
if ($el.data(`${attrPrefix}-icon`)) {
$elText.html($el.data(`${attrPrefix}-icon`));
} else {
$elText.text($el.data(`${attrPrefix}-text`));
}
});
};
Sidebar.prototype.sidebarDropdownLoading = function(e) {
var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
i = $sidebarCollapsedIcon.find('i');
$loading = $('<i class="fa fa-spinner fa-spin"></i>');
if (img.length) {
img.before($loading);
return img.hide();
} else if (i.length) {
i.before($loading);
return i.hide();
}
};
Sidebar.prototype.sidebarDropdownLoaded = function(e) {
var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
$sidebarCollapsedIcon.find('i.fa-spin').remove();
i = $sidebarCollapsedIcon.find('i');
if (img.length) {
return img.show();
} else {
return i.show();
}
};
Sidebar.prototype.sidebarCollapseClicked = function(e) {
var $block, sidebar;
if ($(e.currentTarget).hasClass('dont-change-state')) {
return;
}
sidebar = e.data;
e.preventDefault();
$block = $(this).closest('.block');
return sidebar.openDropdown($block);
};
Sidebar.prototype.openDropdown = function(blockOrName) {
var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
this.toggleSidebar('open');
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
});
};
Sidebar.prototype.setCollapseAfterUpdate = function($block) {
$block.addClass('collapse-after-update');
return $('.layout-page').addClass('with-overlay');
};
Sidebar.prototype.onSidebarDropdownHidden = function(e) {
var $block, sidebar;
sidebar = e.data;
e.preventDefault();
$block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block);
};
Sidebar.prototype.sidebarDropdownHidden = function($block) {
if ($block.hasClass('collapse-after-update')) {
$block.removeClass('collapse-after-update');
$('.layout-page').removeClass('with-overlay');
return this.toggleSidebar('hide');
}
};
Sidebar.prototype.triggerOpenSidebar = function() {
return this.sidebar.find('.js-sidebar-toggle').trigger('click');
};
Sidebar.prototype.toggleSidebar = function(action) {
if (action == null) {
action = 'toggle';
}
if (action === 'toggle') {
this.triggerOpenSidebar();
}
if (action === 'open') {
if (!this.isOpen()) {
this.triggerOpenSidebar();
}
}
if (action === 'hide') {
if (this.isOpen()) {
return this.triggerOpenSidebar();
}
}
};
Sidebar.prototype.isOpen = function() {
return this.sidebar.is('.right-sidebar-expanded');
};
Sidebar.prototype.isOpen = function() {
return this.sidebar.is('.right-sidebar-expanded');
};
Sidebar.prototype.getBlock = function(name) {
return this.sidebar.find(".block." + name);
};
Sidebar.prototype.getBlock = function(name) {
return this.sidebar.find(".block." + name);
};
return Sidebar;
})();
}).call(window);
export default Sidebar;
/* global Mousetrap */
/* global sidebar */
import _ from 'underscore';
import 'mousetrap';
import Sidebar from './right_sidebar';
import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
......@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
}
static openSidebarDropdown(name) {
sidebar.openDropdown(name);
Sidebar.instance.openDropdown(name);
return false;
}
}
......@@ -6,7 +6,7 @@ Vue.use(VueResource);
export default class MRWidgetService {
constructor(endpoints) {
this.mergeResource = Vue.resource(endpoints.mergePath);
this.mergeCheckResource = Vue.resource(endpoints.statusPath);
this.mergeCheckResource = Vue.resource(`${endpoints.statusPath}?serializer=widget`);
this.cancelAutoMergeResource = Vue.resource(endpoints.cancelAutoMergePath);
this.removeWIPResource = Vue.resource(endpoints.removeWIPPath);
this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath);
......
......@@ -133,7 +133,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(project, current_user, wip_event: 'unwip')
.execute(@merge_request)
render json: serializer.represent(@merge_request)
render json: serialize_widget(@merge_request)
end
def commit_change_content
......@@ -149,7 +149,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(@project, current_user)
.cancel(@merge_request)
render json: serializer.represent(@merge_request)
render json: serialize_widget(@merge_request)
end
def merge
......@@ -313,6 +313,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
end
def serialize_widget(merge_request)
serializer.represent(merge_request, serializer: 'widget')
end
def serializer
MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
end
......
......@@ -32,7 +32,7 @@ module IssuablesHelper
end
end
def serialize_issuable(issuable)
def serialize_issuable(issuable, serializer: nil)
serializer_klass = case issuable
when Issue
IssueSerializer
......@@ -42,7 +42,7 @@ module IssuablesHelper
serializer_klass
.new(current_user: current_user, project: issuable.project)
.represent(issuable)
.represent(issuable, serializer: serializer)
.to_json
end
......
......@@ -3,14 +3,6 @@ class IssuableEntity < Grape::Entity
expose :id
expose :iid
expose :author_id
expose :description
expose :lock_version
expose :milestone_id
expose :title
expose :updated_by_id
expose :created_at
expose :updated_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
end
class IssuableSidebarEntity < Grape::Entity
include TimeTrackableEntity
include RequestAwareEntity
prepend ::EE::IssuableSidebarEntity
......@@ -9,9 +10,4 @@ class IssuableSidebarEntity < Grape::Entity
expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project)
end
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
expose :human_total_time_spent
end
......@@ -2,7 +2,15 @@ class IssueEntity < IssuableEntity
include TimeTrackableEntity
expose :state
expose :milestone_id
expose :updated_by_id
expose :created_at
expose :updated_at
expose :deleted_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
expose :lock_version
expose :author_id
expose :confidential
expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic
......
class MergeRequestSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `merge_request` based on `basic` key in `opts` param.
# to serialize the `merge_request` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def represent(merge_request, opts = {})
entity =
case opts[:serializer]
when 'basic', 'sidebar'
MergeRequestBasicEntity
else
MergeRequestEntity
when 'widget'
MergeRequestWidgetEntity
end
super(merge_request, opts, entity)
......
class MergeRequestEntity < IssuableEntity
include TimeTrackableEntity
prepend ::EE::MergeRequestEntity
class MergeRequestWidgetEntity < IssuableEntity
prepend ::EE::MergeRequestWidgetEntity
expose :state
expose :deleted_at
expose :in_progress_merge_commit_sha
expose :merge_commit_sha
expose :merge_error
......
......@@ -21,7 +21,7 @@
-# haml-lint:disable InlineJavaScript
:javascript
window.gl = window.gl || {};
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')}
// Append static, server-generated data not included in merge request entity (EE-Only)
// Object.assign would be useful here, but it blows up Phantom.js in tests
......
......@@ -8,16 +8,17 @@
= image_tag 'illustrations/issues.svg'
.col-xs-12
.text-content
- if has_button && current_user
- if current_user
%h4
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
%p
= _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.")
.text-center
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- else
= link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- if has_button
.text-center
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- else
= link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- else
%h4.text-center= _("There are no issues to show")
%p
......
---
title: Stop sending milestone and labels data over the wire for MR widget requests
merge_request:
author:
type: performance
......@@ -502,8 +502,8 @@ stages:
unit_test:
stage: test
script:
- composer install
- cp .env.example .env
- composer install
- php artisan key:generate
- php artisan migrate
- vendor/bin/phpunit
......
module EE
module MergeRequestEntity
module MergeRequestWidgetEntity
extend ActiveSupport::Concern
prepended do
......
......@@ -16,18 +16,18 @@ module QA
end
end
module Scenario
module Factory
autoload :License, 'qa/ee/factory/license'
module Geo
autoload :Node, 'qa/ee/scenario/geo/node'
autoload :Node, 'qa/ee/factory/geo/node'
end
end
module Scenario
module Test
autoload :Geo, 'qa/ee/scenario/test/geo'
end
module License
autoload :Add, 'qa/ee/scenario/license/add'
end
end
end
end
module QA
module EE
module Scenario
module Factory
module Geo
class Node < QA::Scenario::Template
class Node < QA::Factory::Base
attr_accessor :address
def perform
def fabricate!
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_geo_nodes }
......
module QA
module EE
module Factory
class License < QA::Factory::Base
def fabricate!(license)
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_license }
EE::Page::Admin::License.act(license) do |key|
add_new_license(key) if no_license?
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
module QA
module EE
module Scenario
module License
class Add < QA::Scenario::Template
def perform(license)
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_license }
EE::Page::Admin::License.act(license) do |key|
add_new_license(key) if no_license?
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
end
......@@ -39,32 +39,26 @@ module QA
end
def add_license
# TODO EE license to Runtime.license, gitlab-org/gitlab-qa#86
#
puts 'Adding GitLab EE license ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Scenario::License::Add.perform(ENV['EE_LICENSE'])
Factory::License.fabricate!(ENV['EE_LICENSE'])
end
end
def enable_hashed_storage
# TODO, Factory::HashedStorage - gitlab-org/gitlab-qa#86
#
puts 'Enabling hashed repository storage setting ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
QA::Scenario::Gitlab::Admin::HashedStorage.perform(:enabled)
QA::Factory::Settings::HashedStorage.fabricate!(:enabled)
end
end
def add_secondary_node
# TODO, Factory::Geo::Node - gitlab-org/gitlab-qa#86
#
puts 'Adding new Geo secondary node ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Scenario::Geo::Node.perform do |node|
Factory::Geo::Node.fabricate! do |node|
node.address = QA::Runtime::Scenario.geo_secondary_address
end
end
......
......@@ -11,7 +11,7 @@ module QA
return unless ENV['EE_LICENSE']
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) do
EE::Scenario::License::Add.perform(ENV['EE_LICENSE'])
EE::Factory::License.fabricate!(ENV['EE_LICENSE'])
end
end
end
......
......@@ -4,9 +4,9 @@ module QA
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
scenario.name = 'geo-project'
scenario.description = 'Geo test project'
Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project'
project.description = 'Geo test project'
end
geo_project_name = Page::Project::Show.act { project_name }
......
......@@ -91,11 +91,11 @@ describe Projects::MergeRequestsController do
end
end
context 'without basic serializer param' do
it 'renders the merge request in the json format' do
go(format: :json)
context 'with widget serializer param' do
it 'renders widget MR entity as json' do
go(serializer: 'widget', format: :json)
expect(response).to match_response_schema('entities/merge_request')
expect(response).to match_response_schema('entities/merge_request_widget')
end
end
end
......
......@@ -10,8 +10,7 @@ describe EpicEntity do
subject { described_class.new(resource, request: request).as_json }
it 'has Issuable attributes' do
expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
:title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
expect(subject).to include(:id, :iid, :description, :title)
end
it 'has epic specific attributes' do
......
require 'spec_helper'
describe MergeRequestEntity do
describe MergeRequestWidgetEntity do
let(:user) { create(:user) }
let(:project) { create :project, :repository }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
......
......@@ -8,765 +8,789 @@ describe 'Issues' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
sign_in(user)
user2 = create(:user)
project.team << [[user, user2], :developer]
end
describe 'while user is signed out' do
describe 'empty state' do
it 'user sees empty state' do
visit project_issues_path(project)
describe 'Edit issue' do
let!(:issue) do
create(:issue,
author: user,
assignees: [user],
project: project)
expect(page).to have_content('Register / Sign In')
expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.')
expect(page).to have_content('You can register or sign in to create issues for this project.')
end
end
end
describe 'while user is signed in' do
before do
visit edit_project_issue_path(project, issue)
find('.js-zen-enter').click
end
it 'opens new issue popup' do
expect(page).to have_content("Issue ##{issue.iid}")
end
end
sign_in(user)
user2 = create(:user)
describe 'Editing issue assignee' do
let!(:issue) do
create(:issue,
author: user,
assignees: [user],
project: project)
project.team << [[user, user2], :developer]
end
it 'allows user to select unassigned', :js do
visit edit_project_issue_path(project, issue)
expect(page).to have_content "Assignee #{user.name}"
describe 'empty state' do
it 'user sees empty state' do
visit project_issues_path(project)
first('.js-user-search').click
click_link 'Unassigned'
expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project')
expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.')
expect(page).to have_content('New issue')
end
end
click_button 'Save changes'
describe 'Edit issue' do
let!(:issue) do
create(:issue,
author: user,
assignees: [user],
project: project)
end
page.within('.assignee') do
expect(page).to have_content 'No assignee - assign yourself'
before do
visit edit_project_issue_path(project, issue)
find('.js-zen-enter').click
end
expect(issue.reload.assignees).to be_empty
it 'opens new issue popup' do
expect(page).to have_content("Issue ##{issue.iid}")
end
end
end
describe 'due date', :js do
context 'on new form' do
before do
visit new_project_issue_path(project)
describe 'Editing issue assignee' do
let!(:issue) do
create(:issue,
author: user,
assignees: [user],
project: project)
end
it 'saves with due date' do
date = Date.today.at_beginning_of_month
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
it 'allows user to select unassigned', :js do
visit edit_project_issue_path(project, issue)
page.within '.pika-single' do
click_button date.day
end
expect(page).to have_content "Assignee #{user.name}"
expect(find('#issuable-due-date').value).to eq date.to_s
first('.js-user-search').click
click_link 'Unassigned'
click_button 'Submit issue'
click_button 'Save changes'
page.within '.issuable-sidebar' do
expect(page).to have_content date.to_s(:medium)
page.within('.assignee') do
expect(page).to have_content 'No assignee - assign yourself'
end
expect(issue.reload.assignees).to be_empty
end
end
context 'on edit form' do
let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
describe 'due date', :js do
context 'on new form' do
before do
visit new_project_issue_path(project)
end
before do
visit edit_project_issue_path(project, issue)
end
it 'saves with due date' do
date = Date.today.at_beginning_of_month
it 'saves with due date' do
date = Date.today.at_beginning_of_month
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
expect(find('#issuable-due-date').value).to eq date.to_s
page.within '.pika-single' do
click_button date.day
end
date = date.tomorrow
expect(find('#issuable-due-date').value).to eq date.to_s
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
click_button 'Submit issue'
page.within '.pika-single' do
click_button date.day
page.within '.issuable-sidebar' do
expect(page).to have_content date.to_s(:medium)
end
end
end
expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Save changes'
context 'on edit form' do
let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
page.within '.issuable-sidebar' do
expect(page).to have_content date.to_s(:medium)
before do
visit edit_project_issue_path(project, issue)
end
end
it 'warns about version conflict' do
issue.update(title: "New title")
it 'saves with due date' do
date = Date.today.at_beginning_of_month
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Save changes'
date = date.tomorrow
expect(page).to have_content 'Someone edited the issue the same time you did'
end
end
end
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
describe 'Issue info' do
it 'links to current issue in breadcrubs' do
issue = create(:issue, project: project)
page.within '.pika-single' do
click_button date.day
end
visit project_issue_path(project, issue)
expect(find('#issuable-due-date').value).to eq date.to_s
expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue))
end
click_button 'Save changes'
it 'excludes award_emoji from comment count' do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar')
create(:award_emoji, awardable: issue)
page.within '.issuable-sidebar' do
expect(page).to have_content date.to_s(:medium)
end
end
visit project_issues_path(project, assignee_id: user.id)
it 'warns about version conflict' do
issue.update(title: "New title")
expect(page).to have_content 'foobar'
expect(page.all('.no-comments').first.text).to eq "0"
end
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
it 'shows weight on issue row' do
create(:issue, author: user, project: project, weight: 2)
click_button 'Save changes'
visit project_issues_path(project)
page.within(first('.issuable-info')) do
expect(page).to have_selector('.fa-balance-scale')
expect(page).to have_content(2)
expect(page).to have_content 'Someone edited the issue the same time you did'
end
end
end
end
describe 'Filter issue' do
before do
%w(foobar barbaz gitlab).each do |title|
create(:issue,
author: user,
assignees: [user],
project: project,
title: title)
end
describe 'Issue info' do
it 'links to current issue in breadcrubs' do
issue = create(:issue, project: project)
@issue = Issue.find_by(title: 'foobar')
@issue.milestone = create(:milestone, project: project)
@issue.assignees = []
@issue.save
end
visit project_issue_path(project, issue)
let(:issue) { @issue }
expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue))
end
it 'allows filtering by issues with no specified assignee' do
visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
it 'excludes award_emoji from comment count' do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar')
create(:award_emoji, awardable: issue)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
expect(page).not_to have_content 'gitlab'
end
visit project_issues_path(project, assignee_id: user.id)
it 'allows filtering by a specified assignee' do
visit project_issues_path(project, assignee_id: user.id)
expect(page).to have_content 'foobar'
expect(page.all('.no-comments').first.text).to eq "0"
end
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
expect(page).to have_content 'gitlab'
end
end
it 'shows weight on issue row' do
create(:issue, author: user, project: project, weight: 2)
describe 'filter issue' do
titles = %w[foo bar baz]
titles.each_with_index do |title, index|
let!(title.to_sym) do
create(:issue, title: title,
project: project,
created_at: Time.now - (index * 60))
visit project_issues_path(project)
page.within(first('.issuable-info')) do
expect(page).to have_selector('.fa-balance-scale')
expect(page).to have_content(2)
end
end
end
let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
it 'sorts by newest' do
visit project_issues_path(project, sort: sort_value_created_date)
describe 'Filter issue' do
before do
%w(foobar barbaz gitlab).each do |title|
create(:issue,
author: user,
assignees: [user],
project: project,
title: title)
end
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
@issue = Issue.find_by(title: 'foobar')
@issue.milestone = create(:milestone, project: project)
@issue.assignees = []
@issue.save
end
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
visit project_issues_path(project, sort: sort_value_recently_updated)
let(:issue) { @issue }
expect(first_issue).to include('baz')
end
it 'allows filtering by issues with no specified assignee' do
visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
describe 'sorting by due date' do
before do
foo.update(due_date: 1.day.from_now)
bar.update(due_date: 6.days.from_now)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
expect(page).not_to have_content 'gitlab'
end
it 'sorts by due date' do
visit project_issues_path(project, sort: sort_value_due_date)
it 'allows filtering by a specified assignee' do
visit project_issues_path(project, assignee_id: user.id)
expect(first_issue).to include('foo')
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
expect(page).to have_content 'gitlab'
end
end
it 'sorts by due date by excluding nil due dates' do
bar.update(due_date: nil)
describe 'filter issue' do
titles = %w[foo bar baz]
titles.each_with_index do |title, index|
let!(title.to_sym) do
create(:issue, title: title,
project: project,
created_at: Time.now - (index * 60))
end
end
let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
visit project_issues_path(project, sort: sort_value_due_date)
it 'sorts by newest' do
visit project_issues_path(project, sort: sort_value_created_date)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
context 'with a filter on labels' do
let(:label) { create(:label, project: project) }
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
visit project_issues_path(project, sort: sort_value_recently_updated)
expect(first_issue).to include('baz')
end
describe 'sorting by due date' do
before do
create(:label_link, label: label, target: foo)
foo.update(due_date: 1.day.from_now)
bar.update(due_date: 6.days.from_now)
end
it 'sorts by least recently due date by excluding nil due dates' do
it 'sorts by due date' do
visit project_issues_path(project, sort: sort_value_due_date)
expect(first_issue).to include('foo')
end
it 'sorts by due date by excluding nil due dates' do
bar.update(due_date: nil)
visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
visit project_issues_path(project, sort: sort_value_due_date)
expect(first_issue).to include('foo')
end
end
end
describe 'filtering by due date' do
before do
foo.update(due_date: 1.day.from_now)
bar.update(due_date: 6.days.from_now)
end
context 'with a filter on labels' do
let(:label) { create(:label, project: project) }
before do
create(:label_link, label: label, target: foo)
end
it 'sorts by least recently due date by excluding nil due dates' do
bar.update(due_date: nil)
it 'filters by none' do
visit project_issues_path(project, due_date: Issue::NoDueDate.name)
visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
expect(first_issue).to include('foo')
end
end
end
it 'filters by any' do
visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
describe 'filtering by due date' do
before do
foo.update(due_date: 1.day.from_now)
bar.update(due_date: 6.days.from_now)
end
it 'filters by none' do
visit project_issues_path(project, due_date: Issue::NoDueDate.name)
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).to have_content('baz')
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
end
end
it 'filters by any' do
visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).to have_content('baz')
end
end
end
it 'filters by due this week' do
foo.update(due_date: Date.today.beginning_of_week + 2.days)
bar.update(due_date: Date.today.end_of_week)
baz.update(due_date: Date.today - 8.days)
it 'filters by due this week' do
foo.update(due_date: Date.today.beginning_of_week + 2.days)
bar.update(due_date: Date.today.end_of_week)
baz.update(due_date: Date.today - 8.days)
visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).not_to have_content('baz')
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).not_to have_content('baz')
end
end
end
it 'filters by due this month' do
foo.update(due_date: Date.today.beginning_of_month + 2.days)
bar.update(due_date: Date.today.end_of_month)
baz.update(due_date: Date.today - 50.days)
it 'filters by due this month' do
foo.update(due_date: Date.today.beginning_of_month + 2.days)
bar.update(due_date: Date.today.end_of_month)
baz.update(due_date: Date.today - 50.days)
visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).not_to have_content('baz')
page.within '.issues-holder' do
expect(page).to have_content('foo')
expect(page).to have_content('bar')
expect(page).not_to have_content('baz')
end
end
end
it 'filters by overdue' do
foo.update(due_date: Date.today + 2.days)
bar.update(due_date: Date.today + 20.days)
baz.update(due_date: Date.yesterday)
it 'filters by overdue' do
foo.update(due_date: Date.today + 2.days)
bar.update(due_date: Date.today + 20.days)
baz.update(due_date: Date.yesterday)
visit project_issues_path(project, due_date: Issue::Overdue.name)
visit project_issues_path(project, due_date: Issue::Overdue.name)
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
end
end
end
end
describe 'sorting by milestone' do
before do
foo.milestone = newer_due_milestone
foo.save
bar.milestone = later_due_milestone
bar.save
end
describe 'sorting by milestone' do
before do
foo.milestone = newer_due_milestone
foo.save
bar.milestone = later_due_milestone
bar.save
end
it 'sorts by milestone' do
visit project_issues_path(project, sort: sort_value_milestone)
it 'sorts by milestone' do
visit project_issues_path(project, sort: sort_value_milestone)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
end
end
describe 'combine filter and sort' do
let(:user2) { create(:user) }
describe 'combine filter and sort' do
let(:user2) { create(:user) }
before do
foo.assignees << user2
foo.save
bar.assignees << user2
bar.save
end
before do
foo.assignees << user2
foo.save
bar.assignees << user2
bar.save
end
it 'sorts with a filter applied' do
visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id)
it 'sorts with a filter applied' do
visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id)
expect(first_issue).to include('foo')
expect(last_issue).to include('bar')
expect(page).not_to have_content('baz')
expect(first_issue).to include('foo')
expect(last_issue).to include('bar')
expect(page).not_to have_content('baz')
end
end
end
end
describe 'when I want to reset my incoming email token' do
let(:project1) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project1) }
describe 'when I want to reset my incoming email token' do
let(:project1) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project1) }
before do
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
project1.team << [user, :master]
visit namespace_project_issues_path(user.namespace, project1)
end
before do
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
project1.team << [user, :master]
visit namespace_project_issues_path(user.namespace, project1)
end
it 'changes incoming email address token', :js do
find('.issuable-email-modal-btn').click
previous_token = find('input#issuable_email').value
find('.incoming-email-token-reset').click
it 'changes incoming email address token', :js do
find('.issuable-email-modal-btn').click
previous_token = find('input#issuable_email').value
find('.incoming-email-token-reset').click
wait_for_requests
wait_for_requests
expect(page).to have_no_field('issuable_email', with: previous_token)
new_token = project1.new_issuable_address(user.reload, 'issue')
expect(page).to have_field(
'issuable_email',
with: new_token
)
expect(page).to have_no_field('issuable_email', with: previous_token)
new_token = project1.new_issuable_address(user.reload, 'issue')
expect(page).to have_field(
'issuable_email',
with: new_token
)
end
end
end
describe 'update labels from issue#show', :js do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
let!(:label) { create(:label, project: project) }
describe 'update labels from issue#show', :js do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
let!(:label) { create(:label, project: project) }
before do
visit project_issue_path(project, issue)
end
before do
visit project_issue_path(project, issue)
end
it 'will not send ajax request when no data is changed' do
page.within '.labels' do
click_link 'Edit'
it 'will not send ajax request when no data is changed' do
page.within '.labels' do
click_link 'Edit'
find('.dropdown-menu-close', match: :first).click
find('.dropdown-menu-close', match: :first).click
expect(page).not_to have_selector('.block-loading')
expect(page).not_to have_selector('.block-loading')
end
end
end
end
describe 'update assignee from issue#show' do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
describe 'update assignee from issue#show' do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
context 'by authorized user' do
it 'allows user to select unassigned', :js do
visit project_issue_path(project, issue)
context 'by authorized user' do
it 'allows user to select unassigned', :js do
visit project_issue_path(project, issue)
page.within('.assignee') do
expect(page).to have_content "#{user.name}"
page.within('.assignee') do
expect(page).to have_content "#{user.name}"
click_link 'Edit'
click_link 'Unassigned'
first('.title').click
expect(page).to have_content 'No assignee'
end
click_link 'Edit'
click_link 'Unassigned'
first('.title').click
expect(page).to have_content 'No assignee'
end
# wait_for_requests does not work with vue-resource at the moment
sleep 1
# wait_for_requests does not work with vue-resource at the moment
sleep 1
expect(issue.reload.assignees).to be_empty
end
expect(issue.reload.assignees).to be_empty
end
it 'allows user to select an assignee', :js do
issue2 = create(:issue, project: project, author: user)
visit project_issue_path(project, issue2)
it 'allows user to select an assignee', :js do
issue2 = create(:issue, project: project, author: user)
visit project_issue_path(project, issue2)
page.within('.assignee') do
expect(page).to have_content "No assignee"
end
page.within('.assignee') do
expect(page).to have_content "No assignee"
end
page.within '.assignee' do
click_link 'Edit'
end
page.within '.assignee' do
click_link 'Edit'
end
page.within '.dropdown-menu-user' do
click_link user.name
end
page.within '.dropdown-menu-user' do
click_link user.name
end
page.within('.assignee') do
expect(page).to have_content user.name
page.within('.assignee') do
expect(page).to have_content user.name
end
end
end
it 'allows user to unselect themselves', :js do
issue2 = create(:issue, project: project, author: user)
visit project_issue_path(project, issue2)
it 'allows user to unselect themselves', :js do
issue2 = create(:issue, project: project, author: user)
visit project_issue_path(project, issue2)
page.within '.assignee' do
click_link 'Edit'
click_link user.name
page.within '.assignee' do
click_link 'Edit'
click_link user.name
find('.dropdown-menu-toggle').click
find('.dropdown-menu-toggle').click
page.within '.value .author' do
expect(page).to have_content user.name
end
page.within '.value .author' do
expect(page).to have_content user.name
end
click_link 'Edit'
click_link user.name
click_link 'Edit'
click_link user.name
find('.dropdown-menu-toggle').click
find('.dropdown-menu-toggle').click
page.within '.value .assign-yourself' do
expect(page).to have_content "No assignee"
page.within '.value .assign-yourself' do
expect(page).to have_content "No assignee"
end
end
end
end
end
context 'by unauthorized user' do
let(:guest) { create(:user) }
context 'by unauthorized user' do
let(:guest) { create(:user) }
before do
project.team << [[guest], :guest]
end
before do
project.team << [[guest], :guest]
end
it 'shows assignee text', :js do
sign_out(:user)
sign_in(guest)
it 'shows assignee text', :js do
sign_out(:user)
sign_in(guest)
visit project_issue_path(project, issue)
expect(page).to have_content issue.assignees.first.name
visit project_issue_path(project, issue)
expect(page).to have_content issue.assignees.first.name
end
end
end
end
describe 'update weight from issue#show', :js do
let!(:issue) { create(:issue, project: project) }
describe 'update weight from issue#show', :js do
let!(:issue) { create(:issue, project: project) }
before do
visit project_issue_path(project, issue)
end
before do
visit project_issue_path(project, issue)
end
it 'allows user to update to a weight' do
page.within('.weight') do
expect(page).to have_content "None"
click_link 'Edit'
it 'allows user to update to a weight' do
page.within('.weight') do
expect(page).to have_content "None"
click_link 'Edit'
find('.dropdown-content a', text: '1').click
find('.dropdown-content a', text: '1').click
page.within('.value') do
expect(page).to have_content "1"
page.within('.value') do
expect(page).to have_content "1"
end
end
end
end
end
describe 'update milestone from issue#show' do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:milestone) { create(:milestone, project: project) }
describe 'update milestone from issue#show' do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:milestone) { create(:milestone, project: project) }
context 'by authorized user' do
it 'allows user to select unassigned', :js do
visit project_issue_path(project, issue)
context 'by authorized user' do
it 'allows user to select unassigned', :js do
visit project_issue_path(project, issue)
page.within('.milestone') do
expect(page).to have_content "None"
end
page.within('.milestone') do
expect(page).to have_content "None"
end
find('.block.milestone .edit-link').click
sleep 2 # wait for ajax stuff to complete
first('.dropdown-content li').click
sleep 2
page.within('.milestone') do
expect(page).to have_content 'None'
end
find('.block.milestone .edit-link').click
sleep 2 # wait for ajax stuff to complete
first('.dropdown-content li').click
sleep 2
page.within('.milestone') do
expect(page).to have_content 'None'
expect(issue.reload.milestone).to be_nil
end
expect(issue.reload.milestone).to be_nil
end
it 'allows user to de-select milestone', :js do
visit project_issue_path(project, issue)
it 'allows user to de-select milestone', :js do
visit project_issue_path(project, issue)
page.within('.milestone') do
click_link 'Edit'
click_link milestone.title
page.within('.milestone') do
click_link 'Edit'
click_link milestone.title
page.within '.value' do
expect(page).to have_content milestone.title
end
page.within '.value' do
expect(page).to have_content milestone.title
end
click_link 'Edit'
click_link milestone.title
click_link 'Edit'
click_link milestone.title
page.within '.value' do
expect(page).to have_content 'None'
page.within '.value' do
expect(page).to have_content 'None'
end
end
end
end
end
context 'by unauthorized user' do
let(:guest) { create(:user) }
context 'by unauthorized user' do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
issue.milestone = milestone
issue.save
end
before do
project.team << [guest, :guest]
issue.milestone = milestone
issue.save
end
it 'shows milestone text', :js do
sign_out(:user)
sign_in(guest)
it 'shows milestone text', :js do
sign_out(:user)
sign_in(guest)
visit project_issue_path(project, issue)
expect(page).to have_content milestone.title
visit project_issue_path(project, issue)
expect(page).to have_content milestone.title
end
end
end
end
describe 'new issue' do
let!(:issue) { create(:issue, project: project) }
describe 'new issue' do
let!(:issue) { create(:issue, project: project) }
context 'by unauthenticated user' do
before do
sign_out(:user)
end
context 'by unauthenticated user' do
before do
sign_out(:user)
end
it 'redirects to signin then back to new issue after signin' do
visit project_issues_path(project)
it 'redirects to signin then back to new issue after signin' do
visit project_issues_path(project)
page.within '.nav-controls' do
click_link 'New issue'
end
page.within '.nav-controls' do
click_link 'New issue'
end
expect(current_path).to eq new_user_session_path
expect(current_path).to eq new_user_session_path
gitlab_sign_in(create(:user))
gitlab_sign_in(create(:user))
expect(current_path).to eq new_project_issue_path(project)
expect(current_path).to eq new_project_issue_path(project)
end
end
end
context 'dropzone upload file', :js do
before do
visit new_project_issue_path(project)
end
context 'dropzone upload file', :js do
before do
visit new_project_issue_path(project)
end
it 'uploads file when dragging into textarea' do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
it 'uploads file when dragging into textarea' do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
it "doesn't add double newline to end of a single attachment markdown" do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
it "doesn't add double newline to end of a single attachment markdown" do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).not_to match /\n\n$/
end
expect(page.find_field("issue_description").value).not_to match /\n\n$/
end
it "cancels a file upload correctly" do
slow_requests do
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
it "cancels a file upload correctly" do
slow_requests do
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
click_button 'Cancel'
end
click_button 'Cancel'
end
expect(page).to have_button('Attach a file')
expect(page).not_to have_button('Cancel')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
expect(page).to have_button('Attach a file')
expect(page).not_to have_button('Cancel')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
end
end
context 'form filled by URL parameters' do
let(:project) { create(:project, :public, :repository) }
before do
project.repository.create_file(
user,
'.gitlab/issue_templates/bug.md',
'this is a test "bug" template',
message: 'added issue template',
branch_name: 'master')
context 'form filled by URL parameters' do
let(:project) { create(:project, :public, :repository) }
visit new_project_issue_path(project, issuable_template: 'bug')
end
before do
project.repository.create_file(
user,
'.gitlab/issue_templates/bug.md',
'this is a test "bug" template',
message: 'added issue template',
branch_name: 'master')
visit new_project_issue_path(project, issuable_template: 'bug')
end
it 'fills in template' do
expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug')
it 'fills in template' do
expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug')
end
end
end
end
describe 'new issue by email' do
shared_examples 'show the email in the modal' do
let(:issue) { create(:issue, project: project) }
describe 'new issue by email' do
shared_examples 'show the email in the modal' do
let(:issue) { create(:issue, project: project) }
before do
project.issues << issue
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
before do
project.issues << issue
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
visit project_issues_path(project)
click_button('Email a new issue')
end
visit project_issues_path(project)
click_button('Email a new issue')
end
it 'click the button to show modal for the new email' do
page.within '#issuable-email-modal' do
email = project.new_issuable_address(user, 'issue')
it 'click the button to show modal for the new email' do
page.within '#issuable-email-modal' do
email = project.new_issuable_address(user, 'issue')
expect(page).to have_selector("input[value='#{email}']")
expect(page).to have_selector("input[value='#{email}']")
end
end
end
end
context 'with existing issues' do
let!(:issue) { create(:issue, project: project, author: user) }
context 'with existing issues' do
let!(:issue) { create(:issue, project: project, author: user) }
it_behaves_like 'show the email in the modal'
end
it_behaves_like 'show the email in the modal'
end
context 'without existing issues' do
it_behaves_like 'show the email in the modal'
context 'without existing issues' do
it_behaves_like 'show the email in the modal'
end
end
end
describe 'due date' do
context 'update due on issue#show', :js do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
describe 'due date' do
context 'update due on issue#show', :js do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
before do
visit project_issue_path(project, issue)
end
before do
visit project_issue_path(project, issue)
end
it 'adds due date to issue' do
date = Date.today.at_beginning_of_month + 2.days
it 'adds due date to issue' do
date = Date.today.at_beginning_of_month + 2.days
page.within '.due_date' do
click_link 'Edit'
page.within '.due_date' do
click_link 'Edit'
page.within '.pika-single' do
click_button date.day
end
page.within '.pika-single' do
click_button date.day
end
wait_for_requests
wait_for_requests
expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
end
end
end
it 'removes due date from issue' do
date = Date.today.at_beginning_of_month + 2.days
it 'removes due date from issue' do
date = Date.today.at_beginning_of_month + 2.days
page.within '.due_date' do
click_link 'Edit'
page.within '.due_date' do
click_link 'Edit'
page.within '.pika-single' do
click_button date.day
end
page.within '.pika-single' do
click_button date.day
end
wait_for_requests
wait_for_requests
expect(page).to have_no_content 'No due date'
expect(page).to have_no_content 'No due date'
click_link 'remove due date'
expect(page).to have_content 'No due date'
click_link 'remove due date'
expect(page).to have_content 'No due date'
end
end
end
end
end
describe 'title issue#show', :js do
it 'updates the title', :js do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title')
describe 'title issue#show', :js do
it 'updates the title', :js do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title')
visit project_issue_path(project, issue)
visit project_issue_path(project, issue)
expect(page).to have_text("new title")
expect(page).to have_text("new title")
issue.update(title: "updated title")
issue.update(title: "updated title")
wait_for_requests
expect(page).to have_text("updated title")
wait_for_requests
expect(page).to have_text("updated title")
end
end
end
describe 'confidential issue#show', :js do
it 'shows confidential sibebar information as confidential and can be turned off' do
issue = create(:issue, :confidential, project: project)
describe 'confidential issue#show', :js do
it 'shows confidential sibebar information as confidential and can be turned off' do
issue = create(:issue, :confidential, project: project)
visit project_issue_path(project, issue)
visit project_issue_path(project, issue)
expect(page).to have_css('.issuable-note-warning')
expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active')
expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active')
expect(page).to have_css('.issuable-note-warning')
expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active')
expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active')
find('.confidential-edit').click
expect(page).to have_css('.sidebar-item-warning-message')
find('.confidential-edit').click
expect(page).to have_css('.sidebar-item-warning-message')
within('.sidebar-item-warning-message') do
find('.btn-close').click
end
within('.sidebar-item-warning-message') do
find('.btn-close').click
end
wait_for_requests
wait_for_requests
visit project_issue_path(project, issue)
visit project_issue_path(project, issue)
expect(page).not_to have_css('.is-active')
expect(page).not_to have_css('.is-active')
end
end
end
end
......@@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do
visit_merge_request
end
def visit_merge_request(format = :html)
visit project_merge_request_path(project, merge_request, format: format)
def visit_merge_request(format: :html, serializer: nil)
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
it 'should display a mini pipeline graph' do
......@@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do
end
it 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
create(:ci_build, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
expect(before.count).to eq(after.count)
expect(before.cached_count).to eq(after.cached_count)
......
......@@ -81,15 +81,15 @@
"target_branch_tree_path": { "type": "string" },
"source_branch_path": { "type": "string" },
"conflict_resolution_path": { "type": ["string", "null"] },
"cancel_merge_when_pipeline_succeeds_path": { "type": "string" },
"create_issue_to_resolve_discussions_path": { "type": "string" },
"merge_path": { "type": "string" },
"cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] },
"create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
"merge_path": { "type": ["string", "null"] },
"cherry_pick_in_fork_path": { "type": ["string", "null"] },
"revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
"status_path": { "type": "string" },
"new_blob_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
"merge_commit_message_with_description": { "type": "string" },
......
/* global Sidebar */
/* eslint-disable no-new */
import _ from 'underscore';
import '~/right_sidebar';
import Sidebar from '~/right_sidebar';
describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html.raw';
......
/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
/* global LineHighlighter */
import '~/line_highlighter';
import LineHighlighter from '~/line_highlighter';
(function() {
describe('LineHighlighter', function() {
......
/* global Notes */
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import '~/render_math';
import '~/notes';
import Notes from '~/notes';
const upArrowKeyCode = 38;
......
/* eslint-disable space-before-function-paren, no-return-assign */
/* global MergeRequest */
import '~/merge_request';
import MergeRequest from '~/merge_request';
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper';
......
/* eslint-disable no-var, comma-dangle, object-shorthand */
/* global Notes */
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs';
......@@ -7,7 +6,7 @@ import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
import Diff from '~/diff';
import '~/notes';
import Notes from '~/notes';
import 'vendor/jquery.scrollTo';
(function () {
......@@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo';
loadFixtures('merge_requests/diff_comment.html.raw');
$('body').attr('data-page', 'projects:merge_requests:show');
window.gl.ImageFile = () => {};
window.notes = new Notes('', []);
spyOn(window.notes, 'toggleDiffNote').and.callThrough();
Notes.initialize('', []);
spyOn(Notes.instance, 'toggleDiffNote').and.callThrough();
});
afterEach(() => {
......@@ -338,7 +337,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object),
lineType: 'old',
forceShow: true,
......@@ -349,7 +348,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
......@@ -359,7 +358,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
});
......@@ -393,7 +392,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object),
lineType: 'new',
forceShow: true,
......@@ -404,7 +403,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
......@@ -414,7 +413,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
});
......
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
/* global Notes */
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import '~/notes';
import Notes from '~/notes';
(function() {
window.gon || (window.gon = {});
......
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
/* global Sidebar */
import '~/commons/bootstrap';
import '~/right_sidebar';
import Sidebar from '~/right_sidebar';
(function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
......
require 'spec_helper'
describe MergeRequestSerializer do
let(:user) { build_stubbed(:user) }
let(:merge_request) { build_stubbed(:merge_request) }
let(:serializer) do
let(:user) { create(:user) }
let(:resource) { create(:merge_request) }
let(:json_entity) do
described_class.new(current_user: user)
.represent(resource, serializer: serializer)
.with_indifferent_access
end
describe '#represent' do
let(:opts) { { serializer: serializer_entity } }
subject { serializer.represent(merge_request, serializer: serializer_entity) }
context 'widget merge request serialization' do
let(:serializer) { 'widget' }
context 'when passing basic serializer param' do
let(:serializer_entity) { 'basic' }
it 'matches issue json schema' do
expect(json_entity).to match_schema('entities/merge_request_widget')
end
end
it 'calls super class #represent with correct params' do
expect_any_instance_of(BaseSerializer).to receive(:represent)
.with(merge_request, opts, MergeRequestBasicEntity)
context 'sidebar merge request serialization' do
let(:serializer) { 'sidebar' }
subject
end
it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end
end
context 'when serializer param is falsy' do
let(:serializer_entity) { nil }
context 'basic merge request serialization' do
let(:serializer) { 'basic' }
it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end
end
it 'calls super class #represent with correct params' do
expect_any_instance_of(BaseSerializer).to receive(:represent)
.with(merge_request, opts, MergeRequestEntity)
context 'no serializer' do
let(:serializer) { nil }
subject
end
it 'raises an error' do
expect { json_entity }.to raise_error(NoMethodError)
end
end
end
require 'spec_helper'
describe MergeRequestEntity do
describe MergeRequestWidgetEntity do
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
......@@ -35,37 +35,6 @@ describe MergeRequestEntity do
end
end
it 'includes issues_links' do
issues_links = subject[:issues_links]
expect(issues_links).to include(:closing, :mentioned_but_not_closing,
:assign_to_closing)
end
it 'has Issuable attributes' do
expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
:title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
end
it 'has time estimation attributes' do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
it 'has important MergeRequest attributes' do
expect(subject).to include(:state, :deleted_at, :diff_head_sha, :merge_commit_message,
:has_conflicts, :has_ci, :merge_path,
:conflict_resolution_path,
:cancel_merge_when_pipeline_succeeds_path,
:create_issue_to_resolve_discussions_path,
:source_branch_path, :target_branch_commits_path,
:target_branch_tree_path, :commits_count, :merge_ongoing,
:ff_only_enabled,
## EE
:can_push_to_source_branch, :approvals_before_merge,
:squash, :rebase_commit_sha, :rebase_in_progress,
:approvals_path)
end
it 'has email_patches_path' do
expect(subject[:email_patches_path])
.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch")
......@@ -120,18 +89,6 @@ describe MergeRequestEntity do
end
end
it 'includes merge_event' do
create(:event, :merged, author: user, project: resource.project, target: resource)
expect(subject[:merge_event]).to include(:author, :updated_at)
end
it 'includes closed_event' do
create(:event, :closed, author: user, project: resource.project, target: resource)
expect(subject[:closed_event]).to include(:author, :updated_at)
end
describe 'diverged_commits_count' do
context 'when MR open and its diverging' do
it 'returns diverged commits count' do
......
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