summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2018-01-16 23:29:13 +1100
committerSimon Knox <psimyn@gmail.com>2018-01-16 23:29:13 +1100
commit5816a42ed20829968cf64b08fc2bd9c2c4768079 (patch)
tree825fd95ad2dad2ea304ab7154564cd0b919f85be /app
parent81660bc35010307d472ea4283d6046661e22a1ca (diff)
parentbe353219d1eec05a7b04e9c2e94f61573889549d (diff)
downloadgitlab-ce-41874-closed-todo.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into 41874-closed-todo41874-closed-todo
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.js159
-rw-r--r--app/assets/javascripts/init_labels.js18
-rw-r--r--app/assets/javascripts/jobs/components/header.vue6
-rw-r--r--app/assets/javascripts/merge_request.js8
-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/branches/new/index.js3
-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/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/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/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/stacked-progress-bar.scss54
-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/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/repository.rb36
-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/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml12
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-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/workers/repository_fork_worker.rb5
72 files changed, 839 insertions, 355 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 6880784d7d0..0cd2d5f6fef 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,45 +1,37 @@
/* 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 Group from './group';
import ProjectsList from './projects_list';
-import setupProjectEdit from './project_edit';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
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 Star from './star';
import TreeView from './tree';
@@ -58,16 +50,15 @@ 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';
import Activities from './activities';
+import { fetchCommitMergeRequests } from './commit_merge_requests';
(function() {
var Dispatcher;
@@ -121,22 +112,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')
@@ -193,18 +184,28 @@ 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();
@@ -212,8 +213,14 @@ import Activities from './activities';
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
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':
import('./pages/projects/branches/index')
@@ -221,13 +228,16 @@ import Activities from './activities';
.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');
@@ -255,9 +265,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();
@@ -271,11 +281,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();
@@ -311,6 +334,7 @@ import Activities from './activities';
const stickyBarPaddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ fetchCommitMergeRequests();
break;
case 'projects:commit:pipelines':
new MiniPipelineGraph({
@@ -345,12 +369,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':
@@ -410,22 +436,15 @@ 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':
@@ -442,12 +461,24 @@ import Activities from './activities';
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();
}
@@ -467,7 +498,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;
@@ -524,7 +555,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);
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..321a4872ccc 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: {
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index e0360b104ca..bedd50de1bb 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -111,7 +111,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
});
};
-MergeRequest.prototype.setStatusBoxToMerged = function() {
+MergeRequest.setStatusBoxToMerged = function() {
$('.detail-page-header .status-box')
.removeClass('status-box-open')
.addClass('status-box-mr-merged')
@@ -119,14 +119,14 @@ MergeRequest.prototype.setStatusBoxToMerged = function() {
.text(__('Merged'));
};
-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/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/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/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/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/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 ad278140e15..f16414ad5c0 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.setStatusBoxToMerged();
- window.mergeRequest.hideCloseButton();
- window.mergeRequest.decreaseCounter();
- }
+ MergeRequest.setStatusBoxToMerged();
+ 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/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/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/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/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/repository.rb b/app/models/repository.rb
index d27212b2058..a67bb7294e6 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)
@@ -936,9 +938,11 @@ class Repository
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/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/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/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/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/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