summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFilipe Freire <livrofubia@gmail.com>2018-01-16 20:19:06 +0000
committerFilipe Freire <livrofubia@gmail.com>2018-01-16 20:19:06 +0000
commit1ab0cf14c40791c555681c3c15d202b10bdf1bb5 (patch)
tree685ef188943a9df186dda6b6ba08f633ca61f728 /app
parenta1a5d142981379087ca7183d402300a3a3b6ad52 (diff)
parent66ae75600af3cdcaf67991b4ae0701d84de2f31a (diff)
downloadgitlab-ce-1ab0cf14c40791c555681c3c15d202b10bdf1bb5.tar.gz
Merge branch 'master' of https://gitlab.com/filipefreire/gitlab-ce into filipefreire_155
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue3
-rw-r--r--app/assets/javascripts/commit_merge_requests.js73
-rw-r--r--app/assets/javascripts/create_item_dropdown.js (renamed from app/assets/javascripts/protected_tags/protected_tag_dropdown.js)57
-rw-r--r--app/assets/javascripts/dispatcher.js269
-rw-r--r--app/assets/javascripts/init_labels.js18
-rw-r--r--app/assets/javascripts/jobs/components/header.vue7
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue5
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue1
-rw-r--r--app/assets/javascripts/pages/groups/milestones/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/milestones/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/blame/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/compare/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/compare/show/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js12
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/imports/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js33
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js16
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js18
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js13
-rw-r--r--app/assets/javascripts/pages/projects/labels/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/labels/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/labels/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js16
-rw-r--r--app/assets/javascripts/pages/projects/milestones/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/milestones/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/tags/new/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js15
-rw-r--r--app/assets/javascripts/pages/snippets/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/snippets/form.js7
-rw-r--r--app/assets/javascripts/pages/snippets/new/index.js3
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue1
-rw-r--r--app/assets/javascripts/projects_dropdown/index.js7
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js12
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js90
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_create.js11
-rw-r--r--app/assets/javascripts/shared/milestones/form.js9
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue65
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue127
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue6
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss10
-rw-r--r--app/assets/stylesheets/framework/header.scss5
-rw-r--r--app/assets/stylesheets/framework/modal.scss32
-rw-r--r--app/assets/stylesheets/framework/stacked-progress-bar.scss54
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss11
-rw-r--r--app/controllers/projects/commit_controller.rb14
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb6
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/commit.rb17
-rw-r--r--app/models/concerns/resolvable_discussion.rb25
-rw-r--r--app/models/concerns/sha_attribute.rb1
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/merge_request_diff.rb3
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/push_event.rb3
-rw-r--r--app/models/repository.rb38
-rw-r--r--app/models/route.rb2
-rw-r--r--app/models/user.rb8
-rw-r--r--app/services/system_hooks_service.rb5
-rw-r--r--app/services/system_note_service.rb23
-rw-r--r--app/views/admin/dashboard/index.html.haml56
-rw-r--r--app/views/dashboard/_activity_head.html.haml4
-rw-r--r--app/views/dashboard/_snippets_head.html.haml4
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml12
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml4
-rw-r--r--app/views/projects/_export.html.haml2
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml26
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/forks/index.html.haml4
-rw-r--r--app/views/projects/jobs/_empty_state.html.haml5
-rw-r--r--app/views/projects/jobs/_table.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml7
-rw-r--r--app/views/projects/merge_requests/creations/_diffs.html.haml6
-rw-r--r--app/views/projects/protected_branches/shared/_dropdown.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_dropdown.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml3
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/workers/repository_fork_worker.rb5
103 files changed, 1052 insertions, 478 deletions
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 57457ebd0a3..ff2e0768a87 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -81,8 +81,7 @@
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
- </a>`,
+ ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
);
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
new file mode 100644
index 00000000000..f76c9b7e690
--- /dev/null
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -0,0 +1,73 @@
+/* global Flash */
+
+import axios from './lib/utils/axios_utils';
+import { n__, s__ } from './locale';
+
+export function getHeaderText(childElementCount, mergeRequestCount) {
+ if (childElementCount === 0) {
+ return `${mergeRequestCount} ${n__('merge request', 'merge requests', mergeRequestCount)}`;
+ }
+ return ',';
+}
+
+export function createHeader(childElementCount, mergeRequestCount) {
+ const headerText = getHeaderText(childElementCount, mergeRequestCount);
+
+ return $('<span />', {
+ class: 'append-right-5',
+ text: headerText,
+ });
+}
+
+export function createLink(mergeRequest) {
+ return $('<a />', {
+ class: 'append-right-5',
+ href: mergeRequest.path,
+ text: `!${mergeRequest.iid}`,
+ });
+}
+
+export function createTitle(mergeRequest) {
+ return $('<span />', {
+ text: mergeRequest.title,
+ });
+}
+
+export function createItem(mergeRequest) {
+ const $item = $('<span />');
+ const $link = createLink(mergeRequest);
+ const $title = createTitle(mergeRequest);
+ $item.append($link);
+ $item.append($title);
+
+ return $item;
+}
+
+export function createContent(mergeRequests) {
+ const $content = $('<span />');
+
+ if (mergeRequests.length === 0) {
+ $content.text(s__('Commits|No related merge requests found'));
+ } else {
+ mergeRequests.forEach((mergeRequest) => {
+ const $header = createHeader($content.children().length, mergeRequests.length);
+ const $item = createItem(mergeRequest);
+ $content.append($header);
+ $content.append($item);
+ });
+ }
+
+ return $content;
+}
+
+export function fetchCommitMergeRequests() {
+ const $container = $('.merge-requests');
+
+ axios.get($container.data('projectCommitPath'))
+ .then((response) => {
+ const $content = createContent(response.data);
+
+ $container.html($content);
+ })
+ .catch(() => Flash(s__('Commits|An error occurred while fetching merge requests data.')));
+}
diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/create_item_dropdown.js
index a0224213aa0..c3eceb285f5 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js
+++ b/app/assets/javascripts/create_item_dropdown.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
-export default class ProtectedTagDropdown {
+export default class CreateItemDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
@@ -8,11 +8,14 @@ export default class ProtectedTagDropdown {
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
- this.onSelect = options.onSelect;
+ this.defaultToggleLabel = options.defaultToggleLabel;
+ this.fieldName = options.fieldName;
+ this.onSelect = options.onSelect || (() => {});
+ this.getDataOption = options.getData;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
- this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag');
+ this.$createButton = this.$dropdownContainer.find('.js-dropdown-create-new-item');
this.buildDropdown();
this.bindEvents();
@@ -23,7 +26,7 @@ export default class ProtectedTagDropdown {
buildDropdown() {
this.$dropdown.glDropdown({
- data: this.getProtectedTags.bind(this),
+ data: this.getData.bind(this),
filterable: true,
remote: false,
search: {
@@ -31,14 +34,14 @@ export default class ProtectedTagDropdown {
},
selectable: true,
toggleLabel(selected) {
- return (selected && 'id' in selected) ? selected.title : 'Protected Tag';
+ return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel;
},
- fieldName: 'protected_tag[name]',
- text(protectedTag) {
- return _.escape(protectedTag.title);
+ fieldName: this.fieldName,
+ text(item) {
+ return _.escape(item.title);
},
- id(protectedTag) {
- return _.escape(protectedTag.id);
+ id(item) {
+ return _.escape(item.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
@@ -49,37 +52,37 @@ export default class ProtectedTagDropdown {
}
bindEvents() {
- this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this));
+ this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
+ e.preventDefault();
+
+ // Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
- e.preventDefault();
}
- getProtectedTags(term, callback) {
- if (this.selectedTag) {
- callback(gon.open_tags.concat(this.selectedTag));
- } else {
- callback(gon.open_tags);
- }
+ getData(term, callback) {
+ this.getDataOption(term, (data = []) => {
+ callback(data.concat(this.selectedItem || []));
+ });
}
- toggleCreateNewButton(tagName) {
- if (tagName) {
- this.selectedTag = {
- title: tagName,
- id: tagName,
- text: tagName,
+ toggleCreateNewButton(item) {
+ if (item) {
+ this.selectedItem = {
+ title: item,
+ id: item,
+ text: item,
};
this.$dropdownContainer
- .find('.js-create-new-protected-tag code')
- .text(tagName);
+ .find('.js-dropdown-create-new-item code')
+ .text(item);
}
- this.toggleFooter(!tagName);
+ this.toggleFooter(!item);
}
toggleFooter(toggleState) {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index f8fedab36a1..1c6336073e9 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,50 +1,34 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-import { s__ } from './locale';
import projectSelect from './project_select';
-import IssuableIndex from './issuable_index';
import Milestone from './milestone';
import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select';
import MilestoneSelect from './milestone_select';
-import NewBranchForm from './new_branch_form';
import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
-import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
import MergeRequest from './merge_request';
import Compare from './compare';
-import initCompareAutocomplete from './compare_autocomplete';
-import ProjectFindFile from './project_find_file';
import ProjectNew from './project_new';
-import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
import Sidebar from './right_sidebar';
import IssuableTemplateSelectors from './templates/issuable_template_selectors';
import Flash from './flash';
-import CommitsList from './commits';
-import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import SecretValues from './behaviors/secret_values';
-import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
import ProjectsList from './projects_list';
-import setupProjectEdit from './project_edit';
-import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
-import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
-import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import ShortcutsWiki from './shortcuts_wiki';
import BlobViewer from './blob/viewer/index';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select';
-import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
-import ShortcutsBlob from './shortcuts_blob';
import Star from './star';
import TreeView from './tree';
import Wikis from './wikis';
@@ -54,21 +38,16 @@ import PerformanceBar from './performance_bar';
import initNotes from './init_notes';
import initIssuableSidebar from './init_issuable_sidebar';
import initProjectVisibilitySelector from './project_visibility';
-import GpgBadges from './gpg_badges';
-import initChangesDropdown from './init_changes_dropdown';
import NewGroupChild from './groups/new_group_child';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
-import AjaxLoadingSpinner from './ajax_loading_spinner';
import GlFieldErrors from './gl_field_errors';
import GLForm from './gl_form';
import Shortcuts from './shortcuts';
import ShortcutsNavigation from './shortcuts_navigation';
-import ShortcutsFindFile from './shortcuts_find_file';
import ShortcutsIssuable from './shortcuts_issuable';
import U2FAuthenticate from './u2f/authenticate';
import Members from './members';
import memberExpirationDate from './member_expiration_date';
-import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import SearchAutocomplete from './search_autocomplete';
@@ -85,7 +64,7 @@ import Activities from './activities';
}
Dispatcher.prototype.initPageScripts = function() {
- var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
+ var path, shortcut_handler;
const page = $('body').attr('data-page');
if (!page) {
return false;
@@ -110,33 +89,6 @@ import Activities from './activities';
});
});
- function initBlob() {
- new LineHighlighter();
-
- new BlobLinePermalinkUpdater(
- document.querySelector('#blob-content-holder'),
- '.diff-line-num[data-line-number]',
- document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
- );
-
- shortcut_handler = new ShortcutsNavigation();
- fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
- fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
- new ShortcutsBlob({
- skipResetBindings: true,
- fileBlobPermalinkUrl,
- });
-
- new BlobForkSuggestion({
- openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
- forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
- cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
- suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
- actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
- })
- .init();
- }
-
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
switch (page) {
@@ -153,22 +105,22 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:merge_requests:index':
+ import('./pages/projects/merge_requests/index')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
+ break;
case 'projects:issues:index':
- if (filteredSearchEnabled) {
- const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
- filteredSearchManager.setup();
- }
- const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
- new IssuableIndex(pagePrefix);
-
- shortcut_handler = new ShortcutsNavigation();
- new UsersSelect();
+ import('./pages/projects/issues/index')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:issues:show':
- new Issue();
- shortcut_handler = new ShortcutsIssuable();
- new ZenMode();
- initIssuableSidebar();
+ import('./pages/projects/issues/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'dashboard:milestones:index':
import('./pages/dashboard/milestones/index')
@@ -225,40 +177,60 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:milestones:new':
+ case 'projects:milestones:create':
+ import('./pages/projects/milestones/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:milestones:edit':
case 'projects:milestones:update':
- new ZenMode();
- new DueDateSelectors();
- new GLForm($('.milestone-form'), true);
+ import('./pages/projects/milestones/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups:milestones:new':
+ case 'groups:milestones:create':
+ import('./pages/groups/milestones/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:milestones:edit':
case 'groups:milestones:update':
- new ZenMode();
- new DueDateSelectors();
- new GLForm($('.milestone-form'), false);
+ import('./pages/groups/milestones/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:compare:show':
- new Diff();
- const paddingTop = 16;
- initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
+ import('./pages/projects/compare/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:branches:new':
+ import('./pages/projects/branches/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:branches:create':
- new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
+ import('./pages/projects/branches/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:branches:index':
- AjaxLoadingSpinner.init();
- new DeleteModal();
+ import('./pages/projects/branches/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:issues:new':
+ import('./pages/projects/issues/new')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
+ break;
case 'projects:issues:edit':
- shortcut_handler = new ShortcutsNavigation();
- new GLForm($('.issue-form'), true);
- new IssuableForm($('.issue-form'));
- new LabelsSelect();
- new MilestoneSelect();
- new IssuableTemplateSelectors();
+ import('./pages/projects/issues/edit')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:merge_requests:creations:new':
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
@@ -286,9 +258,9 @@ import Activities from './activities';
new AutoWidthDropdownSelect($('.js-target-branch-select')).init();
break;
case 'projects:tags:new':
- new ZenMode();
- new GLForm($('.tag-form'), true);
- new RefSelectDropdown($('.js-branch-select'));
+ import('./pages/projects/tags/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:snippets:show':
initNotes();
@@ -302,11 +274,24 @@ import Activities from './activities';
new ZenMode();
break;
case 'snippets:new':
+ import('./pages/snippets/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:edit':
+ import('./pages/snippets/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:create':
+ import('./pages/snippets/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:update':
- new GLForm($('.snippet-form'), false);
- new ZenMode();
+ import('./pages/snippets/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:releases:edit':
new ZenMode();
@@ -332,22 +317,15 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:commit:show':
- new Diff();
- new ZenMode();
- shortcut_handler = new ShortcutsNavigation();
- new MiniPipelineGraph({
- container: '.js-commit-pipeline-graph',
- }).bindEvents();
- initNotes();
- const stickyBarPaddingTop = 16;
- initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
- $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ import('./pages/projects/commit/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:commit:pipelines':
- new MiniPipelineGraph({
- container: '.js-commit-pipeline-graph',
- }).bindEvents();
- $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ import('./pages/projects/commit/pipelines')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:activity':
import('./pages/projects/activity')
@@ -356,9 +334,10 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:commits:show':
- CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
- shortcut_handler = new ShortcutsNavigation();
- GpgBadges.fetch();
+ import('./pages/projects/commits/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:show':
shortcut_handler = new ShortcutsNavigation();
@@ -376,12 +355,14 @@ import Activities from './activities';
});
break;
case 'projects:edit':
- setupProjectEdit();
- // Initialize expandable settings panels
- initSettingsPanels();
+ import('./pages/projects/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:imports:show':
- projectImport();
+ import('./pages/projects/imports/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:pipelines:new':
case 'projects:pipelines:create':
@@ -441,39 +422,49 @@ import Activities from './activities';
groupAvatar();
break;
case 'projects:tree:show':
- shortcut_handler = new ShortcutsNavigation();
- new TreeView();
- new BlobViewer();
- new NewCommitForm($('.js-create-dir-form'));
- $('#tree-slider').waitForImages(function() {
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
- });
+ import('./pages/projects/tree/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:find_file:show':
- const findElement = document.querySelector('.js-file-finder');
- const projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
- url: findElement.dataset.fileFindUrl,
- treeUrl: findElement.dataset.findTreeUrl,
- blobUrlTemplate: findElement.dataset.blobUrlTemplate,
- });
- new ShortcutsFindFile(projectFindFile);
+ import('./pages/projects/find_file/show')
+ .then(callDefault)
+ .catch(fail);
shortcut_handler = true;
break;
case 'projects:blob:show':
- new BlobViewer();
- initBlob();
+ import('./pages/projects/blob/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:blame:show':
- initBlob();
+ import('./pages/projects/blame/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'groups:labels:new':
case 'groups:labels:edit':
+ new Labels();
+ break;
case 'projects:labels:new':
+ import('./pages/projects/labels/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:labels:edit':
- new Labels();
+ import('./pages/projects/labels/edit')
+ .then(callDefault)
+ .catch(fail);
break;
- case 'groups:labels:index':
case 'projects:labels:index':
+ import('./pages/projects/labels/index')
+ .then(callDefault)
+ .catch(fail);
+ break;
+ case 'groups:labels:index':
if ($('.prioritized-labels').length) {
new LabelManager();
}
@@ -493,7 +484,7 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:forks:new':
- import(/* webpackChunkName: 'project_fork' */ './project_fork')
+ import('./pages/projects/forks/new')
.then(callDefault)
.catch(fail);
break;
@@ -550,7 +541,9 @@ import Activities from './activities';
import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail);
break;
case 'snippets:show':
- import('./pages/snippets/show').then(m => m.default()).catch(fail);
+ import('./pages/snippets/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
@@ -566,20 +559,14 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:clusters:show':
- import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
- .then(cluster => new cluster.default()) // eslint-disable-line new-cap
- .catch((err) => {
- Flash(s__('ClusterIntegration|Problem setting up the cluster'));
- throw err;
- });
+ import('./pages/projects/clusters/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:clusters:index':
- import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index')
- .then(clusterIndex => clusterIndex.default())
- .catch((err) => {
- Flash(s__('ClusterIntegration|Problem setting up the clusters list'));
- throw err;
- });
+ import('./pages/projects/clusters/index')
+ .then(callDefault)
+ .catch(fail);
break;
}
switch (path[0]) {
@@ -659,7 +646,9 @@ import Activities from './activities';
projectAvatar();
switch (path[1]) {
case 'compare':
- initCompareAutocomplete();
+ import('./pages/projects/compare')
+ .then(callDefault)
+ .catch(fail);
break;
case 'edit':
shortcut_handler = new ShortcutsNavigation();
diff --git a/app/assets/javascripts/init_labels.js b/app/assets/javascripts/init_labels.js
new file mode 100644
index 00000000000..5f20055510f
--- /dev/null
+++ b/app/assets/javascripts/init_labels.js
@@ -0,0 +1,18 @@
+import LabelManager from './label_manager';
+import GroupLabelSubscription from './group_label_subscription';
+import ProjectLabelSubscription from './project_label_subscription';
+
+export default () => {
+ if ($('.prioritized-labels').length) {
+ new LabelManager(); // eslint-disable-line no-new
+ }
+ $('.label-subscription').each((i, el) => {
+ const $el = $(el);
+
+ if ($el.find('.dropdown-group-label').length) {
+ new GroupLabelSubscription($el); // eslint-disable-line no-new
+ } else {
+ new ProjectLabelSubscription($el); // eslint-disable-line no-new
+ }
+ });
+};
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 9e3f659db5f..357bc9aab17 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -30,8 +30,12 @@
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length;
},
+ /**
+ * When job has not started the key will be `false`
+ * When job started the key will be a string with a date.
+ */
jobStarted() {
- return this.job.started;
+ return !this.job.started === false;
},
},
watch: {
@@ -72,6 +76,7 @@
<loading-icon
v-if="isLoading"
size="2"
+ class="prepend-top-default append-bottom-default"
/>
</div>
</template>
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index cb3cdea8111..e26bf437efc 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -110,7 +110,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
});
};
-MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
+MergeRequest.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box')
.removeClass(classToRemove)
.addClass(classToAdd)
@@ -118,14 +118,14 @@ MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, ne
.text(newStatusText);
};
-MergeRequest.prototype.decreaseCounter = function(by = 1) {
- const $el = $('.nav-links .js-merge-counter');
+MergeRequest.decreaseCounter = function(by = 1) {
+ const $el = $('.js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
-MergeRequest.prototype.hideCloseButton = function() {
+MergeRequest.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 1f18c196137..3c8452ac808 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -271,7 +271,7 @@ Please check your network connection and try again.`;
<div class="timeline-content timeline-content-form">
<form
ref="commentForm"
- class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
+ class="new-note common-note-form gfm-form js-main-target-form"
>
<div class="error-alert"></div>
@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
:disabled="isSubmitting"
placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()"
- @keydown.meta.enter="handleSave()">
+ @keydown.meta.enter="handleSave()"
+ @keydown.ctrl.enter="handleSave()">
</textarea>
</markdown-field>
<div class="note-form-actions">
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index aeda3497715..d382a9bb642 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -155,6 +155,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
slot="textarea"
placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="handleUpdate()"
+ @keydown.ctrl.enter="handleUpdate()"
@keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)">
</textarea>
diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js
new file mode 100644
index 00000000000..5c99c90e24d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm(false);
diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js
new file mode 100644
index 00000000000..5c99c90e24d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/milestones/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm(false);
diff --git a/app/assets/javascripts/pages/projects/blame/show/index.js b/app/assets/javascripts/pages/projects/blame/show/index.js
new file mode 100644
index 00000000000..480357a309c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/blame/show/index.js
@@ -0,0 +1,3 @@
+import initBlob from '~/pages/projects/init_blob';
+
+export default initBlob;
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
new file mode 100644
index 00000000000..a3eeb1cefb6
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -0,0 +1,7 @@
+import BlobViewer from '~/blob/viewer/index';
+import initBlob from '~/pages/projects/init_blob';
+
+export default () => {
+ new BlobViewer(); // eslint-disable-line no-new
+ initBlob();
+};
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
new file mode 100644
index 00000000000..cee0f19bf2a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -0,0 +1,7 @@
+import AjaxLoadingSpinner from '~/ajax_loading_spinner';
+import DeleteModal from '~/branches/branches_delete_modal';
+
+export default () => {
+ AjaxLoadingSpinner.init();
+ new DeleteModal(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
new file mode 100644
index 00000000000..ae5e033e97e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -0,0 +1,3 @@
+import NewBranchForm from '~/new_branch_form';
+
+export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
new file mode 100644
index 00000000000..d531ab81dc7
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -0,0 +1,5 @@
+import ClustersIndex from '~/clusters/clusters_index';
+
+export default () => {
+ new ClustersIndex(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
new file mode 100644
index 00000000000..0458c02a66f
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -0,0 +1,5 @@
+import ClustersBundle from '~/clusters/clusters_bundle';
+
+export default () => {
+ new ClustersBundle(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
new file mode 100644
index 00000000000..523ad567021
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -0,0 +1,8 @@
+import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+
+export default () => {
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+};
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
new file mode 100644
index 00000000000..5ac38e6f278
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -0,0 +1,22 @@
+/* eslint-disable no-new */
+import Diff from '~/diff';
+import ZenMode from '~/zen_mode';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+import initNotes from '~/init_notes';
+import initChangesDropdown from '~/init_changes_dropdown';
+import { fetchCommitMergeRequests } from '~/commit_merge_requests';
+
+export default () => {
+ new Diff();
+ new ZenMode();
+ new ShortcutsNavigation();
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
+ initNotes();
+ const stickyBarPaddingTop = 16;
+ initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ fetchCommitMergeRequests();
+};
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
new file mode 100644
index 00000000000..90b5882a24f
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -0,0 +1,9 @@
+import CommitsList from '~/commits';
+import GpgBadges from '~/gpg_badges';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default () => {
+ CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ GpgBadges.fetch();
+};
diff --git a/app/assets/javascripts/pages/projects/compare/index.js b/app/assets/javascripts/pages/projects/compare/index.js
new file mode 100644
index 00000000000..890062eeee6
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/compare/index.js
@@ -0,0 +1,5 @@
+import initCompareAutocomplete from '~/compare_autocomplete';
+
+export default () => {
+ initCompareAutocomplete();
+};
diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js
new file mode 100644
index 00000000000..6b8d4503568
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/compare/show/index.js
@@ -0,0 +1,8 @@
+import Diff from '~/diff';
+import initChangesDropdown from '~/init_changes_dropdown';
+
+export default () => {
+ new Diff(); // eslint-disable-line no-new
+ const paddingTop = 16;
+ initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
+};
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
new file mode 100644
index 00000000000..7f662ef6b6a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -0,0 +1,8 @@
+import initSettingsPanels from '~/settings_panels';
+import setupProjectEdit from '~/project_edit';
+
+export default () => {
+ setupProjectEdit();
+ // Initialize expandable settings panels
+ initSettingsPanels();
+};
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
new file mode 100644
index 00000000000..42bde0ff779
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -0,0 +1,12 @@
+import ProjectFindFile from '~/project_find_file';
+import ShortcutsFindFile from '~/shortcuts_find_file';
+
+export default () => {
+ const findElement = document.querySelector('.js-file-finder');
+ const projectFindFile = new ProjectFindFile($('.file-finder-holder'), {
+ url: findElement.dataset.fileFindUrl,
+ treeUrl: findElement.dataset.findTreeUrl,
+ blobUrlTemplate: findElement.dataset.blobUrlTemplate,
+ });
+ new ShortcutsFindFile(projectFindFile); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/forks/new/index.js b/app/assets/javascripts/pages/projects/forks/new/index.js
new file mode 100644
index 00000000000..7825eb01949
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/index.js
@@ -0,0 +1,5 @@
+import ProjectFork from '~/project_fork';
+
+export default () => {
+ new ProjectFork(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/imports/show/index.js b/app/assets/javascripts/pages/projects/imports/show/index.js
new file mode 100644
index 00000000000..378f7b3f38b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/imports/show/index.js
@@ -0,0 +1,5 @@
+import ProjectImport from '~/project_import';
+
+export default () => {
+ new ProjectImport(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
new file mode 100644
index 00000000000..26f0ad46114
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -0,0 +1,33 @@
+import LineHighlighter from '~/line_highlighter';
+import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsBlob from '~/shortcuts_blob';
+import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
+
+export default () => {
+ new LineHighlighter(); // eslint-disable-line no-new
+
+ new BlobLinePermalinkUpdater( // eslint-disable-line no-new
+ document.querySelector('#blob-content-holder'),
+ '.diff-line-num[data-line-number]',
+ document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
+ );
+
+ const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
+ const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
+
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+
+ new ShortcutsBlob({ // eslint-disable-line no-new
+ skipResetBindings: true,
+ fileBlobPermalinkUrl,
+ });
+
+ new BlobForkSuggestion({ // eslint-disable-line no-new
+ openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
+ forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
+ cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
+ suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
+ actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
+ }).init();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
new file mode 100644
index 00000000000..7f27f379d8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -0,0 +1,5 @@
+import initForm from '../form';
+
+export default () => {
+ initForm();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
new file mode 100644
index 00000000000..5c7daf84738
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -0,0 +1,16 @@
+/* eslint-disable no-new */
+import GLForm from '~/gl_form';
+import IssuableForm from '~/issuable_form';
+import LabelsSelect from '~/labels_select';
+import MilestoneSelect from '~/milestone_select';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
+
+export default () => {
+ new ShortcutsNavigation();
+ new GLForm($('.issue-form'), true);
+ new IssuableForm($('.issue-form'));
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssuableTemplateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
new file mode 100644
index 00000000000..fd395a45f00
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -0,0 +1,18 @@
+
+/* eslint-disable no-new */
+
+import IssuableIndex from '~/issuable_index';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import UsersSelect from '~/users_select';
+
+export default () => {
+ const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
+ if (filteredSearchEnabled) {
+ const filteredSearchManager = new gl.FilteredSearchManager('issues');
+ filteredSearchManager.setup();
+ }
+ new IssuableIndex('issue_');
+
+ new ShortcutsNavigation();
+ new UsersSelect();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
new file mode 100644
index 00000000000..7f27f379d8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -0,0 +1,5 @@
+import initForm from '../form';
+
+export default () => {
+ initForm();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
new file mode 100644
index 00000000000..48ed8fb2243
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -0,0 +1,13 @@
+
+/* eslint-disable no-new */
+import initIssuableSidebar from '~/init_issuable_sidebar';
+import Issue from '~/issue';
+import ShortcutsIssuable from '~/shortcuts_issuable';
+import ZenMode from '~/zen_mode';
+
+export default () => {
+ new Issue();
+ new ShortcutsIssuable();
+ new ZenMode();
+ initIssuableSidebar();
+};
diff --git a/app/assets/javascripts/pages/projects/labels/edit/index.js b/app/assets/javascripts/pages/projects/labels/edit/index.js
new file mode 100644
index 00000000000..72c5e4744ac
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/edit/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
new file mode 100644
index 00000000000..018345fa112
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -0,0 +1,3 @@
+import initLabels from '~/init_labels';
+
+export default initLabels;
diff --git a/app/assets/javascripts/pages/projects/labels/new/index.js b/app/assets/javascripts/pages/projects/labels/new/index.js
new file mode 100644
index 00000000000..72c5e4744ac
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/new/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
new file mode 100644
index 00000000000..a52bea03aa2
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -0,0 +1,16 @@
+import IssuableIndex from '~/issuable_index';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import UsersSelect from '~/users_select';
+
+export default () => {
+ const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
+
+ if (filteredSearchEnabled) {
+ const filteredSearchManager = new gl.FilteredSearchManager('merge_requests');
+ filteredSearchManager.setup();
+ }
+
+ new IssuableIndex('merge_request_'); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new UsersSelect(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js
new file mode 100644
index 00000000000..10e3979a36e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm();
diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js
new file mode 100644
index 00000000000..10e3979a36e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/milestones/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm();
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
new file mode 100644
index 00000000000..dacc2875c8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -0,0 +1,9 @@
+import RefSelectDropdown from '../../../../ref_select_dropdown';
+import ZenMode from '../../../../zen_mode';
+import GLForm from '../../../../gl_form';
+
+export default () => {
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($('.tag-form'), true); // eslint-disable-line no-new
+ new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
new file mode 100644
index 00000000000..28a0160f47d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -0,0 +1,15 @@
+import TreeView from '../../../../tree';
+import ShortcutsNavigation from '../../../../shortcuts_navigation';
+import BlobViewer from '../../../../blob/viewer';
+import NewCommitForm from '../../../../new_commit_form';
+import { ajaxGet } from '../../../../lib/utils/common_utils';
+
+export default () => {
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new TreeView(); // eslint-disable-line no-new
+ new BlobViewer(); // eslint-disable-line no-new
+ new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
+ $('#tree-slider').waitForImages(() =>
+ ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
+};
+
diff --git a/app/assets/javascripts/pages/snippets/edit/index.js b/app/assets/javascripts/pages/snippets/edit/index.js
new file mode 100644
index 00000000000..9c664b5f1ff
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/edit/index.js
@@ -0,0 +1,3 @@
+import form from '../form';
+
+export default form;
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
new file mode 100644
index 00000000000..f996d3cd74e
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -0,0 +1,7 @@
+import GLForm from '~/gl_form';
+import ZenMode from '~/zen_mode';
+
+export default () => {
+ new GLForm($('.snippet-form'), false); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/snippets/new/index.js b/app/assets/javascripts/pages/snippets/new/index.js
new file mode 100644
index 00000000000..9c664b5f1ff
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/new/index.js
@@ -0,0 +1,3 @@
+import form from '../form';
+
+export default form;
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 942acc8c412..e08c2092680 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -92,6 +92,7 @@
<loading-icon
v-if="isLoading"
size="2"
+ class="prepend-top-default append-bottom-default"
/>
</div>
</template>
diff --git a/app/assets/javascripts/projects_dropdown/index.js b/app/assets/javascripts/projects_dropdown/index.js
index 2660da3c558..e78ebce2923 100644
--- a/app/assets/javascripts/projects_dropdown/index.js
+++ b/app/assets/javascripts/projects_dropdown/index.js
@@ -19,11 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
- $(navEl).on('show.bs.dropdown', (e) => {
- const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
- dropdownEl.one('transitionend', () => {
- eventHub.$emit('dropdownOpen');
- });
+ $(navEl).on('shown.bs.dropdown', () => {
+ eventHub.$emit('dropdownOpen');
});
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 0a9fdb074e5..2948baeab11 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
-import ProtectedBranchDropdown from './protected_branch_dropdown';
+import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor';
const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
@@ -35,10 +35,12 @@ export default class ProtectedBranchCreate {
onSelect: this.onSelectCallback,
});
- // Protected branch dropdown
- this.protectedBranchDropdown = new ProtectedBranchDropdown({
+ this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown,
+ defaultToggleLabel: 'Protected Branch',
+ fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
+ getData: ProtectedBranchCreate.getProtectedBranches,
});
this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown'));
@@ -60,6 +62,10 @@ export default class ProtectedBranchCreate {
this.$form.find('input[type="submit"]').attr('disabled', completedForm);
}
+ static getProtectedBranches(term, callback) {
+ callback(gon.open_branches);
+ }
+
loadPreviousSelection(mergeDropdown, pushDropdown) {
let mergeIndex = 0;
let pushIndex = 0;
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
deleted file mode 100644
index 678882a8d2c..00000000000
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import _ from 'underscore';
-
-export default class ProtectedBranchDropdown {
- /**
- * @param {Object} options containing
- * `$dropdown` target element
- * `onSelect` event callback
- * $dropdown must be an element created using `dropdown_branch()` rails helper
- */
- constructor(options) {
- this.onSelect = options.onSelect;
- this.$dropdown = options.$dropdown;
- this.$dropdownContainer = this.$dropdown.parent();
- this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
- this.$protectedBranch = this.$dropdownContainer.find('.js-create-new-protected-branch');
-
- this.buildDropdown();
- this.bindEvents();
-
- // Hide footer
- this.toggleFooter(true);
- }
-
- buildDropdown() {
- this.$dropdown.glDropdown({
- data: this.getProtectedBranches.bind(this),
- filterable: true,
- remote: false,
- search: {
- fields: ['title'],
- },
- selectable: true,
- toggleLabel(selected) {
- return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
- },
- fieldName: 'protected_branch[name]',
- text(protectedBranch) {
- return _.escape(protectedBranch.title);
- },
- id(protectedBranch) {
- return _.escape(protectedBranch.id);
- },
- onFilter: this.toggleCreateNewButton.bind(this),
- clicked: (options) => {
- options.e.preventDefault();
- this.onSelect();
- },
- });
- }
-
- bindEvents() {
- this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
- }
-
- onClickCreateWildcard(e) {
- e.preventDefault();
-
- // Refresh the dropdown's data, which ends up calling `getProtectedBranches`
- this.$dropdown.data('glDropdown').remote.execute();
- this.$dropdown.data('glDropdown').selectRowAtIndex();
- }
-
- getProtectedBranches(term, callback) {
- if (this.selectedBranch) {
- callback(gon.open_branches.concat(this.selectedBranch));
- } else {
- callback(gon.open_branches);
- }
- }
-
- toggleCreateNewButton(branchName) {
- if (branchName) {
- this.selectedBranch = {
- title: branchName,
- id: branchName,
- text: branchName,
- };
-
- this.$dropdownContainer
- .find('.js-create-new-protected-branch code')
- .text(branchName);
- }
-
- this.toggleFooter(!branchName);
- }
-
- toggleFooter(toggleState) {
- this.$dropdownFooter.toggleClass('hidden', toggleState);
- }
-}
diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js
index 91bd140bd12..d1e4a75c17b 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_create.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_create.js
@@ -1,5 +1,5 @@
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
-import ProtectedTagDropdown from './protected_tag_dropdown';
+import CreateItemDropdown from '../create_item_dropdown';
export default class ProtectedTagCreate {
constructor() {
@@ -24,9 +24,12 @@ export default class ProtectedTagCreate {
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected tag dropdown
- this.protectedTagDropdown = new ProtectedTagDropdown({
+ this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'),
+ defaultToggleLabel: 'Protected Tag',
+ fieldName: 'protected_tag[name]',
onSelect: this.onSelectCallback,
+ getData: ProtectedTagCreate.getProtectedTags,
});
}
@@ -38,4 +41,8 @@ export default class ProtectedTagCreate {
this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length));
}
+
+ static getProtectedTags(term, callback) {
+ callback(gon.open_tags);
+ }
}
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
new file mode 100644
index 00000000000..db466f722c4
--- /dev/null
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -0,0 +1,9 @@
+import ZenMode from '../../zen_mode';
+import DueDateSelectors from '../../due_date_select';
+import GLForm from '../../gl_form';
+
+export default (initGFM = true) => {
+ new ZenMode(); // eslint-disable-line no-new
+ new DueDateSelectors(); // eslint-disable-line no-new
+ new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 7226076a8fc..d69d100a26c 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,12 +1,22 @@
<script>
- /* eslint-disable vue/require-default-prop */
- import { __ } from '../../../locale';
+ import { __ } from '~/locale';
+ import icon from '~/vue_shared/components/icon.vue';
+ import toggleButton from '~/vue_shared/components/toggle_button.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
- import loadingButton from '../../../vue_shared/components/loading_button.vue';
+
+ const ICON_ON = 'notifications';
+ const ICON_OFF = 'notifications-off';
+ const LABEL_ON = __('Notifications on');
+ const LABEL_OFF = __('Notifications off');
export default {
+ directives: {
+ tooltip,
+ },
components: {
- loadingButton,
+ icon,
+ toggleButton,
},
props: {
loading: {
@@ -17,22 +27,23 @@
subscribed: {
type: Boolean,
required: false,
+ default: null,
},
id: {
type: Number,
required: false,
+ default: null,
},
},
computed: {
- buttonLabel() {
- let label;
- if (this.subscribed === false) {
- label = __('Subscribe');
- } else if (this.subscribed === true) {
- label = __('Unsubscribe');
- }
-
- return label;
+ showLoadingState() {
+ return this.subscribed === null;
+ },
+ notificationIcon() {
+ return this.subscribed ? ICON_ON : ICON_OFF;
+ },
+ notificationTooltip() {
+ return this.subscribed ? LABEL_ON : LABEL_OFF;
},
},
methods: {
@@ -46,21 +57,29 @@
<template>
<div>
<div class="sidebar-collapsed-icon">
- <i
- class="fa fa-rss"
- aria-hidden="true"
+ <span
+ v-tooltip
+ :title="notificationTooltip"
+ data-container="body"
+ data-placement="left"
>
- </i>
+ <icon
+ :name="notificationIcon"
+ :size="16"
+ aria-hidden="true"
+ class="sidebar-item-icon is-active"
+ />
+ </span>
</div>
<span class="issuable-header-text hide-collapsed pull-left">
{{ __('Notifications') }}
</span>
- <loading-button
- ref="loadingButton"
- class="btn btn-default pull-right hide-collapsed js-issuable-subscribe-button"
- :loading="loading"
- :label="buttonLabel"
- @click="toggleSubscription"
+ <toggle-button
+ ref="toggleButton"
+ class="pull-right hide-collapsed js-issuable-subscribe-button"
+ :is-loading="showLoadingState"
+ :value="subscribed"
+ @change="toggleSubscription"
/>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index e82fb979162..60f42c46ffe 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -1,6 +1,7 @@
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
@@ -165,11 +166,9 @@ export default {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('FetchActionsContent');
- if (window.mergeRequest) {
- window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
- window.mergeRequest.hideCloseButton();
- window.mergeRequest.decreaseCounter();
- }
+ MergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
+ MergeRequest.hideCloseButton();
+ MergeRequest.decreaseCounter();
stopPolling();
// If user checked remove source branch and we didn't remove the branch yet
diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index c103c45c7dd..8227428d8ba 100644
--- a/app/assets/javascripts/vue_shared/components/modal.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -122,7 +122,7 @@
>
<button
type="button"
- class="btn pull-left"
+ class="btn"
:class="btnCancelKindClass"
@click="emitCancel($event)"
data-dismiss="modal"
@@ -132,7 +132,7 @@
<button
v-if="primaryButtonLabel"
type="button"
- class="btn pull-right js-primary-button"
+ class="btn js-primary-button"
:disabled="submitDisabled"
:class="btnKindClass"
@click="emitSubmit($event)"
diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
new file mode 100644
index 00000000000..86f06c8d266
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
@@ -0,0 +1,127 @@
+<script>
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ successLabel: {
+ type: String,
+ required: true,
+ },
+ failureLabel: {
+ type: String,
+ required: true,
+ },
+ neutralLabel: {
+ type: String,
+ required: true,
+ },
+ successCount: {
+ type: Number,
+ required: true,
+ },
+ failureCount: {
+ type: Number,
+ required: true,
+ },
+ totalCount: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ neutralCount() {
+ return this.totalCount - this.successCount - this.failureCount;
+ },
+ successPercent() {
+ return this.getPercent(this.successCount);
+ },
+ successBarStyle() {
+ return this.barStyle(this.successPercent);
+ },
+ successTooltip() {
+ return this.getTooltip(this.successLabel, this.successCount);
+ },
+ failurePercent() {
+ return this.getPercent(this.failureCount);
+ },
+ failureBarStyle() {
+ return this.barStyle(this.failurePercent);
+ },
+ failureTooltip() {
+ return this.getTooltip(this.failureLabel, this.failureCount);
+ },
+ neutralPercent() {
+ return this.getPercent(this.neutralCount);
+ },
+ neutralBarStyle() {
+ return this.barStyle(this.neutralPercent);
+ },
+ neutralTooltip() {
+ return this.getTooltip(this.neutralLabel, this.neutralCount);
+ },
+ },
+ methods: {
+ getPercent(count) {
+ return Math.ceil((count / this.totalCount) * 100);
+ },
+ barStyle(percent) {
+ return `width: ${percent}%;`;
+ },
+ getTooltip(label, count) {
+ return `${label}: ${count}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="stacked-progress-bar"
+ :class="cssClass"
+ >
+ <span
+ v-if="!totalCount"
+ class="status-unavailable"
+ >
+ {{ __("Not available") }}
+ </span>
+ <span
+ v-tooltip
+ v-if="successPercent"
+ class="status-green"
+ data-placement="bottom"
+ :title="successTooltip"
+ :style="successBarStyle"
+ >
+ {{ successPercent }}%
+ </span>
+ <span
+ v-tooltip
+ v-if="neutralPercent"
+ class="status-neutral"
+ data-placement="bottom"
+ :title="neutralTooltip"
+ :style="neutralBarStyle"
+ >
+ {{ neutralPercent }}%
+ </span>
+ <span
+ v-tooltip
+ v-if="failurePercent"
+ class="status-red"
+ data-placement="bottom"
+ :title="failureTooltip"
+ :style="failureBarStyle"
+ >
+ {{ failurePercent }}%
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index 2b12718ae96..09031d3ffa1 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -23,11 +23,12 @@
name: {
type: String,
required: false,
- default: '',
+ default: null,
},
value: {
type: Boolean,
- required: true,
+ required: false,
+ default: null,
},
disabledInput: {
type: Boolean,
@@ -61,6 +62,7 @@
<template>
<label class="toggle-wrapper">
<input
+ v-if="name"
type="hidden"
:name="name"
:value="value"
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 43b16d3cf7d..cff47ea76ec 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -59,3 +59,4 @@
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
+@import "framework/stacked-progress-bar";
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d1b3754d4ef..1d2303a3a2b 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -666,6 +666,16 @@
}
}
+.dropdown-create-new-item-button {
+ @include dropdown-link;
+
+ width: 100%;
+ background-color: transparent;
+ border: 0;
+ text-align: left;
+ text-overflow: ellipsis;
+}
+
.dropdown-loading {
position: absolute;
top: 0;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ad160f37641..3b7256f3000 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -104,7 +104,10 @@
img {
height: 28px;
- margin-right: 8px;
+
+ + .logo-text {
+ margin-left: 8px;
+ }
}
&.wrap {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 1be66d0ab21..924472f2d7e 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -1,4 +1,5 @@
.modal-header {
+ background-color: $modal-body-bg;
padding: #{3 * $grid-size} #{2 * $grid-size};
.page-title {
@@ -8,6 +9,7 @@
.modal-body {
background-color: $modal-body-bg;
+ min-height: $modal-body-height;
position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size};
@@ -20,6 +22,30 @@
}
}
+.modal-footer {
+ display: flex;
+ flex-direction: row;
+
+ .btn + .btn {
+ margin-left: $grid-size;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ flex-direction: column;
+
+ .btn + .btn {
+ margin-left: 0;
+ margin-top: $grid-size;
+ }
+ }
+
+ @media (min-width: $screen-sm-min) {
+ .btn:first-of-type {
+ margin-left: auto;
+ }
+ }
+}
+
body.modal-open {
overflow: hidden;
}
@@ -32,12 +58,6 @@ body.modal-open {
}
}
-@media (min-width: $screen-md-min) {
- .modal-dialog {
- width: 860px;
- }
-}
-
@media (min-width: $screen-lg-min) {
.modal-full {
width: 98%;
diff --git a/app/assets/stylesheets/framework/stacked-progress-bar.scss b/app/assets/stylesheets/framework/stacked-progress-bar.scss
new file mode 100644
index 00000000000..4869cda73e5
--- /dev/null
+++ b/app/assets/stylesheets/framework/stacked-progress-bar.scss
@@ -0,0 +1,54 @@
+.stacked-progress-bar {
+ display: flex;
+ height: 16px;
+ border-radius: 10px;
+ overflow: hidden;
+ background-color: $theme-gray-100;
+
+ .status-unavailable,
+ .status-green,
+ .status-neutral,
+ .status-red, {
+ height: 100%;
+ min-width: 25px;
+ padding: 0 5px;
+ font-size: $tooltip-font-size;
+ font-weight: normal;
+ color: $white-light;
+ line-height: 16px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .status-unavailable {
+ padding: 0 10px;
+ color: $theme-gray-700;
+ }
+
+ .status-green {
+ background-color: $green-500;
+
+ &:hover {
+ background-color: $green-600;
+ }
+ }
+
+ .status-neutral {
+ background-color: $theme-gray-200;
+ color: $gl-gray-dark;
+
+ &:hover {
+ background-color: $theme-gray-300;
+ }
+ }
+
+ .status-red {
+ background-color: $red-500;
+
+ &:hover {
+ background-color: $red-600;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index a23131e0818..d04e555769b 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -194,6 +194,6 @@ $modal-body-bg: $white-light;
//** Modal footer border color
// $modal-footer-border-color: $modal-header-border-color
-// $modal-lg: 900px
-// $modal-md: 600px
+$modal-lg: 860px;
+$modal-md: 540px;
// $modal-sm: 300px
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ef1520f1f63..da18ddf78d3 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -733,3 +733,8 @@ $popup-box-shadow-color: rgba(90, 90, 90, 0.05);
Multi file editor
*/
$border-color-settings: #e1e1e1;
+
+/*
+Modals
+*/
+$modal-body-height: 134px;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index ae9a8b0182c..759719a72da 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -162,10 +162,6 @@
border: 0;
}
- span {
- display: inline-block;
- }
-
.select2-container span {
margin-top: 0;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 61a76d0387a..bf41005b6d5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -895,17 +895,6 @@ pre.light-well {
}
}
-.create-new-protected-branch-button,
-.create-new-protected-tag-button {
- @include dropdown-link;
-
- width: 100%;
- background-color: transparent;
- border: 0;
- text-align: left;
- text-overflow: ellipsis;
-}
-
.protected-branches-list,
.protected-tags-list {
margin-bottom: 30px;
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 2e7344b1cad..effb484ef0f 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :commit
- before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines]
+ before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines, :merge_requests]
before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
@@ -52,6 +52,18 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def merge_requests
+ @merge_requests = @commit.merge_requests.map do |mr|
+ { iid: mr.iid, path: merge_request_path(mr), title: mr.title }
+ end
+
+ respond_to do |format|
+ format.json do
+ render json: @merge_requests.to_json
+ end
+ end
+ end
+
def branches
# branch_names_contains/tag_names_contains can take a long time when there are thousands of
# branches/tags - each `git branch --contains xxx` request can consume a cpu core.
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 4865ec3dfe5..8b54ba3ad7c 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController
:project,
:tags
])
- @builds = @builds.page(params[:page]).per(30)
+ @builds = @builds.page(params[:page]).per(30).without_count
end
def cancel_all
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 3d2926d5d75..0df80fa700f 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def diffs
- @diffs = if @merge_request.can_be_created
- @merge_request.diffs(diff_options)
- else
- []
- end
+ @diffs = @merge_request.diffs(diff_options) if @merge_request.can_be_created
@diff_notes_disabled = true
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 5e3b2e5581c..a6e1de6ffdc 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,6 +1,8 @@
module BlobHelper
def highlight(blob_name, blob_content, repository: nil, plain: false)
+ plain ||= blob_content.length > Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE
highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository)
+
raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6012dbba1b9..df67fb243ad 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -8,6 +8,7 @@ module Ci
MissingDependenciesError = Class.new(StandardError)
+ belongs_to :project, inverse_of: :builds
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index d4690da3be6..d7153d7b816 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -7,7 +7,7 @@ module Ci
include Presentable
include Gitlab::OptimisticLocking
- belongs_to :project
+ belongs_to :project, inverse_of: :pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 39d7f5b159d..2d2d89af030 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -238,6 +238,10 @@ class Commit
notes.includes(:author)
end
+ def merge_requests
+ @merge_requests ||= project.merge_requests.by_commit_sha(sha)
+ end
+
def method_missing(method, *args, &block)
@raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -342,10 +346,11 @@ class Commit
@merged_merge_request_hash[current_user]
end
- def has_been_reverted?(current_user, noteable = self)
+ def has_been_reverted?(current_user, notes_association = nil)
ext = all_references(current_user)
+ notes_association ||= notes_with_associations
- noteable.notes_with_associations.system.each do |note|
+ notes_association.system.each do |note|
note.all_references(current_user, extractor: ext)
end
@@ -367,19 +372,19 @@ class Commit
# uri_type('doc/README.md') # => :blob
# uri_type('doc/logo.png') # => :raw
# uri_type('doc/api') # => :tree
- # uri_type('not/found') # => :nil
+ # uri_type('not/found') # => nil
#
# Returns a symbol
def uri_type(path)
- entry = @raw.rugged_tree_entry(path)
+ entry = @raw.tree_entry(path)
+ return unless entry
+
if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob.image? || blob.video? ? :raw : :blob
else
entry[:type]
end
- rescue Rugged::TreeError
- nil
end
def raw_diffs(*args)
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index b6c7b6735b9..7c236369793 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -1,5 +1,6 @@
module ResolvableDiscussion
extend ActiveSupport::Concern
+ include ::Gitlab::Utils::StrongMemoize
included do
# A number of properties of this `Discussion`, like `first_note` and `resolvable?`, are memoized.
@@ -31,27 +32,37 @@ module ResolvableDiscussion
end
def resolvable?
- @resolvable ||= potentially_resolvable? && notes.any?(&:resolvable?)
+ strong_memoize(:resolvable) do
+ potentially_resolvable? && notes.any?(&:resolvable?)
+ end
end
def resolved?
- @resolved ||= resolvable? && notes.none?(&:to_be_resolved?)
+ strong_memoize(:resolved) do
+ resolvable? && notes.none?(&:to_be_resolved?)
+ end
end
def first_note
- @first_note ||= notes.first
+ strong_memoize(:first_note) do
+ notes.first
+ end
end
def first_note_to_resolve
return unless resolvable?
- @first_note_to_resolve ||= notes.find(&:to_be_resolved?) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ strong_memoize(:first_note_to_resolve) do
+ notes.find(&:to_be_resolved?)
+ end
end
def last_resolved_note
return unless resolved?
- @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ strong_memoize(:last_resolved_note) do
+ resolved_notes.sort_by(&:resolved_at).last
+ end
end
def resolved_notes
@@ -93,8 +104,8 @@ module ResolvableDiscussion
# Set the notes array to the updated notes
@notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
- self.class.memoized_values.each do |var|
- instance_variable_set(:"@#{var}", nil)
+ self.class.memoized_values.each do |name|
+ clear_memoization(name)
end
end
end
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 67ecf470f7e..703a72c355c 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -3,6 +3,7 @@ module ShaAttribute
module ClassMethods
def sha_attribute(name)
+ return if ENV['STATIC_VERIFICATION']
return unless table_exists?
column = columns.find { |c| c.name == name.to_s }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2669d2a6ff3..8028ff3875b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -140,7 +140,9 @@ class MergeRequest < ActiveRecord::Base
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
-
+ scope :by_commit_sha, ->(sha) do
+ where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
+ end
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
scope :assigned, -> { where("assignee_id IS NOT NULL") }
@@ -982,7 +984,16 @@ class MergeRequest < ActiveRecord::Base
end
def can_be_reverted?(current_user)
- merge_commit && !merge_commit.has_been_reverted?(current_user, self)
+ return false unless merge_commit
+
+ merged_at = metrics&.merged_at
+ notes_association = notes_with_associations
+
+ if merged_at
+ notes_association = notes_association.where('created_at > ?', merged_at)
+ end
+
+ !merge_commit.has_been_reverted?(current_user, notes_association)
end
def can_be_cherry_picked?
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index afab72930c1..69a846da9be 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -28,6 +28,9 @@ class MergeRequestDiff < ActiveRecord::Base
end
scope :viewable, -> { without_state(:empty) }
+ scope :by_commit_sha, ->(sha) do
+ joins(:merge_request_diff_commits).where(merge_request_diff_commits: { sha: sha }).reorder(nil)
+ end
scope :recent, -> { order(id: :desc).limit(100) }
diff --git a/app/models/project.rb b/app/models/project.rb
index 029f2da2e4e..d011b614c69 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -20,6 +20,7 @@ class Project < ActiveRecord::Base
include GroupDescendant
include Gitlab::SQL::Pattern
include DeploymentPlatform
+ include ::Gitlab::Utils::StrongMemoize
extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
@@ -198,13 +199,13 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses
- has_many :pipelines, class_name: 'Ci::Pipeline'
+ has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
- has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -993,9 +994,13 @@ class Project < ActiveRecord::Base
end
def repo_exists?
- @repo_exists ||= repository.exists?
- rescue
- @repo_exists = false
+ strong_memoize(:repo_exists) do
+ begin
+ repository.exists?
+ rescue
+ false
+ end
+ end
end
def root_ref?(branch)
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 83ce9014094..90c085c888e 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -46,10 +46,11 @@ class PushEvent < Event
# Returns PushEvent instances for which no merge requests have been created.
def self.without_existing_merge_requests
- existing_mrs = MergeRequest.except(:order)
+ existing_mrs = MergeRequest.except(:order, :where)
.select(1)
.where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref')
+ .where(state: :opened)
# For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d27212b2058..b4bc0f87458 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -831,13 +831,12 @@ class Repository
end
def can_be_merged?(source_sha, target_branch)
- our_commit = rugged.branches[target_branch].target
- their_commit = rugged.lookup(source_sha)
-
- if our_commit && their_commit
- !rugged.merge_commits(our_commit, their_commit).conflicts?
- else
- false
+ raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
+ if is_enabled
+ gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
+ else
+ rugged_can_be_merged?(source_sha, target_branch)
+ end
end
end
@@ -895,15 +894,18 @@ class Repository
branch = Gitlab::Git::Branch.find(self, branch_or_name)
if branch
- @root_ref_sha ||= commit(root_ref).sha
- same_head = branch.target == @root_ref_sha
- merged = ancestor?(branch.target, @root_ref_sha)
+ same_head = branch.target == root_ref_sha
+ merged = ancestor?(branch.target, root_ref_sha)
!same_head && merged
else
nil
end
end
+ def root_ref_sha
+ @root_ref_sha ||= commit(root_ref).sha
+ end
+
delegate :merged_branch_names, to: :raw_repository
def merge_base(first_commit_id, second_commit_id)
@@ -930,15 +932,17 @@ class Repository
return [] if empty? || query.blank?
offset = 2
- args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
+ args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
run_git(args).first.scrub.split(/^--$/)
end
def search_files_by_name(query, ref)
- return [] if empty? || query.blank?
+ safe_query = Regexp.escape(query.sub(/^\/*/, ""))
- args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
+ return [] if empty? || safe_query.blank?
+
+ args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
run_git(args).first.lines.map(&:strip)
end
@@ -1129,6 +1133,14 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
end
+ def gitaly_can_be_merged?(their_commit, our_commit)
+ !raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts?
+ end
+
+ def rugged_can_be_merged?(their_commit, our_commit)
+ !rugged.merge_commits(our_commit, their_commit).conflicts?
+ end
+
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
diff --git a/app/models/route.rb b/app/models/route.rb
index 7ba3ec06041..412f5fb45a5 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- validate :ensure_permanent_paths
+ validate :ensure_permanent_paths, if: :path_changed?
after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
diff --git a/app/models/user.rb b/app/models/user.rb
index 4484ee9ff4c..09aa5a7b318 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -53,7 +53,10 @@ class User < ActiveRecord::Base
serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
- :validatable, :omniauthable, :confirmable, :registerable
+ :validatable, :omniauthable, :confirmable, :registerable
+
+ BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
+ "administrator if you think this is an error.".freeze
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
@@ -217,8 +220,7 @@ class User < ActiveRecord::Base
end
def inactive_message
- "Your account has been blocked. Please contact your GitLab " \
- "administrator if you think this is an error."
+ BLOCKED_MESSAGE
end
end
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 690918b4a00..af6d77ef5e8 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -41,8 +41,11 @@ class SystemHooksService
when User
data.merge!(user_data(model))
- if event == :rename
+ case event
+ when :rename
data[:old_username] = model.username_was
+ when :failed_login
+ data[:state] = model.state
end
when ProjectMember
data.merge!(project_member_data(model))
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 30a5aab13bf..06b23cd7076 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -68,21 +68,14 @@ module SystemNoteService
#
# Returns the created Note object
def change_issue_assignees(issue, project, author, old_assignees)
- body =
- if issue.assignees.any? && old_assignees.any?
- unassigned_users = old_assignees - issue.assignees
- added_users = issue.assignees.to_a - old_assignees
-
- text_parts = []
- text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
- text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
-
- text_parts.join(' and ')
- elsif old_assignees.any?
- "removed assignee"
- elsif issue.assignees.any?
- "assigned to #{issue.assignees.map(&:to_reference).to_sentence}"
- end
+ unassigned_users = old_assignees - issue.assignees
+ added_users = issue.assignees.to_a - old_assignees
+
+ text_parts = []
+ text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
+ text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+
+ body = text_parts.join(' and ')
create_note(NoteSummary.new(issue, project, author, body, action: 'assignee'))
end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index a24516355bf..509f559c120 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -4,6 +4,34 @@
%div{ class: container_class }
.admin-dashboard.prepend-top-default
.row
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_projects_path do
+ %h3.text-center
+ Projects:
+ = number_with_delimiter(Project.cached_count)
+ %hr
+ = link_to('New project', new_project_path, class: "btn btn-new")
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_users_path do
+ %h3.text-center
+ Users:
+ = number_with_delimiter(User.count)
+ %hr
+ = link_to 'New user', new_admin_user_path, class: "btn btn-new"
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_groups_path do
+ %h3.text-center
+ Groups:
+ = number_with_delimiter(Group.count)
+ %hr
+ = link_to 'New group', new_admin_group_path, class: "btn btn-new"
+ .row
.col-md-4
.info-well
.well-segment.admin-well.admin-well-statistics
@@ -136,34 +164,6 @@
%span.pull-right
= Gitlab::Database.version
.row
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_projects_path do
- %h3.text-center
- Projects:
- = number_with_delimiter(Project.cached_count)
- %hr
- = link_to('New project', new_project_path, class: "btn btn-new")
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_users_path do
- %h3.text-center
- Users:
- = number_with_delimiter(User.count)
- %hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-new"
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_groups_path do
- %h3.text-center
- Groups:
- = number_with_delimiter(Group.count)
- %hr
- = link_to 'New group', new_admin_group_path, class: "btn btn-new"
- .row
.col-md-4
.info-well
.well-segment.admin-well
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index ecdf76ef5c5..7a3f3667ac1 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -2,7 +2,7 @@
%ul.nav-links
%li{ class: active_when(params[:filter].nil?) }>
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
- Your Projects
+ Your projects
%li{ class: active_when(params[:filter] == 'starred') }>
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
- Starred Projects
+ Starred projects
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 7330f4cb523..a9488df07bd 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -2,10 +2,10 @@
%ul.nav-links
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
= link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
- Your Snippets
+ Your snippets
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
- Explore Snippets
+ Explore snippets
- if current_user
.nav-controls.hidden-xs
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 42941acc508..3e85535dae0 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -7,7 +7,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
+ = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 46727811be4..e7fc83a8d04 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -6,8 +6,10 @@
%h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo
- %span.logo-text.hidden-xs
- = brand_header_logo_type
+ - logo_text = brand_header_logo_type
+ - if logo_text.present?
+ %span.logo-text.hidden-xs
+ = logo_text
- if current_user
= render "layouts/nav/dashboard"
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 0c27b09f7b1..96aae06a9df 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,5 +1,5 @@
-- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
-- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+- issues_count = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute.count
+- merge_requests_count = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute.count
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
@@ -39,14 +39,14 @@
= sprite_icon('issues')
%span.nav-item-name
Issues
- %span.badge.count= number_with_delimiter(issues.count)
+ %span.badge.count= number_with_delimiter(issues_count)
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
#{ _('Issues') }
- %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues.count)
+ %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
%li.divider.fly-out-top-item
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do
@@ -69,13 +69,13 @@
= sprite_icon('git-merge')
%span.nav-item-name
Merge Requests
- %span.badge.count= number_with_delimiter(merge_requests.count)
+ %span.badge.count= number_with_delimiter(merge_requests_count)
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
= link_to merge_requests_group_path(@group) do
%strong.fly-out-top-item-name
#{ _('Merge Requests') }
- %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
+ %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count)
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group) do
.nav-icon-container
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 86ebec0179c..e44506ec9c9 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -3,12 +3,12 @@
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
GPG keys allow you to verify signed commits.
- .col-lg-9
+ .col-lg-8
%h5.prepend-top-0
Add a GPG key
%p.profile-settings-content
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index c5b1897c492..e759c87bda7 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -30,7 +30,7 @@
%li CI variables
%li Any encrypted tokens
%p
- Once the exported file is ready, you will receive a notification email with a download link.
+ Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_project_path
= link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 03ab1bb59e4..5d48a35dc4c 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -1,5 +1,5 @@
#modal-create-new-dir.modal
- .modal-dialog
+ .modal-dialog.modal-lg
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 05b7dfe2872..21b6aa4bad9 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -1,5 +1,5 @@
#modal-upload-blob.modal
- .modal-dialog
+ .modal-dialog.modal-lg
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 8e8c911185a..a94d9c14722 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -10,37 +10,28 @@
- if can_create_issue
%li
- = link_to new_project_issue_path(@project) do
- #{ _('New issue') }
-
+ = link_to _('New issue'), new_project_issue_path(@project)
- if merge_project
%li
- = link_to project_new_merge_request_path(merge_project) do
- #{ _('New merge request') }
-
+ = link_to _('New merge request'), project_new_merge_request_path(merge_project)
- if can_create_snippet
%li
- = link_to new_project_snippet_path(@project) do
- #{ _('New snippet') }
+ = link_to _('New snippet'), new_project_snippet_path(@project)
- if can_create_issue || merge_project || can_create_snippet
%li.divider
- if can?(current_user, :push_code, @project)
%li
- = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
- #{ _('New file') }
+ = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
%li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
+ = link_to _('New branch'), new_project_branch_path(@project)
%li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ = link_to _('New tag'), new_project_tag_path(@project)
- elsif current_user && current_user.already_forked?(@project)
%li
- = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
- #{ _('New file') }
+ = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif can?(current_user, :fork_project, @project)
%li
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
@@ -48,5 +39,4 @@
notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New file') }
+ = link_to _('New file'), fork_path, method: :post
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 09934c09865..461129a3e0e 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -64,6 +64,12 @@
.commit-info.branches
%i.fa.fa-spinner.fa-spin
+ .well-segment.merge-request-info
+ .icon-container
+ = custom_icon('mr_bold')
+ %span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
+ = icon('spinner spin')
+
- if @commit.last_pipeline
- last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 111cbcda266..21a4702a2a9 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
- = custom_icon('icon_fork')
+ = sprite_icon('fork', size: 12)
%span Fork
- else
= link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
- = custom_icon('icon_fork')
+ = sprite_icon('fork', size: 12)
%span Fork
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
index c66313bdbf3..311934d9c33 100644
--- a/app/views/projects/jobs/_empty_state.html.haml
+++ b/app/views/projects/jobs/_empty_state.html.haml
@@ -1,7 +1,7 @@
- illustration = local_assigns.fetch(:illustration)
- illustration_size = local_assigns.fetch(:illustration_size)
- title = local_assigns.fetch(:title)
-- content = local_assigns.fetch(:content)
+- content = local_assigns.fetch(:content, nil)
- action = local_assigns.fetch(:action, nil)
.row.empty-state
@@ -11,7 +11,8 @@
.col-xs-12
.text-content
%h4.text-center= title
- %p= content
+ - if content
+ %p= content
- if action
.text-center
= action
diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml
index 82806f022ee..d124d3ebfc1 100644
--- a/app/views/projects/jobs/_table.html.haml
+++ b/app/views/projects/jobs/_table.html.haml
@@ -22,4 +22,4 @@
= render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, pipeline_link: true, stage: true, allow_retry: true, admin: admin }
- = paginate builds, theme: 'gitlab'
+ = paginate_collection(builds)
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 8b05440fc78..1e6d6f67e66 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -93,14 +93,13 @@
illustration: 'illustrations/manual_action.svg',
illustration_size: 'svg-394',
title: _('This job requires a manual action'),
- content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.'),
- action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), class: 'btn btn-primary', title: _('Trigger this manual action') )
+ content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'),
+ action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') )
- else
= render 'empty_state',
illustration: 'illustrations/job_not_triggered.svg',
illustration_size: 'svg-306',
- title: _('This job has not been triggered yet'),
- content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
+ title: _('This job has not been triggered yet')
= render "sidebar"
diff --git a/app/views/projects/merge_requests/creations/_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml
index 627fc4e9671..5b70e894b39 100644
--- a/app/views/projects/merge_requests/creations/_diffs.html.haml
+++ b/app/views/projects/merge_requests/creations/_diffs.html.haml
@@ -1 +1,5 @@
-= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false
+- if @merge_request.can_be_created
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false
+- else
+ .nothing-here-block
+ This merge request cannot be created.
diff --git a/app/views/projects/protected_branches/shared/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml
index 6e9c473494e..74435236808 100644
--- a/app/views/projects/protected_branches/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml
@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
- %button{ class: "create-new-protected-branch-button js-create-new-protected-branch", title: "New Protected Branch" }
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Branch" }
Create wildcard
%code
diff --git a/app/views/projects/protected_tags/shared/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml
index 9b6923210f7..f0d7dcccd36 100644
--- a/app/views/projects/protected_tags/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml
@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
- %button{ class: "create-new-protected-tag-button js-create-new-protected-tag", title: "New Protected Tag" }
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Tag" }
Create wildcard
%code
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index a4e820628f3..67607e4e9c6 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -23,6 +23,3 @@
%h4.underlined-title Available shared Runners : #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
= render partial: 'projects/runners/runner', collection: @shared_runners, as: :runner
- - if @shared_runners_count > 10
- .light
- and #{@shared_runners_count - 10} more...
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 63aa4e29ec9..2a75b46d376 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -52,7 +52,7 @@
= render_project_pipeline_status(project.pipeline_status)
- if forks
%span.prepend-left-10
- = sprite_icon('fork')
+ = sprite_icon('fork', size: 12)
= number_with_delimiter(project.forks_count)
- if stars
%span.prepend-left-10
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index d1c57b82681..07584fab7c8 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -17,10 +17,7 @@ class RepositoryForkWorker
project.repository_storage_path, project.disk_path)
raise "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result
- project.repository.after_import
- raise "Project #{project_id} had an invalid repository after fork" unless project.valid_repo?
-
- project.import_finish
+ project.after_import
end
private