summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md20
-rw-r--r--app/assets/javascripts/application.js31
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js.es62
-rw-r--r--app/assets/javascripts/dispatcher.js.es614
-rw-r--r--app/assets/javascripts/extensions/element.js.es617
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6113
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js33
-rw-r--r--app/assets/javascripts/pipelines.js.es69
-rw-r--r--app/assets/javascripts/subscription.js52
-rw-r--r--app/assets/javascripts/subscription.js.es650
-rw-r--r--app/assets/javascripts/wikis.js38
-rw-r--r--app/assets/javascripts/wikis.js.es673
-rw-r--r--app/assets/stylesheets/framework/calendar.scss2
-rw-r--r--app/assets/stylesheets/framework/flash.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/framework/pagination.scss4
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss7
-rw-r--r--app/assets/stylesheets/framework/typography.scss1
-rw-r--r--app/assets/stylesheets/pages/boards.scss1
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss8
-rw-r--r--app/assets/stylesheets/pages/environments.scss2
-rw-r--r--app/assets/stylesheets/pages/groups.scss6
-rw-r--r--app/assets/stylesheets/pages/milestone.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/profile.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss2
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/assets/stylesheets/pages/wiki.scss125
-rw-r--r--app/controllers/help_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/pipelines_controller.rb10
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb26
-rw-r--r--app/helpers/issuables_helper.rb10
-rw-r--r--app/helpers/nav_helper.rb5
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/merge_request.rb17
-rw-r--r--app/models/merge_request_diff.rb10
-rw-r--r--app/services/merge_requests/refresh_service.rb4
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml1
-rw-r--r--app/views/projects/boards/components/sidebar/_notifications.html.haml12
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml17
-rw-r--r--app/views/projects/pipelines/show.html.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml3
-rw-r--r--app/views/projects/wikis/_nav.html.haml16
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml23
-rw-r--r--app/views/projects/wikis/edit.html.haml32
-rw-r--r--app/views/projects/wikis/git_access.html.haml52
-rw-r--r--app/views/projects/wikis/history.html.haml17
-rw-r--r--app/views/projects/wikis/pages.html.haml13
-rw-r--r--app/views/projects/wikis/show.html.haml17
-rw-r--r--app/views/shared/_issues.html.haml2
-rw-r--r--app/views/shared/_merge_requests.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml11
-rw-r--r--changelogs/unreleased/18546-update-wiki-page-design.yml4
-rw-r--r--changelogs/unreleased/22781-user-generated-permalinks.yml4
-rw-r--r--changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml4
-rw-r--r--changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml4
-rw-r--r--changelogs/unreleased/24726-remove-across-gitlab.yml4
-rw-r--r--changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml4
-rw-r--r--changelogs/unreleased/24814-pipeline-tabs.yml4
-rw-r--r--changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml4
-rw-r--r--changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml4
-rw-r--r--changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml4
-rw-r--r--changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml4
-rw-r--r--changelogs/unreleased/boards-issue-sorting.yml4
-rw-r--r--changelogs/unreleased/clean-up-jira-service.yml4
-rw-r--r--changelogs/unreleased/events-cache-invalidation.yml4
-rw-r--r--changelogs/unreleased/fix-ca-no-date.yml4
-rw-r--r--changelogs/unreleased/fix-git-access-wiki-when-repository-feature-disabled.yml4
-rw-r--r--changelogs/unreleased/fix-github-branch-formatter.yml4
-rw-r--r--changelogs/unreleased/fixed-commit-timeago.yml4
-rw-r--r--changelogs/unreleased/refresh-authorizations-with-lease.yml4
-rw-r--r--changelogs/unreleased/rephrase-system-notes.yml4
-rw-r--r--changelogs/unreleased/resolve-discussions-timeago.yml4
-rw-r--r--changelogs/unreleased/right-sidebar-fixture.yml4
-rw-r--r--changelogs/unreleased/sh-update-sidekiq-cron.yml4
-rw-r--r--changelogs/unreleased/shortcuts-issuable-fixture.yml4
-rw-r--r--changelogs/unreleased/timeout-merge-request-for-binary-file.yml4
-rw-r--r--changelogs/unreleased/use-st-commits-where-possible.yml5
-rw-r--r--changelogs/unreleased/workhorse-v1-0-1.yml4
-rw-r--r--config/initializers/ar_monkey_patch.rb17
-rw-r--r--config/initializers/sidekiq.rb2
-rw-r--r--config/routes/project.rb1
-rw-r--r--doc/development/gotchas.md48
-rw-r--r--features/project/wiki.feature5
-rw-r--r--features/steps/project/source/markdown_render.rb6
-rw-r--r--features/steps/project/wiki.rb16
-rw-r--r--features/steps/shared/markdown.rb2
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb8
-rw-r--r--lib/gitlab/github_import/branch_formatter.rb2
-rw-r--r--spec/controllers/help_controller_spec.rb24
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/help_pages_spec.rb13
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb154
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb (renamed from spec/features/projects/pipelines_spec.rb)59
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb12
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb4
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js.es655
-rw-r--r--spec/javascripts/fixtures/issuable.html.haml2
-rw-r--r--spec/javascripts/fixtures/linked_tabs.html.haml13
-rw-r--r--spec/javascripts/fixtures/right_sidebar.html.haml17
-rw-r--r--spec/javascripts/right_sidebar_spec.js14
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js6
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb21
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb12
-rw-r--r--spec/models/build_spec.rb4
-rw-r--r--spec/models/merge_request_diff_spec.rb34
-rw-r--r--spec/models/merge_request_spec.rb33
-rw-r--r--spec/services/projects/destroy_service_spec.rb30
-rw-r--r--spec/support/matchers/have_issuable_counts.rb8
-rw-r--r--spec/support/matchers/markdown_matchers.rb6
119 files changed, 1118 insertions, 576 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12a3e63ed2e..86b30d2832d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,26 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.14.2 (2016-12-01)
+
+- Remove caching of events data. !6578
+- Rephrase some system notes to be compatible with new system note style. !7692
+- Pass tag SHA to post-receive hook when tag is created via UI. !7700
+- Prevent error when submitting a merge request and pipeline is not defined. !7707
+- Fixes system note style in commit discussion. !7721
+- Use a Redis lease for updating authorized projects. !7733
+- Refactor JiraService by moving code out of JiraService#execute method. !7756
+- Update GitLab Workhorse to v1.0.1. !7759
+- Fix pipelines info being hidden in merge request widget. !7808
+- Fixed commit timeago not rendering after initial page.
+- Fix for error thrown in cycle analytics events if build has not started.
+- Fixed issue boards issue sorting when dragging issue into list.
+- Allow access to the wiki with git when repository feature disabled.
+- Fixed timeago not rendering when resolving a discussion.
+- Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1.
+- Timeout creating and viewing merge request for binary file.
+- Gracefully recover from Redis connection failures in Sidekiq initializer.
+
## 8.14.1 (2016-11-28)
- Fix deselecting calendar days on contribution graph. !6453 (ClemMakesApps)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 76f3c6506ed..cfab4721f4b 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -57,32 +57,11 @@
(function () {
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
- window.addEventListener('hashchange', gl.utils.shiftWindow);
-
- // automatically adjust scroll position for hash urls taking the height of the navbar into account
- // https://github.com/twitter/bootstrap/issues/1768
- window.adjustScroll = function() {
- var navbar = document.querySelector('.navbar-gitlab');
- var subnav = document.querySelector('.layout-nav');
- var fixedTabs = document.querySelector('.js-tabs-affix');
-
- adjustment = 0;
- if (navbar) adjustment -= navbar.offsetHeight;
- if (subnav) adjustment -= subnav.offsetHeight;
- if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
-
- return scrollBy(0, adjustment);
- };
-
- window.addEventListener("hashchange", adjustScroll);
-
- window.onload = function () {
- // Scroll the window to avoid the topnav bar
- // https://github.com/twitter/bootstrap/issues/1768
- if (location.hash) {
- return setTimeout(adjustScroll, 100);
- }
- };
+ window.addEventListener('hashchange', gl.utils.handleLocationHash);
+ window.addEventListener('load', function onLoad() {
+ window.removeEventListener('load', onLoad, false);
+ gl.utils.handleLocationHash();
+ }, false);
$(function () {
var $body = $('body');
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6
index d5cb6164e0b..1644a772737 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js.es6
+++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6
@@ -47,7 +47,7 @@
new gl.DueDateSelectors();
new LabelsSelect();
new Sidebar();
- new Subscription('.subscription');
+ gl.Subscription.bindAll('.subscription');
}
});
})();
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 16df4b0b005..ab521c6c1fc 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -135,8 +135,18 @@
new TreeView();
}
break;
+ case 'projects:pipelines:builds':
case 'projects:pipelines:show':
- new gl.Pipelines();
+ const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
+
+ new gl.Pipelines({
+ initTabs: true,
+ tabsOptions: {
+ action: controllerAction,
+ defaultAction: 'pipelines',
+ parentEl: '.pipelines-tabs',
+ },
+ });
break;
case 'groups:activity':
new gl.Activities();
@@ -262,7 +272,7 @@
new NotificationsDropdown();
break;
case 'wikis':
- new Wikis();
+ new gl.Wikis();
shortcut_handler = new ShortcutsNavigation();
new ZenMode();
new GLForm($('.wiki-form'));
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6
index 6d9b0c4bc3e..3f12ad9ff9f 100644
--- a/app/assets/javascripts/extensions/element.js.es6
+++ b/app/assets/javascripts/extensions/element.js.es6
@@ -1,9 +1,20 @@
/* global Element */
-/* eslint-disable consistent-return, max-len */
-
-Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
+/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
if (!selectedElement) return;
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
};
+
+Element.prototype.matches = Element.prototype.matches ||
+ Element.prototype.matchesSelector ||
+ Element.prototype.mozMatchesSelector ||
+ Element.prototype.msMatchesSelector ||
+ Element.prototype.oMatchesSelector ||
+ Element.prototype.webkitMatchesSelector ||
+ function (s) {
+ const matches = (this.document || this.ownerDocument).querySelectorAll(s);
+ let i = matches.length;
+ while (--i >= 0 && matches.item(i) !== this) {}
+ return i > -1;
+ };
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6
new file mode 100644
index 00000000000..e810ee85bd3
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6
@@ -0,0 +1,113 @@
+/**
+ * Linked Tabs
+ *
+ * Handles persisting and restores the current tab selection and content.
+ * Reusable component for static content.
+ *
+ * ### Example Markup
+ *
+ * <ul class="nav-links tab-links">
+ * <li class="active">
+ * <a data-action="tab1" data-target="#tab1" data-toggle="tab" href="/path/tab1">
+ * Tab 1
+ * </a>
+ * </li>
+ * <li class="groups-tab">
+ * <a data-action="tab2" data-target="#tab2" data-toggle="tab" href="/path/tab2">
+ * Tab 2
+ * </a>
+ * </li>
+ *
+ *
+ * <div class="tab-content">
+ * <div class="tab-pane" id="tab1">
+ * Tab 1 Content
+ * </div>
+ * <div class="tab-pane" id="tab2">
+ * Tab 2 Content
+ * </div>
+ * </div>
+ *
+ *
+ * ### How to use
+ *
+ * new window.gl.LinkedTabs({
+ * action: "#{controller.action_name}",
+ * defaultAction: 'tab1',
+ * parentEl: '.tab-links'
+ * });
+ */
+
+(() => {
+ window.gl = window.gl || {};
+
+ window.gl.LinkedTabs = class LinkedTabs {
+ /**
+ * Binds the events and activates de default tab.
+ *
+ * @param {Object} options
+ */
+ constructor(options) {
+ this.options = options || {};
+
+ this.defaultAction = this.options.defaultAction;
+ this.action = this.options.action || this.defaultAction;
+
+ if (this.action === 'show') {
+ this.action = this.defaultAction;
+ }
+
+ this.currentLocation = window.location;
+
+ const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
+
+ // since this is a custom event we need jQuery :(
+ $(document)
+ .off('shown.bs.tab', tabSelector)
+ .on('shown.bs.tab', tabSelector, e => this.tabShown(e));
+
+ this.activateTab(this.action);
+ }
+
+ /**
+ * Handles the `shown.bs.tab` event to set the currect url action.
+ *
+ * @param {type} evt
+ * @return {Function}
+ */
+ tabShown(evt) {
+ const source = evt.target.getAttribute('href');
+
+ return this.setCurrentAction(source);
+ }
+
+ /**
+ * Updates the URL with the path that matched the given action.
+ *
+ * @param {String} source
+ * @return {String}
+ */
+ setCurrentAction(source) {
+ const copySource = source;
+
+ copySource.replace(/\/+$/, '');
+
+ const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
+
+ history.replaceState({
+ turbolinks: true,
+ url: newState,
+ }, document.title, newState);
+ return newState;
+ }
+
+ /**
+ * Given the current action activates the correct tab.
+ * http://getbootstrap.com/javascript/#tab-show
+ * Note: Will trigger `shown.bs.tab`
+ */
+ activateTab() {
+ return $(`${this.options.parentEl} a[data-action='${this.action}']`).tab('show');
+ }
+ };
+})();
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index d83c41fae9d..c5846068b07 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */
(function() {
(function(w) {
var base;
@@ -94,10 +94,35 @@
return $(document).off('scroll');
};
- w.gl.utils.shiftWindow = function() {
- return w.scrollBy(0, -100);
- };
+ // automatically adjust scroll position for hash urls taking the height of the navbar into account
+ // https://github.com/twitter/bootstrap/issues/1768
+ w.gl.utils.handleLocationHash = function() {
+ var hash = w.gl.utils.getLocationHash();
+ if (!hash) return;
+
+ var navbar = document.querySelector('.navbar-gitlab');
+ var subnav = document.querySelector('.layout-nav');
+ var fixedTabs = document.querySelector('.js-tabs-affix');
+ var adjustment = 0;
+ if (navbar) adjustment -= navbar.offsetHeight;
+ if (subnav) adjustment -= subnav.offsetHeight;
+
+ // scroll to user-generated markdown anchor if we cannot find a match
+ if (document.getElementById(hash) === null) {
+ var target = document.getElementById('user-content-' + hash);
+ if (target && target.scrollIntoView) {
+ target.scrollIntoView(true);
+ window.scrollBy(0, adjustment);
+ }
+ } else {
+ // only adjust for fixedTabs when not targeting user-generated content
+ if (fixedTabs) {
+ adjustment -= fixedTabs.offsetHeight;
+ }
+ window.scrollBy(0, adjustment);
+ }
+ };
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index a84db9c0233..72c6c4a1fcd 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -1,8 +1,15 @@
+//= require lib/utils/bootstrap_linked_tabs
+
/* eslint-disable */
((global) => {
class Pipelines {
- constructor() {
+ constructor(options) {
+
+ if (options.initTabs && options.tabsOptions) {
+ new global.LinkedTabs(options.tabsOptions);
+ }
+
this.addMarginToBuildColumns();
}
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
deleted file mode 100644
index 6d75688deeb..00000000000
--- a/app/assets/javascripts/subscription.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, one-var, one-var-declaration-per-line, camelcase, consistent-return, no-undef, padded-blocks, max-len */
-(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
- this.Subscription = (function() {
- function Subscription(container) {
- this.toggleSubscription = bind(this.toggleSubscription, this);
- var $container;
- this.$container = $(container);
- this.url = this.$container.attr('data-url');
- this.subscribe_button = this.$container.find('.js-subscribe-button');
- this.subscription_status = this.$container.find('.subscription-status');
- this.subscribe_button.unbind('click').click(this.toggleSubscription);
- }
-
- Subscription.prototype.toggleSubscription = function(event) {
- var action, btn, current_status;
- btn = $(event.currentTarget);
- action = btn.find('span').text();
- current_status = this.subscription_status.attr('data-status');
- btn.addClass('disabled');
-
- if ($('html').hasClass('issue-boards-page')) {
- this.url = this.$container.attr('data-url');
- }
-
- return $.post(this.url, (function(_this) {
- return function() {
- var status;
- btn.removeClass('disabled');
-
- if ($('html').hasClass('issue-boards-page')) {
- Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed);
- } else {
- status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
- _this.subscription_status.attr('data-status', status);
- action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
- btn.find('span').text(action);
- _this.subscription_status.find('>div').toggleClass('hidden');
- if (btn.attr('data-original-title')) {
- return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
- }
- }
- };
- })(this));
- };
-
- return Subscription;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/subscription.js.es6 b/app/assets/javascripts/subscription.js.es6
new file mode 100644
index 00000000000..62d1604fe9e
--- /dev/null
+++ b/app/assets/javascripts/subscription.js.es6
@@ -0,0 +1,50 @@
+/* global Vue */
+
+(() => {
+ class Subscription {
+ constructor(containerElm) {
+ this.containerElm = containerElm;
+
+ const subscribeButton = containerElm.querySelector('.js-subscribe-button');
+ if (subscribeButton) {
+ // remove class so we don't bind twice
+ subscribeButton.classList.remove('js-subscribe-button');
+ subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
+ }
+ }
+
+ toggleSubscription(event) {
+ const button = event.currentTarget;
+ const buttonSpan = button.querySelector('span');
+ if (!buttonSpan || button.classList.contains('disabled')) {
+ return;
+ }
+ button.classList.add('disabled');
+
+ const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
+ const toggleActionUrl = this.containerElm.dataset.url;
+
+ $.post(toggleActionUrl, () => {
+ button.classList.remove('disabled');
+
+ // hack to allow this to work with the issue boards Vue object
+ if (document.querySelector('html').classList.contains('issue-boards-page')) {
+ Vue.set(
+ gl.issueBoards.BoardsStore.detail.issue,
+ 'subscribed',
+ !gl.issueBoards.BoardsStore.detail.issue.subscribed,
+ );
+ } else {
+ buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
+ }
+ });
+ }
+
+ static bindAll(selector) {
+ [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
+ }
+ }
+
+ window.gl = window.gl || {};
+ window.gl.Subscription = Subscription;
+})();
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
deleted file mode 100644
index 5dd853389c2..00000000000
--- a/app/assets/javascripts/wikis.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, consistent-return, one-var, one-var-declaration-per-line, no-undef, prefer-template, padded-blocks, max-len */
-
-/*= require latinise */
-
-(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
- this.Wikis = (function() {
- function Wikis() {
- this.slugify = bind(this.slugify, this);
- $('.new-wiki-page').on('submit', (function(_this) {
- return function(e) {
- var field, path, slug;
- $('[data-error~=slug]').addClass('hidden');
- field = $('#new_wiki_path');
- slug = _this.slugify(field.val());
- if (slug.length > 0) {
- path = field.attr('data-wikis-path');
- location.href = path + '/' + slug;
- return e.preventDefault();
- }
- };
- })(this));
- }
-
- Wikis.prototype.dasherize = function(value) {
- return value.replace(/[_\s]+/g, '-');
- };
-
- Wikis.prototype.slugify = function(value) {
- return this.dasherize(value.trim().toLowerCase().latinise());
- };
-
- return Wikis;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6
new file mode 100644
index 00000000000..ecff5fd5bf4
--- /dev/null
+++ b/app/assets/javascripts/wikis.js.es6
@@ -0,0 +1,73 @@
+/* eslint-disable no-param-reassign */
+/* global Breakpoints */
+
+/*= require latinise */
+/*= require breakpoints */
+/*= require jquery.nicescroll */
+
+((global) => {
+ const dasherize = str => str.replace(/[_\s]+/g, '-');
+ const slugify = str => dasherize(str.trim().toLowerCase().latinise());
+
+ class Wikis {
+ constructor() {
+ this.bp = Breakpoints.get();
+ this.sidebarEl = document.querySelector('.js-wiki-sidebar');
+ this.sidebarExpanded = false;
+ $(this.sidebarEl).niceScroll();
+
+ const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
+ for (let i = 0; i < sidebarToggles.length; i += 1) {
+ sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
+ }
+
+ this.newWikiForm = document.querySelector('form.new-wiki-page');
+ if (this.newWikiForm) {
+ this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
+ }
+
+ window.addEventListener('resize', () => this.renderSidebar());
+ this.renderSidebar();
+ }
+
+ handleNewWikiSubmit(e) {
+ if (!this.newWikiForm) return;
+
+ const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
+ const slug = slugify(slugInput.value);
+
+ if (slug.length > 0) {
+ const wikisPath = slugInput.getAttribute('data-wikis-path');
+ window.location.href = `${wikisPath}/${slug}`;
+ e.preventDefault();
+ }
+ }
+
+ handleToggleSidebar(e) {
+ e.preventDefault();
+ this.sidebarExpanded = !this.sidebarExpanded;
+ this.renderSidebar();
+ }
+
+ sidebarCanCollapse() {
+ const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+ }
+
+ renderSidebar() {
+ if (!this.sidebarEl) return;
+ const { classList } = this.sidebarEl;
+ if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+ if (!classList.contains('right-sidebar-expanded')) {
+ classList.remove('right-sidebar-collapsed');
+ classList.add('right-sidebar-expanded');
+ }
+ } else if (classList.contains('right-sidebar-expanded')) {
+ classList.add('right-sidebar-collapsed');
+ classList.remove('right-sidebar-expanded');
+ }
+ }
+ }
+
+ global.Wikis = Wikis;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 8642b7530e2..81852158b94 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -2,7 +2,7 @@
padding-left: 0;
padding-right: 0;
- @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
+ @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
overflow-x: scroll;
}
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index a9006de6d3e..eadb9409fee 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -38,7 +38,7 @@
}
}
-@media (max-width: $screen-md-min) {
+@media (max-width: $screen-sm-max) {
ul.notes {
.flash-container.timeline-content {
margin-left: 0;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index e83a1f7ad68..a01899ccbd2 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -98,7 +98,7 @@ label {
}
}
- @media(max-width: $screen-sm-min) {
+ @media(max-width: $screen-xs-max) {
padding: 0 $gl-padding;
.control-label,
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 91ab1503439..c5e5dad574d 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -21,7 +21,6 @@
background: $color-darker;
}
- .sidebar-header,
.sidebar-action-buttons {
color: $color-light;
background-color: lighten($color-darker, 5%);
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 16ecf466931..f9bcbbf2ca5 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -228,7 +228,7 @@ header {
}
.page-sidebar-pinned.right-sidebar-expanded {
- @media (max-width: $screen-lg-min) {
+ @media (max-width: $screen-md-max) {
.header-content .title {
width: 300px;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 1839ffa0976..98f72e58c23 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -123,7 +123,7 @@
line-height: 28px;
/* Small devices (phones, tablets, 768px and lower) */
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
width: 100%;
}
}
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index cb2c351c368..b37c1d0d670 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -43,7 +43,7 @@
/**
* Small screen pagination
*/
-@media (max-width: $screen-xs) {
+@media (max-width: $screen-xs-min) {
.gl-pagination {
.pagination li a {
padding: 6px 10px;
@@ -62,7 +62,7 @@
/**
* Medium screen pagination
*/
-@media (min-width: $screen-xs) and (max-width: $screen-md-max) {
+@media (min-width: $screen-xs-min) and (max-width: $screen-md-max) {
.gl-pagination {
.page {
display: none;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 44c445c0543..4269d365578 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -59,11 +59,6 @@
padding: 0 !important;
}
- .sidebar-header {
- padding: 11px 22px 12px;
- font-size: 20px;
- }
-
li {
&.separate-item {
padding-top: 10px;
@@ -220,7 +215,7 @@ header.header-sidebar-pinned {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
- &:not(.build-sidebar) {
+ &:not(.build-sidebar):not(.wiki-sidebar) {
padding-right: $sidebar_collapsed_width;
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 070e42d63d2..aa604b1cd19 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -182,6 +182,7 @@
left: -16px;
position: absolute;
text-decoration: none;
+ outline: none;
&::after {
content: image-url('icon_anchor.svg');
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 4327f8bf640..82f36f24867 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -325,7 +325,6 @@
}
.issuable-header-text {
- width: 100%;
padding-right: 35px;
> strong {
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 498a8f68e49..0b4930ec98f 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -53,7 +53,7 @@
border-bottom: none;
position: relative;
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
padding: 6px 0 24px;
}
}
@@ -61,7 +61,7 @@
.column {
text-align: center;
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
padding: 15px 0;
}
@@ -78,7 +78,7 @@
}
&:last-child {
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
text-align: center;
}
}
@@ -156,7 +156,7 @@
}
.inner-content {
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
padding: 0 28px;
text-align: center;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 4b382e8adaf..de3d2ba549f 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -8,7 +8,7 @@
font-size: 34px;
}
-@media (max-width: $screen-sm-min) {
+@media (max-width: $screen-xs-max) {
.environments-container {
width: 100%;
overflow: auto;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 57d028cec8c..a9af7af59e2 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -49,14 +49,14 @@
padding: 50px 100px;
overflow: hidden;
- @media (max-width: $screen-md-min) {
+ @media (max-width: $screen-sm-max) {
padding: 50px 0;
}
svg {
float: right;
- @media (max-width: $screen-md-min) {
+ @media (max-width: $screen-sm-max) {
float: none;
display: block;
width: 250px;
@@ -71,7 +71,7 @@
width: 460px;
margin-top: 120px;
- @media (max-width: $screen-md-min) {
+ @media (max-width: $screen-sm-max) {
float: none;
margin-top: 60px;
width: auto;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 8843d1463db..dfc6079bd15 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -123,7 +123,7 @@
padding: 20px 0;
}
-@media (max-width: $screen-sm-min) {
+@media (max-width: $screen-xs-max) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 16ddef481bd..65775c45e5b 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -111,7 +111,7 @@
text-align: center;
font-size: 13px;
- @media (max-width: $screen-md-min) {
+ @media (max-width: $screen-sm-max) {
// On smaller devices the warning becomes the fourth item in the list,
// rather than centering, and grows to span the full width of the
// comment area.
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 15ec8be831e..56a798a7b6d 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -253,7 +253,7 @@ ul.notes {
}
.page-sidebar-pinned.right-sidebar-expanded {
- @media (max-width: $screen-lg-min) {
+ @media (max-width: $screen-md-max) {
.note-header {
.note-headline-light {
display: block;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 6fab97a71aa..f8677f93fe0 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -180,7 +180,7 @@
.modal-dialog {
width: 380px;
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
width: auto;
}
@@ -261,4 +261,4 @@ table.u2f-registrations {
td:not(:last-child) {
border-right: solid 1px transparent;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 0562ee7b178..1cf7587dbb4 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -189,7 +189,7 @@
}
.download-button {
- @media (max-width: $screen-lg-min) {
+ @media (max-width: $screen-md-max) {
margin-left: 0;
}
}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 69288b31cc4..779db77da60 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -37,7 +37,7 @@
@include make-md-column(6);
margin-top: 10px;
- @media (max-width: $screen-sm-min) {
+ @media (max-width: $screen-xs-max) {
width: 100%;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 2b836fa1f4a..20ad63be045 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -31,7 +31,7 @@
.last-commit {
@include str-truncated(506px);
- @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) {
+ @media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
@include str-truncated(450px);
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index dfaeba41cf6..b9f81533150 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,3 +4,128 @@
margin-right: auto;
padding-right: 7px;
}
+
+.wiki-page-header {
+ @extend .top-area;
+ position: relative;
+
+ .wiki-page-title {
+ margin: 0;
+ font-size: 22px;
+ }
+
+ .wiki-last-edit-by {
+ color: $gl-gray-light;
+
+ strong {
+ color: $gl-text-color;
+ }
+ }
+
+ .light {
+ font-weight: normal;
+ color: $gl-gray-light;
+ }
+
+ .git-access-header {
+ padding: 16px 40px 11px 0;
+ line-height: 28px;
+ font-size: 18px;
+ }
+
+ .git-clone-holder {
+ width: 100%;
+ padding-bottom: 40px;
+ }
+
+ button.sidebar-toggle {
+ position: absolute;
+ right: 0;
+ top: 11px;
+ display: block;
+ }
+
+ @media (min-width: $screen-sm-min) {
+ &.has-sidebar-toggle {
+ padding-right: 40px;
+ }
+
+ .git-clone-holder {
+ width: 480px;
+ }
+
+ .nav-controls {
+ width: auto;
+ min-width: 50%;
+ white-space: nowrap;
+ }
+ }
+
+ @media (min-width: $screen-md-min) {
+ &.has-sidebar-toggle {
+ padding-right: 0;
+ }
+
+ button.sidebar-toggle {
+ display: none;
+ }
+ }
+}
+
+.wiki-git-access {
+ margin: $gl-padding 0;
+
+ h3 {
+ font-size: 22px;
+ font-weight: normal;
+ margin-top: 1.4em;
+ }
+}
+
+.right-sidebar.wiki-sidebar {
+ padding: $gl-padding 0;
+
+ &.right-sidebar-collapsed {
+ display: none;
+ }
+
+ .blocks-container {
+ padding: 0 $gl-padding;
+ }
+
+ .block {
+ width: 100%;
+ }
+
+ a {
+ color: $layout-link-gray;
+
+ &:hover,
+ &.active {
+ color: $black;
+ }
+ }
+
+ .active > a {
+ color: $black;
+ }
+
+ ul.wiki-pages,
+ ul.wiki-pages li {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ ul.wiki-pages li {
+ margin: 5px 0 10px;
+ }
+
+ .wiki-sidebar-header {
+ padding: 0 $gl-padding $gl-padding;
+
+ .gutter-toggle {
+ margin-top: 0;
+ }
+ }
+}
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index a10cdcce72b..37feff79999 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -7,8 +7,10 @@ class HelpController < ApplicationController
@help_index = File.read(Rails.root.join('doc', 'README.md'))
# Prefix Markdown links with `help/` unless they are external links
- # See http://rubular.com/r/MioSrVLK3S
- @help_index.gsub!(%r{(\]\()(?!.+://)([^\)\(]+\))}, '\1/help/\2')
+ # See http://rubular.com/r/X3baHTbPO2
+ @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do
+ "#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}"
+ end
end
def show
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f47df8b623b..d2cef52842c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -492,7 +492,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def validates_merge_request
# Show git not found page
# if there is no saved commits between source & target branch
- if @merge_request.commits.blank?
+ if @merge_request.has_no_commits?
# and if target branch doesn't exist
return invalid_mr unless @merge_request.target_branch_exists?
end
@@ -500,7 +500,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
@noteable = @merge_request
- @commits_count = @merge_request.commits.count
+ @commits_count = @merge_request.commits_count
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 533af80aee0..85188cfdd4c 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -1,6 +1,6 @@
class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create]
- before_action :commit, only: [:show]
+ before_action :commit, only: [:show, :builds]
before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
@@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController
def show
end
+ def builds
+ respond_to do |format|
+ format.html do
+ render 'show'
+ end
+ end
+ end
+
def retry
pipeline.retry_failed(current_user)
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 177ccf5eec9..c3353446fd1 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -115,6 +115,8 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
+
+ @sidebar_wiki_pages = @project_wiki.pages.first(15)
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9a74e36870b..001c83ccb4b 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -49,6 +49,32 @@ class IssuableFinder
execute.find_by(*params)
end
+ # We often get counts for each state by running a query per state, and
+ # counting those results. This is typically slower than running one query
+ # (even if that query is slower than any of the individual state queries) and
+ # grouping and counting within that query.
+ #
+ def count_by_state
+ count_params = params.merge(state: nil, sort: nil)
+ labels_count = label_names.any? ? label_names.count : 1
+ finder = self.class.new(current_user, count_params)
+ counts = Hash.new(0)
+
+ # Searching by label includes a GROUP BY in the query, but ours will be last
+ # because it is added last. Searching by multiple labels also includes a row
+ # per issuable, so we have to count those in Ruby - which is bad, but still
+ # better than performing multiple queries.
+ #
+ finder.execute.reorder(nil).group(:state).count.each do |key, value|
+ counts[Array(key).last.to_sym] += value / labels_count
+ end
+
+ counts[:all] = counts.values.sum
+ counts[:opened] += counts[:reopened]
+
+ counts
+ end
+
def group
return @group if defined?(@group)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 6584aa3edd5..8231f8fa334 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -180,12 +180,9 @@ module IssuablesHelper
end
def issuables_count_for_state(issuable_type, state)
- issuables_finder = public_send("#{issuable_type}_finder")
-
- params = issuables_finder.params.merge(state: state)
- finder = issuables_finder.class.new(issuables_finder.current_user, params)
-
- finder.execute.page(1).total_count
+ @counts ||= {}
+ @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
+ @counts[issuable_type][state]
end
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
@@ -195,6 +192,7 @@ module IssuablesHelper
opts = params.with_indifferent_access
opts[:state] = state
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+ opts.delete_if { |_, value| value.blank? }
hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index df87fac132d..a3331dc80cb 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -20,6 +20,11 @@ module NavHelper
end
elsif current_path?('builds#show')
"page-gutter build-sidebar right-sidebar-expanded"
+ elsif current_path?('wikis#show') ||
+ current_path?('wikis#edit') ||
+ current_path?('wikis#history') ||
+ current_path?('wikis#git_access')
+ "page-gutter wiki-sidebar right-sidebar-expanded"
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e7d33bd26db..88c46076df6 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -203,7 +203,7 @@ module Ci
.reorder(iid: :asc)
merge_requests.find do |merge_request|
- merge_request.commits.any? { |ci| ci.id == pipeline.sha }
+ merge_request.commits_sha.include?(pipeline.sha)
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 64990f8134e..bfb016df46d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -22,7 +22,8 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
- delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
+ delegate :commits, :real_size, :commits_sha, :commits_count,
+ to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -662,7 +663,7 @@ class MergeRequest < ActiveRecord::Base
end
def broken?
- self.commits.blank? || branch_missing? || cannot_be_merged?
+ has_no_commits? || branch_missing? || cannot_be_merged?
end
def can_be_merged_by?(user)
@@ -770,10 +771,6 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
- def commits_sha
- commits.map(&:sha)
- end
-
def head_pipeline
return unless diff_head_sha && source_project
@@ -871,4 +868,12 @@ class MergeRequest < ActiveRecord::Base
@conflicts_can_be_resolved_in_ui = false
end
end
+
+ def has_commits?
+ commits_count > 0
+ end
+
+ def has_no_commits?
+ !has_commits?
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 58a24eb84cb..b8f36a2c958 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -127,11 +127,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_sha
- if @commits
- commits.map(&:sha)
- else
- st_commits.map { |commit| commit[:id] }
- end
+ st_commits.map { |commit| commit[:id] }
end
def diff_refs
@@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
end
+ def commits_count
+ st_commits.count
+ end
+
private
# Old GitLab implementations may have generated diffs as ["--broken-diff"].
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 22596b4014a..e4056306bc4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -63,7 +63,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff
else
- mr_commit_ids = merge_request.commits.map(&:id)
+ mr_commit_ids = merge_request.commits_sha
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff if matches.any?
@@ -123,7 +123,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- mr_commit_ids = Set.new(merge_request.commits.map(&:id))
+ mr_commit_ids = Set.new(merge_request.commits_sha)
new_commits, existing_commits = @commits.partition do |commit|
mr_commit_ids.include?(commit.id)
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 2a6d9cda379..817e4bebb05 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,5 +1,4 @@
.nav-sidebar
- .sidebar-header Across GitLab
%ul.nav
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml
index 21c9563e9db..a08c7f2af09 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml
@@ -1,11 +1,7 @@
- if current_user
.block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
- .title
+ %span.issuable-header-text.hide-collapsed.pull-left
Notifications
- %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
- {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }}
- .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" }
- .unsubscribed{ "v-show" => "!issue.subscribed" }
- You're not receiving notifications from this thread.
- .subscribed{ "v-show" => "issue.subscribed" }
- You're receiving notifications because you're subscribed to this thread.
+ %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
+ %span
+ {{issue.subscribed ? 'Unsubscribe' : 'Subscribe'}}
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index eab48b78cb3..5cc92595fe0 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -27,7 +27,7 @@
version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small
- #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
+ #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
= time_ago_with_tooltip(merge_request_diff.created_at)
- if @merge_request_diff.base_commit_sha
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 20c93930abc..eee711dc5af 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -11,7 +11,7 @@
= render 'projects/merge_requests/widget/open/archived'
- elsif @merge_request.branch_missing?
= render 'projects/merge_requests/widget/open/missing_branch'
- - elsif @merge_request.commits.blank?
+ - elsif @merge_request.has_no_commits?
= render 'projects/merge_requests/widget/open/nothing'
- elsif @merge_request.unchecked?
= render 'projects/merge_requests/widget/open/check'
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 718314701f9..3464e155a1b 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -1,14 +1,17 @@
.tabs-holder
- %ul.nav-links.no-top.no-bottom
- %li.active
- = link_to "Pipeline", "#js-tab-pipeline", data: { target: '#js-tab-pipeline', action: 'pipeline', toggle: 'tab' }, class: 'pipeline-tab'
- %li
- = link_to "#js-tab-builds", data: { target: '#js-tab-builds', action: 'build', toggle: 'tab' }, class: 'builds-tab' do
+ %ul.pipelines-tabs.nav-links.no-top.no-bottom
+ %li.js-pipeline-tab-link
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
+ Pipeline
+ %li.js-builds-tab-link
+ = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
Builds
- %span.badge= pipeline.statuses.count
+ %span.badge.js-builds-counter= pipeline.statuses.count
+
+
.tab-content
- #js-tab-pipeline.tab-pane.active
+ #js-tab-pipeline.tab-pane
.build-content.middle-block.pipeline-graph
.pipeline-visualization
%ul.stage-column-list
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 8c6652a5f90..29a41bc664b 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -2,7 +2,7 @@
- page_title "Pipeline"
= render "projects/pipelines/head"
-%div{ class: container_class }
+%div.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
- if @commit
= render "projects/pipelines/info"
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 4e41a15d9f4..c52527332bc 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -34,9 +34,6 @@
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
.pull-right
- - if can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do
- Delete
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
- else
= f.submit 'Create page', class: "btn-create btn"
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
deleted file mode 100644
index afdef70e1cf..00000000000
--- a/app/views/projects/wikis/_nav.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
-
- = nav_link(path: 'wikis#pages') do
- = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project)
-
- = nav_link(path: 'wikis#git_access') do
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- Git Access
-
- = render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
new file mode 100644
index 00000000000..cad9c15a49e
--- /dev/null
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -0,0 +1,23 @@
+%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar
+ .block.wiki-sidebar-header.append-bottom-default
+ %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
+ = icon('angle-double-right')
+
+ - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project)
+ = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
+ = succeed '&nbsp;' do
+ = icon('cloud-download')
+ Clone repository
+
+ .blocks-container
+ .block.block-first
+ %ul.wiki-pages
+ - @sidebar_wiki_pages.each do |wiki_page|
+ %li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
+ = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+ = wiki_page.title.capitalize
+ .block
+ = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
+ More Pages
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 679d6018bef..8cf018da1b7 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,23 +1,35 @@
- @no_container = true
- page_title "Edit", @page.title.capitalize, "Wiki"
-= render 'nav'
%div{ class: container_class }
- .top-area
+ .wiki-page-header.has-sidebar-toggle
+ %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+ = icon('angle-double-left')
+
.nav-text
- %strong
+ %h2.wiki-page-title
- if @page.persisted?
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
- else
= @page.title.capitalize
- %span.light
- &middot;
- Edit Page
+ %span.light
+ &middot;
+ - if @page.persisted?
+ Edit Page
+ - else
+ Create Page
.nav-controls
- - if !(@page && @page.persisted?)
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- New Page
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ New Page
+ - if @page.persisted?
+ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+ Page History
+ - if can?(current_user, :admin_wiki, @project)
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
+ Delete
= render 'form'
+
+= render 'sidebar'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index b8811a28dd6..e25d6a48573 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,34 +1,36 @@
- @no_container = true
- page_title "Git Access", "Wiki"
-= render 'nav'
%div{ class: container_class }
- .sub-header-block
- %span.oneline
- Git access for
+ .wiki-page-header.has-sidebar-toggle
+ %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+ = icon('angle-double-left')
+
+ .git-access-header
+ Clone repository
%strong= @project_wiki.path_with_namespace
- .pull-right
- = render "shared/clone_panel", project: @project_wiki
+ = render "shared/clone_panel", project: @project_wiki
+
+ .wiki-git-access
+ %h3 Install Gollum
+ %pre.dark
+ :preserve
+ gem install gollum
- .prepend-top-default
- %fieldset
- %legend Install Gollum:
- %pre.dark
- :preserve
- gem install gollum
+ %h3 Clone your wiki
+ %pre.dark
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
+ cd #{h @project_wiki.path}
- %legend Clone Your Wiki:
- %pre.dark
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
- cd #{h @project_wiki.path}
+ %h3 Start Gollum and edit locally
+ %pre.dark
+ :preserve
+ gollum
+ == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
+ >> Thin web server (v1.5.0 codename Knife)
+ >> Maximum connections set to 1024
+ >> Listening on 0.0.0.0:4567, CTRL+C to stop
- %legend Start Gollum And Edit Locally:
- %pre.dark
- :preserve
- gollum
- == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
- >> Thin web server (v1.5.0 codename Knife)
- >> Maximum connections set to 1024
- >> Listening on 0.0.0.0:4567, CTRL+C to stop
+= render 'sidebar'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 4c0b14e2c42..dd7213622c1 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,13 +1,16 @@
- page_title "History", @page.title.capitalize, "Wiki"
-= render 'nav'
+
%div{ class: container_class }
- .top-area
+ .wiki-page-header.has-sidebar-toggle
+ %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+ = icon('angle-double-left')
+
.nav-text
- %strong
+ %h2.wiki-page-title
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
- %span.light
- &middot;
- History
+ %span.light
+ &middot;
+ History
.table-holder
%table.table
@@ -35,3 +38,5 @@
%td
%strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+
+= render 'sidebar'
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 9c10acd4cb6..e1eaffc6884 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,9 +1,18 @@
- @no_container = true
- page_title "Pages", "Wiki"
-= render 'nav'
-
%div{ class: container_class }
+ .wiki-page-header
+
+ .nav-text
+ %h2.wiki-page-title
+ Wiki Pages
+
+ .nav-controls
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do
+ = icon('cloud-download')
+ Clone repository
+
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 5cebb538cf5..1b6dceee241 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,15 +1,19 @@
- @no_container = true
- page_title @page.title.capitalize, "Wiki"
-= render 'nav'
%div{ class: container_class }
- .top-area
+ .wiki-page-header.has-sidebar-toggle
+ %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+ = icon('angle-double-left')
+
.nav-text
- %strong= @page.title.capitalize
+ %h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
- &middot;
- last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ Last edited by
+ %strong
+ #{@page.commit.author.name}
+ #{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls
= render 'main_links'
@@ -19,8 +23,9 @@
This is an old version of this page.
You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
-
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
= preserve do
= render_wiki_content(@page)
+
+= render 'sidebar'
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index baa6d5f8206..26b349e8a62 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,4 +1,4 @@
-- if @issues.reorder(nil).any?
+- if @issues.to_a.any?
- @issues.group_by(&:project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index ca3178395c1..2f3605b4d27 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,4 +1,4 @@
-- if @merge_requests.reorder(nil).any?
+- if @merge_requests.to_a.any?
- @merge_requests.group_by(&:target_project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 02427650219..958f8413e1d 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -144,16 +144,11 @@
.block.light.subscription{data: {url: toggle_subscription_path(issuable)}}
.sidebar-collapsed-icon
= icon('rss')
- .title.hide-collapsed
+ %span.issuable-header-text.hide-collapsed.pull-left
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
+ %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
- .subscription-status.hide-collapsed{data: {status: subscribtion_status}}
- .unsubscribed{class: ( 'hidden' if subscribed )}
- You're not receiving notifications from this thread.
- .subscribed{class: ( 'hidden' unless subscribed )}
- You're receiving notifications because you're subscribed to this thread.
- project_ref = cross_project_reference(@project, issuable)
.block.project-reference
@@ -170,6 +165,6 @@
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
- new Subscription('.subscription')
+ gl.Subscription.bindAll('.subscription');
new gl.DueDateSelectors();
sidebar = new Sidebar();
diff --git a/changelogs/unreleased/18546-update-wiki-page-design.yml b/changelogs/unreleased/18546-update-wiki-page-design.yml
new file mode 100644
index 00000000000..c76e17340f2
--- /dev/null
+++ b/changelogs/unreleased/18546-update-wiki-page-design.yml
@@ -0,0 +1,4 @@
+---
+title: Update wiki page design
+merge_request: 7429
+author:
diff --git a/changelogs/unreleased/22781-user-generated-permalinks.yml b/changelogs/unreleased/22781-user-generated-permalinks.yml
new file mode 100644
index 00000000000..e46739e48e3
--- /dev/null
+++ b/changelogs/unreleased/22781-user-generated-permalinks.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent DOM ID collisions resulting from user-generated content anchors
+merge_request: 7631
+author:
diff --git a/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml
new file mode 100644
index 00000000000..2227c81bd34
--- /dev/null
+++ b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml
@@ -0,0 +1,4 @@
+---
+title: Remove the help text under the sidebar subscribe button and style it inline
+merge_request: 7389
+author:
diff --git a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml
new file mode 100644
index 00000000000..01b19a47ecd
--- /dev/null
+++ b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml
@@ -0,0 +1,4 @@
+---
+title: Speed up issuable dashboards
+merge_request:
+author:
diff --git a/changelogs/unreleased/24726-remove-across-gitlab.yml b/changelogs/unreleased/24726-remove-across-gitlab.yml
new file mode 100644
index 00000000000..6436e4b688f
--- /dev/null
+++ b/changelogs/unreleased/24726-remove-across-gitlab.yml
@@ -0,0 +1,4 @@
+---
+title: 24726 Remove Across GitLab from side navigation
+merge_request:
+author:
diff --git a/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml b/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml
deleted file mode 100644
index 9254db40742..00000000000
--- a/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Pass tag SHA to post-receive hook when tag is created via UI
-merge_request: 7700
-author:
diff --git a/changelogs/unreleased/24814-pipeline-tabs.yml b/changelogs/unreleased/24814-pipeline-tabs.yml
new file mode 100644
index 00000000000..f85e7576905
--- /dev/null
+++ b/changelogs/unreleased/24814-pipeline-tabs.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Cicking on tabs on pipeline page should set URL
+merge_request: 7709
+author:
diff --git a/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml b/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml
deleted file mode 100644
index 4b4aea79380..00000000000
--- a/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent error when submitting a merge request and pipeline is not defined
-merge_request: 7707
-author:
diff --git a/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml b/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml
deleted file mode 100644
index 7ddf0b46d4c..00000000000
--- a/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes system note style in commit discussion
-merge_request: 7721
-author:
diff --git a/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml b/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml
deleted file mode 100644
index dad9db0ffef..00000000000
--- a/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix pipelines info being hidden in merge request widget
-merge_request: 7808
-author:
diff --git a/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml
new file mode 100644
index 00000000000..58efd9113f2
--- /dev/null
+++ b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml
@@ -0,0 +1,4 @@
+---
+title: Don't change relative URLs to absolute URLs in the Help page
+merge_request:
+author:
diff --git a/changelogs/unreleased/boards-issue-sorting.yml b/changelogs/unreleased/boards-issue-sorting.yml
deleted file mode 100644
index fb7dc2f9190..00000000000
--- a/changelogs/unreleased/boards-issue-sorting.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed issue boards issue sorting when dragging issue into list
-merge_request:
-author:
diff --git a/changelogs/unreleased/clean-up-jira-service.yml b/changelogs/unreleased/clean-up-jira-service.yml
deleted file mode 100644
index a309cb57532..00000000000
--- a/changelogs/unreleased/clean-up-jira-service.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Refactor JiraService by moving code out of JiraService#execute method
-merge_request: 7756
-author:
diff --git a/changelogs/unreleased/events-cache-invalidation.yml b/changelogs/unreleased/events-cache-invalidation.yml
deleted file mode 100644
index 2b30f4dcbce..00000000000
--- a/changelogs/unreleased/events-cache-invalidation.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove caching of events data
-merge_request: 6578
-author:
diff --git a/changelogs/unreleased/fix-ca-no-date.yml b/changelogs/unreleased/fix-ca-no-date.yml
deleted file mode 100644
index 6de4a56ac0d..00000000000
--- a/changelogs/unreleased/fix-ca-no-date.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix for error thrown in cycle analytics events if build has not started
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-git-access-wiki-when-repository-feature-disabled.yml b/changelogs/unreleased/fix-git-access-wiki-when-repository-feature-disabled.yml
deleted file mode 100644
index 82ca6316876..00000000000
--- a/changelogs/unreleased/fix-git-access-wiki-when-repository-feature-disabled.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow access to the wiki with git when repository feature disabled
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-github-branch-formatter.yml b/changelogs/unreleased/fix-github-branch-formatter.yml
new file mode 100644
index 00000000000..c8698f507de
--- /dev/null
+++ b/changelogs/unreleased/fix-github-branch-formatter.yml
@@ -0,0 +1,4 @@
+---
+title: Fix branch validation for GitHub PR where repo/fork was renamed/deleted
+merge_request:
+author:
diff --git a/changelogs/unreleased/fixed-commit-timeago.yml b/changelogs/unreleased/fixed-commit-timeago.yml
deleted file mode 100644
index 295d8db63d0..00000000000
--- a/changelogs/unreleased/fixed-commit-timeago.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed commit timeago not rendering after initial page
-merge_request:
-author:
diff --git a/changelogs/unreleased/refresh-authorizations-with-lease.yml b/changelogs/unreleased/refresh-authorizations-with-lease.yml
deleted file mode 100644
index bb9b77018e3..00000000000
--- a/changelogs/unreleased/refresh-authorizations-with-lease.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use a Redis lease for updating authorized projects
-merge_request: 7733
-author:
diff --git a/changelogs/unreleased/rephrase-system-notes.yml b/changelogs/unreleased/rephrase-system-notes.yml
deleted file mode 100644
index e77c3a31cb4..00000000000
--- a/changelogs/unreleased/rephrase-system-notes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rephrase some system notes to be compatible with new system note style
-merge_request: 7692
-author:
diff --git a/changelogs/unreleased/resolve-discussions-timeago.yml b/changelogs/unreleased/resolve-discussions-timeago.yml
deleted file mode 100644
index ffedeb93f1d..00000000000
--- a/changelogs/unreleased/resolve-discussions-timeago.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed timeago not rendering when resolving a discussion
-merge_request:
-author:
diff --git a/changelogs/unreleased/right-sidebar-fixture.yml b/changelogs/unreleased/right-sidebar-fixture.yml
new file mode 100644
index 00000000000..46a3e459fef
--- /dev/null
+++ b/changelogs/unreleased/right-sidebar-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for right_sidebar_spec
+merge_request: 7687
+author: winniehell
diff --git a/changelogs/unreleased/sh-update-sidekiq-cron.yml b/changelogs/unreleased/sh-update-sidekiq-cron.yml
deleted file mode 100644
index d79ba817a18..00000000000
--- a/changelogs/unreleased/sh-update-sidekiq-cron.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1
-merge_request:
-author:
diff --git a/changelogs/unreleased/shortcuts-issuable-fixture.yml b/changelogs/unreleased/shortcuts-issuable-fixture.yml
new file mode 100644
index 00000000000..88945600886
--- /dev/null
+++ b/changelogs/unreleased/shortcuts-issuable-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for shortcuts_issuable_spec
+merge_request: 7685
+author: winniehell
diff --git a/changelogs/unreleased/timeout-merge-request-for-binary-file.yml b/changelogs/unreleased/timeout-merge-request-for-binary-file.yml
deleted file mode 100644
index 5161265d1bd..00000000000
--- a/changelogs/unreleased/timeout-merge-request-for-binary-file.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Timeout creating and viewing merge request for binary file
-merge_request:
-author:
diff --git a/changelogs/unreleased/use-st-commits-where-possible.yml b/changelogs/unreleased/use-st-commits-where-possible.yml
new file mode 100644
index 00000000000..e4395461560
--- /dev/null
+++ b/changelogs/unreleased/use-st-commits-where-possible.yml
@@ -0,0 +1,5 @@
+---
+title: Replace references to MergeRequestDiff#commits with st_commits when we care
+ only about the number of commits
+merge_request: 7668
+author:
diff --git a/changelogs/unreleased/workhorse-v1-0-1.yml b/changelogs/unreleased/workhorse-v1-0-1.yml
deleted file mode 100644
index c26c2d45b1d..00000000000
--- a/changelogs/unreleased/workhorse-v1-0-1.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update GitLab Workhorse to v1.0.1
-merge_request: 7759
-author:
diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb
index 0da584626ee..6979f4641b0 100644
--- a/config/initializers/ar_monkey_patch.rb
+++ b/config/initializers/ar_monkey_patch.rb
@@ -52,6 +52,23 @@ module ActiveRecord
raise
end
end
+
+ # This is patched because we need it to query `lock_version IS NULL`
+ # rather than `lock_version = 0` whenever lock_version is NULL.
+ def relation_for_destroy
+ return super unless locking_enabled?
+
+ column_name = self.class.locking_column
+ super.where(self.class.arel_table[column_name].eq(self[column_name]))
+ end
+ end
+
+ # This is patched because we want `lock_version` default to `NULL`
+ # rather than `0`
+ class LockingType < SimpleDelegator
+ def type_cast_from_database(value)
+ super
+ end
end
end
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index b87b31d9697..1d7a3f03ace 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -61,5 +61,5 @@ begin
end
end
end
-rescue Redis::BaseError, SocketError
+rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 1336484a399..0754f0ec3b0 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -129,6 +129,7 @@ constraints(ProjectUrlConstrainer.new) do
member do
post :cancel
post :retry
+ get :builds
end
end
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 7bfc9cb361f..0f78e8238af 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -141,51 +141,3 @@ in an initializer._
### Further reading
- Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting)
-
-## ID-based CSS selectors need to be a bit more specific
-
-Normally, because HTML `id` attributes need to be unique to the page, it's
-perfectly fine to write some JavaScript like the following:
-
-```javascript
-$('#js-my-selector').hide();
-```
-
-However, there's a feature of GitLab's Markdown processing that [automatically
-adds anchors to header elements][ToC Processing], with the `id` attribute being
-automatically generated based on the content of the header.
-
-Unfortunately, this feature makes it possible for user-generated content to
-create a header element with the same `id` attribute we're using in our
-selector, potentially breaking the JavaScript behavior. A user could break the
-above example with the following Markdown:
-
-```markdown
-## JS My Selector
-```
-
-Which gets converted to the following HTML:
-
-```html
-<h2>
- <a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a>
- JS My Selector
-</h2>
-```
-
-[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37
-
-### Solution
-
-The current recommended fix for this is to make our selectors slightly more
-specific:
-
-```javascript
-$('div#js-my-selector').hide();
-```
-
-### Further reading
-
-- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908)
-- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023)
-- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024)
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index 63ce3ccb536..a04228de03b 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -49,7 +49,6 @@ Feature: Project Wiki
Scenario: View all pages
Given I have an existing wiki page
And I browse to that Wiki page
- And I click on the "Pages" button
Then I should see the existing page in the pages list
Scenario: File exists in wiki repo
@@ -72,13 +71,11 @@ Feature: Project Wiki
@javascript
Scenario: New Wiki page that has a path
Given I create a New page with paths
- And I click on the "Pages" button
Then I should see non-escaped link in the pages list
@javascript
Scenario: Edit Wiki page that has a path
Given I create a New page with paths
- And I click on the "Pages" button
And I edit the Wiki page with a path
Then I should see a non-escaped path
And I should see the Editing page
@@ -88,7 +85,6 @@ Feature: Project Wiki
@javascript
Scenario: View the page history of a Wiki page that has a path
Given I create a New page with paths
- And I click on the "Pages" button
And I view the page history of a Wiki page that has a path
Then I should see a non-escaped path
And I should see the page history
@@ -96,7 +92,6 @@ Feature: Project Wiki
@javascript
Scenario: View an old page version of a Wiki page
Given I create a New page with paths
- And I click on the "Pages" button
And I edit the Wiki page with a path
Then I should see a non-escaped path
And I should see the Editing page
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 2134dae168a..dee6a8a5558 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -241,7 +241,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
page.within(:css, ".nav-text") do
expect(page).to have_content "Test"
- expect(page).to have_content "Edit Page"
+ expect(page).to have_content "Create Page"
end
end
@@ -258,7 +258,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
page.within(:css, ".nav-text") do
- expect(page).to have_content "Edit"
+ expect(page).to have_content "Create"
expect(page).to have_content "Api"
end
end
@@ -271,7 +271,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
page.within(:css, ".nav-text") do
- expect(page).to have_content "Edit"
+ expect(page).to have_content "Create"
expect(page).to have_content "Rake"
end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 07a955b1a14..4cb0a21fbb4 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
expect(page).to have_content "link test"
click_link "link test"
- expect(page).to have_content "Edit Page"
+ expect(page).to have_content "Create Page"
end
step 'I have an existing Wiki page' do
@@ -80,13 +80,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
expect(page).to have_content "Page was successfully deleted"
end
- step 'I click on the "Pages" button' do
- click_on "Pages"
- end
-
step 'I should see the existing page in the pages list' do
expect(page).to have_content current_user.name
- expect(page).to have_content @page.title
+ expect(find('.wiki-pages')).to have_content @page.title.capitalize
end
step 'I have an existing Wiki page with images linked on page' do
@@ -125,7 +121,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I should see the new wiki page form' do
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Edit Page')
+ expect(page).to have_content('Create Page')
end
step 'I create a New page with paths' do
@@ -142,8 +138,8 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I edit the Wiki page with a path' do
- expect(page).to have_content('three')
- click_on 'three'
+ expect(find('.wiki-pages')).to have_content('Three')
+ click_on 'Three'
expect(find('.nav-text')).to have_content('Three')
click_on 'Edit'
end
@@ -157,7 +153,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I view the page history of a Wiki page that has a path' do
- click_on 'three'
+ click_on 'Three'
click_on 'Page History'
end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 56b36f7c46c..a036d9b884f 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -2,7 +2,7 @@ module SharedMarkdown
include Spinach::DSL
def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki")
- node = find("#{parent} h#{level} a##{id}")
+ node = find("#{parent} h#{level} a#user-content-#{id}")
expect(node[:href]).to eq "##{id}"
# Work around a weird Capybara behavior where calling `parent` on a node
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index a4eda6fdf76..8e7084f2543 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -35,9 +35,11 @@ module Banzai
headers[id] += 1
if header_content = node.children.first
+ # namespace detection will be automatically handled via javascript (see issue #22781)
+ namespace = "user-content-"
href = "#{id}#{uniq}"
push_toc(href, text)
- header_content.add_previous_sibling(anchor_tag(href))
+ header_content.add_previous_sibling(anchor_tag("#{namespace}#{href}", href))
end
end
@@ -48,8 +50,8 @@ module Banzai
private
- def anchor_tag(href)
- %Q{<a id="#{href}" class="anchor" href="##{href}" aria-hidden="true"></a>}
+ def anchor_tag(id, href)
+ %Q{<a id="#{id}" class="anchor" href="##{href}" aria-hidden="true"></a>}
end
def push_toc(href, text)
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
index 4750675ae9d..0a8d05b5fe1 100644
--- a/lib/gitlab/github_import/branch_formatter.rb
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def valid?
- repo.present?
+ sha.present? && ref.present?
end
private
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index cffed987f6b..d3489324a9c 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -8,26 +8,32 @@ describe HelpController do
end
describe 'GET #index' do
- context 'when url prefixed without /help/' do
- it 'has correct url prefix' do
- stub_readme("[API](api/README.md)")
+ context 'with absolute url' do
+ it 'keeps the URL absolute' do
+ stub_readme("[API](/api/README.md)")
+
get :index
- expect(assigns[:help_index]).to eq '[API](/help/api/README.md)'
+
+ expect(assigns[:help_index]).to eq '[API](/api/README.md)'
end
end
- context 'when url prefixed with help' do
- it 'will be an absolute path' do
- stub_readme("[API](helpful_hints/README.md)")
+ context 'with relative url' do
+ it 'prefixes it with /help/' do
+ stub_readme("[API](api/README.md)")
+
get :index
- expect(assigns[:help_index]).to eq '[API](/help/helpful_hints/README.md)'
+
+ expect(assigns[:help_index]).to eq '[API](/help/api/README.md)'
end
end
context 'when url is an external link' do
- it 'will not be changed' do
+ it 'does not change it' do
stub_readme("[external](https://some.external.link)")
+
get :index
+
expect(assigns[:help_index]).to eq '[external](https://some.external.link)'
end
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index f160052a844..c16aafa1470 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -304,8 +304,8 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.subscription') do
click_button 'Subscribe'
-
- expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.")
+ wait_for_ajax
+ expect(page).to have_content("Unsubscribe")
end
end
end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 73d03837144..4319d6db0d2 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -12,9 +12,9 @@ describe 'Help Pages', feature: true do
end
describe 'Get the main help page' do
- shared_examples_for 'help page' do
+ shared_examples_for 'help page' do |prefix: ''|
it 'prefixes links correctly' do
- expect(page).to have_selector('div.documentation-index > ul a[href="/help/api/README.md"]')
+ expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"]))
end
end
@@ -33,5 +33,14 @@ describe 'Help Pages', feature: true do
it_behaves_like 'help page'
end
+
+ context 'with a relative installation' do
+ before do
+ stub_config_setting(relative_url_root: '/gitlab')
+ visit help_path
+ end
+
+ it_behaves_like 'help page', prefix: '/gitlab'
+ end
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
new file mode 100644
index 00000000000..3350a3aeefc
--- /dev/null
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -0,0 +1,154 @@
+require 'spec_helper'
+
+describe "Pipelines", feature: true, js: true do
+ include GitlabRoutingHelper
+
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ project.team << [user, :developer]
+ end
+
+ describe 'GET /:project/pipelines/:id' do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
+
+ before do
+ @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
+ @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+ @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
+ @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
+ end
+
+ before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ it 'shows the pipeline graph' do
+ expect(page).to have_selector('.pipeline-visualization')
+ expect(page).to have_content('Build')
+ expect(page).to have_content('Test')
+ expect(page).to have_content('Deploy')
+ expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Cancel running')
+ end
+
+ it 'shows Pipeline tab pane as active' do
+ expect(page).to have_css('#js-tab-pipeline.active')
+ end
+
+ context 'page tabs' do
+ it 'shows Pipeline and Builds tabs with link' do
+ expect(page).to have_link('Pipeline')
+ expect(page).to have_link('Builds')
+ end
+
+ it 'shows counter in Builds tab' do
+ expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
+ end
+
+ it 'shows Pipeline tab as active' do
+ expect(page).to have_css('.js-pipeline-tab-link.active')
+ end
+ end
+
+ context 'retrying builds' do
+ it { expect(page).not_to have_content('retried') }
+
+ context 'when retrying' do
+ before { click_on 'Retry failed' }
+
+ it { expect(page).not_to have_content('Retry failed') }
+ end
+ end
+
+ context 'canceling builds' do
+ it { expect(page).not_to have_selector('.ci-canceled') }
+
+ context 'when canceling' do
+ before { click_on 'Cancel running' }
+
+ it { expect(page).not_to have_content('Cancel running') }
+ end
+ end
+ end
+
+ describe 'GET /:project/pipelines/:id/builds' do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
+
+ before do
+ @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
+ @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+ @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
+ @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
+ end
+
+ before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)}
+
+ it 'shows a list of builds' do
+ expect(page).to have_content('Test')
+ expect(page).to have_content(@success.id)
+ expect(page).to have_content('Deploy')
+ expect(page).to have_content(@failed.id)
+ expect(page).to have_content(@running.id)
+ expect(page).to have_content(@external.id)
+ expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Cancel running')
+ expect(page).to have_link('Play')
+ end
+
+ it 'shows Builds tab pane as active' do
+ expect(page).to have_css('#js-tab-builds.active')
+ end
+
+ context 'page tabs' do
+ it 'shows Pipeline and Builds tabs with link' do
+ expect(page).to have_link('Pipeline')
+ expect(page).to have_link('Builds')
+ end
+
+ it 'shows counter in Builds tab' do
+ expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
+ end
+
+ it 'shows Builds tab as active' do
+ expect(page).to have_css('li.js-builds-tab-link.active')
+ end
+ end
+
+ context 'retrying builds' do
+ it { expect(page).not_to have_content('retried') }
+
+ context 'when retrying' do
+ before { click_on 'Retry failed' }
+
+ it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).to have_selector('.retried') }
+ end
+ end
+
+ context 'canceling builds' do
+ it { expect(page).not_to have_selector('.ci-canceled') }
+
+ context 'when canceling' do
+ before { click_on 'Cancel running' }
+
+ it { expect(page).not_to have_content('Cancel running') }
+ it { expect(page).to have_selector('.ci-canceled') }
+ end
+ end
+
+ context 'playing manual build' do
+ before do
+ within '.pipeline-holder' do
+ click_link('Play')
+ end
+ end
+
+ it { expect(@manual.reload).to be_pending }
+ end
+ end
+end
diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 10e5466fc85..f3731698a18 100644
--- a/spec/features/projects/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -152,65 +152,6 @@ describe "Pipelines" do
end
end
- describe 'GET /:project/pipelines/:id' do
- let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
-
- before do
- @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
- @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
- @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
- @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
- @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
- end
-
- before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
-
- it 'shows a list of builds' do
- expect(page).to have_content('Test')
- expect(page).to have_content(@success.id)
- expect(page).to have_content('Deploy')
- expect(page).to have_content(@failed.id)
- expect(page).to have_content(@running.id)
- expect(page).to have_content(@external.id)
- expect(page).to have_content('Retry failed')
- expect(page).to have_content('Cancel running')
- expect(page).to have_link('Play')
- end
-
- context 'retrying builds' do
- it { expect(page).not_to have_content('retried') }
-
- context 'when retrying' do
- before { click_on 'Retry failed' }
-
- it { expect(page).not_to have_content('Retry failed') }
- it { expect(page).to have_selector('.retried') }
- end
- end
-
- context 'canceling builds' do
- it { expect(page).not_to have_selector('.ci-canceled') }
-
- context 'when canceling' do
- before { click_on 'Cancel running' }
-
- it { expect(page).not_to have_content('Cancel running') }
- it { expect(page).to have_selector('.ci-canceled') }
- end
- end
-
- context 'playing manual build' do
- before do
- within '.pipeline-holder' do
- click_link('Play')
- end
- end
-
- it { expect(@manual.reload).to be_pending }
- end
- end
-
describe 'POST /:project/pipelines' do
let(:project) { create(:project) }
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 7afd83b7250..fff8b9f3447 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -20,7 +20,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Home')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
@@ -41,7 +41,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Foo')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
@@ -55,7 +55,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Spaces in the name')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
@@ -69,7 +69,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Hyphens in the name')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
@@ -85,7 +85,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Home')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
@@ -105,7 +105,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
click_button 'Create page'
expect(page).to have_content('Foo')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index ef82d2375dd..f842d14fa96 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -22,7 +22,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
click_button 'Save changes'
expect(page).to have_content('Home')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
@@ -37,7 +37,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
click_button 'Save changes'
expect(page).to have_content('Home')
- expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
new file mode 100644
index 00000000000..9aa3c50611d
--- /dev/null
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
@@ -0,0 +1,55 @@
+//= require lib/utils/bootstrap_linked_tabs
+
+(() => {
+ describe('Linked Tabs', () => {
+ fixture.preload('linked_tabs');
+
+ beforeEach(() => {
+ fixture.load('linked_tabs');
+ });
+
+ describe('when is initialized', () => {
+ it('should activate the tab correspondent to the given action', () => {
+ const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+ action: 'tab1',
+ defaultAction: 'tab1',
+ parentEl: '.linked-tabs',
+ });
+
+ expect(document.querySelector('#tab1').classList).toContain('active');
+ });
+
+ it('should active the default tab action when the action is show', () => {
+ const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+ action: 'show',
+ defaultAction: 'tab1',
+ parentEl: '.linked-tabs',
+ });
+
+ expect(document.querySelector('#tab1').classList).toContain('active');
+ });
+ });
+
+ describe('on click', () => {
+ it('should change the url according to the clicked tab', () => {
+ const historySpy = spyOn(history, 'replaceState').and.callFake(() => {});
+
+ const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+ action: 'show',
+ defaultAction: 'tab1',
+ parentEl: '.linked-tabs',
+ });
+
+ const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a');
+ const newState = secondTab.getAttribute('href') + linkedTabs.currentLocation.search + linkedTabs.currentLocation.hash;
+
+ secondTab.click();
+
+ expect(historySpy).toHaveBeenCalledWith({
+ turbolinks: true,
+ url: newState,
+ }, document.title, newState);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml
deleted file mode 100644
index 42ab4aa68b1..00000000000
--- a/spec/javascripts/fixtures/issuable.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%form.js-main-target-form
- %textarea#note_note
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
new file mode 100644
index 00000000000..93c0cf97ff0
--- /dev/null
+++ b/spec/javascripts/fixtures/linked_tabs.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-tabs.linked-tabs
+ %li
+ %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
+ Tab 1
+ %li
+ %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
+ Tab 2
+
+.tab-content
+ #tab1.tab-pane
+ Tab 1 Content
+ #tab2.tab-pane
+ Tab 2 Content
diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml
deleted file mode 100644
index d259b58f235..00000000000
--- a/spec/javascripts/fixtures/right_sidebar.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%div
- %div.page-gutter.page-with-sidebar
-
- %aside.right-sidebar
- %div.block.issuable-sidebar-header
- %a.gutter-toggle.pull-right.js-sidebar-toggle
- %i.fa.fa-angle-double-left
- %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }}
- %span.js-issuable-todo-text
- Add todo
- %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden
-
- %form.issuable-context-form
- %div.block.labels
- %div.sidebar-collapsed-icon
- %i.fa.fa-tags
- %span 1
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 83ebbd63f3a..0a9bc546144 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -34,9 +34,10 @@
};
describe('RightSidebar', function() {
- fixture.preload('right_sidebar.html');
+ var fixtureName = 'issues/open-issue.html.raw';
+ fixture.preload(fixtureName);
beforeEach(function() {
- fixture.load('right_sidebar.html');
+ fixture.load(fixtureName);
this.sidebar = new Sidebar;
$aside = $('.right-sidebar');
$page = $('.page-with-sidebar');
@@ -44,15 +45,12 @@
$toggle = $aside.find('.js-sidebar-toggle');
return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
});
- it('should expand the sidebar when arrow is clicked', function() {
+ it('should expand/collapse the sidebar when arrow is clicked', function() {
+ assertSidebarState('expanded');
$toggle.click();
- return assertSidebarState('expanded');
- });
- it('should collapse the sidebar when arrow is clicked', function() {
+ assertSidebarState('collapsed');
$toggle.click();
assertSidebarState('expanded');
- $toggle.click();
- return assertSidebarState('collapsed');
});
it('should float over the page and when sidebar icons clicked', function() {
$labelsIcon.click();
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 7d36d79b687..e37816b0a8c 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -4,9 +4,11 @@
(function() {
describe('ShortcutsIssuable', function() {
- fixture.preload('issuable.html');
+ var fixtureName = 'issues/open-issue.html.raw';
+ fixture.preload(fixtureName);
beforeEach(function() {
- fixture.load('issuable.html');
+ fixture.load(fixtureName);
+ document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
return this.shortcut = new ShortcutsIssuable();
});
return describe('#replyWithSelectedText', function() {
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 356dd01a03a..70b31f3a880 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -22,7 +22,7 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
html = header(i, "Header #{i}")
doc = filter(html)
- expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
+ expect(doc.css("h#{i} a").first.attr('id')).to eq "user-content-header-#{i}"
end
end
@@ -32,7 +32,12 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
end
- it 'links to the id' do
+ it 'has a namespaced id' do
+ doc = filter(header(1, 'Header'))
+ expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-header'
+ end
+
+ it 'links to the non-namespaced id' do
doc = filter(header(1, 'Header'))
expect(doc.css('h1 a').first.attr('href')).to eq '#header'
end
@@ -40,29 +45,29 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
describe 'generated IDs' do
it 'translates spaces to dashes' do
doc = filter(header(1, 'This header has spaces in it'))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
+ expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-has-spaces-in-it'
end
it 'squeezes multiple spaces and dashes' do
doc = filter(header(1, 'This---header is poorly-formatted'))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
+ expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-poorly-formatted'
end
it 'removes punctuation' do
doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
+ expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-filled-with-punctuation'
end
it 'appends a unique number to duplicates' do
doc = filter(header(1, 'One') + header(2, 'One'))
- expect(doc.css('h1 a').first.attr('id')).to eq 'one'
- expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
+ expect(doc.css('h1 a').first.attr('href')).to eq '#one'
+ expect(doc.css('h2 a').first.attr('href')).to eq '#one-1'
end
it 'supports Unicode' do
doc = filter(header(1, '한글'))
- expect(doc.css('h1 a').first.attr('id')).to eq '한글'
+ expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-한글'
expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
end
end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
index e5300dbba1e..462caa5b5fe 100644
--- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -49,14 +49,20 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do
end
describe '#valid?' do
- it 'returns true when raw repo is present' do
+ it 'returns true when raw sha and ref are present' do
branch = described_class.new(project, double(raw))
expect(branch.valid?).to eq true
end
- it 'returns false when raw repo is blank' do
- branch = described_class.new(project, double(raw.merge(repo: nil)))
+ it 'returns false when raw sha is blank' do
+ branch = described_class.new(project, double(raw.merge(sha: nil)))
+
+ expect(branch.valid?).to eq false
+ end
+
+ it 'returns false when raw ref is blank' do
+ branch = described_class.new(project, double(raw.merge(ref: nil)))
expect(branch.valid?).to eq false
end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index ef07f2275b1..d4970e38f7c 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -730,8 +730,8 @@ describe Ci::Build, models: true do
pipeline2 = create(:ci_pipeline, project: project)
@build2 = create(:ci_build, pipeline: pipeline2)
- commits = [double(id: pipeline.sha), double(id: pipeline2.sha)]
- allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(@merge_request).to receive(:commits_sha).
+ and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index e5007424041..eb876d105da 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -77,24 +77,13 @@ describe MergeRequestDiff, models: true do
end
describe '#commits_sha' do
- shared_examples 'returning all commits SHA' do
- it 'returns all commits SHA' do
- commits_sha = subject.commits_sha
+ it 'returns all commits SHA using serialized commits' do
+ subject.st_commits = [
+ { id: 'sha1' },
+ { id: 'sha2' }
+ ]
- expect(commits_sha).to eq(subject.commits.map(&:sha))
- end
- end
-
- context 'when commits were loaded' do
- before do
- subject.commits
- end
-
- it_behaves_like 'returning all commits SHA'
- end
-
- context 'when commits were not loaded' do
- it_behaves_like 'returning all commits SHA'
+ expect(subject.commits_sha).to eq(['sha1', 'sha2'])
end
end
@@ -113,4 +102,15 @@ describe MergeRequestDiff, models: true do
expect(diffs.size).to eq(3)
end
end
+
+ describe '#commits_count' do
+ it 'returns number of commits using serialized commits' do
+ subject.st_commits = [
+ { id: 'sha1' },
+ { id: 'sha2' }
+ ]
+
+ expect(subject.commits_count).to eq 2
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 26034cb1c7b..ec22ef93465 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -557,16 +557,13 @@ describe MergeRequest, models: true do
end
describe '#commits_sha' do
- let(:commit0) { double('commit0', sha: 'sha1') }
- let(:commit1) { double('commit1', sha: 'sha2') }
- let(:commit2) { double('commit2', sha: 'sha3') }
-
before do
- allow(subject.merge_request_diff).to receive(:commits).and_return([commit0, commit1, commit2])
+ allow(subject.merge_request_diff).to receive(:commits_sha).
+ and_return(['sha1'])
end
- it 'returns sha of commits' do
- expect(subject.commits_sha).to contain_exactly('sha1', 'sha2', 'sha3')
+ it 'delegates to merge request diff' do
+ expect(subject.commits_sha).to eq ['sha1']
end
end
@@ -1440,4 +1437,26 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#has_commits?' do
+ before do
+ allow(subject.merge_request_diff).to receive(:commits_count).
+ and_return(2)
+ end
+
+ it 'returns true when merge request diff has commits' do
+ expect(subject.has_commits?).to be_truthy
+ end
+ end
+
+ describe '#has_no_commits?' do
+ before do
+ allow(subject.merge_request_diff).to receive(:commits_count).
+ and_return(0)
+ end
+
+ it 'returns true when merge request diff has 0 commits' do
+ expect(subject.has_no_commits?).to be_truthy
+ end
+ end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 7dcd03496bb..90771825f5c 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -7,15 +7,21 @@ describe Projects::DestroyService, services: true do
let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
let!(:async) { false } # execute or async_execute
+ shared_examples 'deleting the project' do
+ it 'deletes the project' do
+ expect(Project.all).not_to include(project)
+ expect(Dir.exist?(path)).to be_falsey
+ expect(Dir.exist?(remove_path)).to be_falsey
+ end
+ end
+
context 'Sidekiq inline' do
before do
# Run sidekiq immediatly to check that renamed repository will be removed
Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
end
- it { expect(Project.all).not_to include(project) }
- it { expect(Dir.exist?(path)).to be_falsey }
- it { expect(Dir.exist?(remove_path)).to be_falsey }
+ it_behaves_like 'deleting the project'
end
context 'Sidekiq fake' do
@@ -38,11 +44,21 @@ describe Projects::DestroyService, services: true do
Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
end
- it 'deletes the project' do
- expect(Project.all).not_to include(project)
- expect(Dir.exist?(path)).to be_falsey
- expect(Dir.exist?(remove_path)).to be_falsey
+ it_behaves_like 'deleting the project'
+ end
+
+ context 'delete with pipeline' do # which has optimistic locking
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ expect(project).to receive(:destroy!).and_call_original
+
+ perform_enqueued_jobs do
+ destroy_project(project, user, {})
+ end
end
+
+ it_behaves_like 'deleting the project'
end
context 'container registry' do
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 02605d6b70e..92cf3de5448 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -1,9 +1,9 @@
RSpec::Matchers.define :have_issuable_counts do |opts|
- match do |actual|
- expected_counts = opts.map do |state, count|
- "#{state.to_s.humanize} #{count}"
- end
+ expected_counts = opts.map do |state, count|
+ "#{state.to_s.humanize} #{count}"
+ end
+ match do |actual|
actual.within '.issues-state-filters' do
expected_counts.each do |expected_count|
expect(actual).to have_content(expected_count)
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 8c98b1f988c..97b8b342eb2 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -38,9 +38,9 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('h1 a#gitlab-markdown')
- expect(actual).to have_selector('h2 a#markdown')
- expect(actual).to have_selector('h3 a#autolinkfilter')
+ expect(actual).to have_selector('h1 a#user-content-gitlab-markdown')
+ expect(actual).to have_selector('h2 a#user-content-markdown')
+ expect(actual).to have_selector('h3 a#user-content-autolinkfilter')
end
end