diff options
author | Constance Okoghenun <cokoghenun@gitlab.com> | 2018-02-27 22:35:30 +0300 |
---|---|---|
committer | Constance Okoghenun <cokoghenun@gitlab.com> | 2018-02-27 22:35:30 +0300 |
commit | cfe4c4d984c7eeea3f374a4d4d709810aac6986b (patch) | |
tree | 8de5e4cf38aae4490e6284efd0d48b49ce5a1b3a /app | |
parent | e7c8f8fb43d2d56c3db7cd09787f4c32077afc4a (diff) | |
parent | d4867c518f2d72125c619c191fa9577dabb1ae9a (diff) |
Resolved conflicts in dispatcher.js
Diffstat (limited to 'app')
78 files changed, 443 insertions, 530 deletions
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 06a8abea940..8e31f1865f0 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -2,11 +2,13 @@ import _ from 'underscore'; import Vue from 'vue'; -import Flash from '../flash'; -import { __ } from '../locale'; + +import Flash from '~/flash'; +import { __ } from '~/locale'; + import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; -import sidebarEventHub from '../sidebar/event_hub'; +import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first import './models/issue'; import './models/label'; import './models/list'; @@ -22,7 +24,7 @@ import './components/board'; import './components/board_sidebar'; import './components/new_list_dropdown'; import './components/modal/index'; -import '../vue_shared/vue_resource_interceptor'; +import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first export default () => { const $boardApp = document.getElementById('board-app'); diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 798d7e0d147..348cdeec737 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -2,7 +2,7 @@ /* global List */ import _ from 'underscore'; import Cookies from 'js-cookie'; -import { getUrlParamsArray } from '../../lib/utils/common_utils'; +import { getUrlParamsArray } from '~/lib/utils/common_utils'; window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 1f9153d95bd..3d89bf1316e 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -15,7 +15,7 @@ const CommitPipelinesTable = Vue.extend(commitPipelinesTable); window.gl = window.gl || {}; window.gl.CommitPipelinesTable = CommitPipelinesTable; -document.addEventListener('DOMContentLoaded', () => { +export default () => { const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); if (pipelineTableViewEl) { @@ -43,4 +43,4 @@ document.addEventListener('DOMContentLoaded', () => { pipelineTableViewEl.appendChild(table.$el); } } -}); +}; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 95ba00bb49a..1ccf96a75dc 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -6,172 +6,80 @@ import GlFieldErrors from './gl_field_errors'; import Shortcuts from './shortcuts'; import SearchAutocomplete from './search_autocomplete'; -var Dispatcher; - -(function() { - Dispatcher = (function() { - function Dispatcher() { - this.initSearch(); - this.initFieldErrors(); - this.initPageScripts(); - } - - Dispatcher.prototype.initPageScripts = function() { - var path, shortcut_handler; - const page = $('body').attr('data-page'); - if (!page) { - return false; - } - - const fail = () => Flash('Error loading dynamic module'); - const callDefault = m => m.default(); - - path = page.split(':'); - shortcut_handler = null; - - $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { - const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); - const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); - gfm.setup($(el), { - emojis: true, - members: enableGFM, - issues: enableGFM, - milestones: enableGFM, - mergeRequests: enableGFM, - labels: enableGFM, - }); - }); - - const shortcutHandlerPages = [ - 'projects:activity', - 'projects:artifacts:browse', - 'projects:artifacts:file', - 'projects:blame:show', - 'projects:blob:show', - 'projects:commit:show', - 'projects:commits:show', - 'projects:find_file:show', - 'projects:issues:edit', - 'projects:issues:index', - 'projects:issues:new', - 'projects:issues:show', - 'projects:merge_requests:creations:diffs', - 'projects:merge_requests:creations:new', - 'projects:merge_requests:edit', - 'projects:merge_requests:index', - 'projects:merge_requests:show', - 'projects:network:show', - 'projects:show', - 'projects:tree:show', - 'groups:show', - ]; +function initSearch() { + // Only when search form is present + if ($('.search').length) { + return new SearchAutocomplete(); + } +} - if (shortcutHandlerPages.indexOf(page) !== -1) { - shortcut_handler = true; - } +function initFieldErrors() { + $('.gl-show-field-errors').each((i, form) => { + new GlFieldErrors(form); + }); +} - switch (path[0]) { - case 'admin': - switch (path[1]) { - case 'broadcast_messages': - import('./pages/admin/broadcast_messages') - .then(callDefault) - .catch(fail); - break; - case 'cohorts': - import('./pages/admin/cohorts') - .then(callDefault) - .catch(fail); - break; - case 'groups': - switch (path[2]) { - case 'show': - import('./pages/admin/groups/show') - .then(callDefault) - .catch(fail); - break; - } - break; - case 'projects': - import('./pages/admin/projects') - .then(callDefault) - .catch(fail); - break; - case 'labels': - switch (path[2]) { - case 'new': - import('./pages/admin/labels/new') - .then(callDefault) - .catch(fail); - break; - case 'edit': - import('./pages/admin/labels/edit') - .then(callDefault) - .catch(fail); - break; - } - case 'abuse_reports': - import('./pages/admin/abuse_reports') - .then(callDefault) - .catch(fail); - break; - } - break; - case 'projects': - import('./pages/projects') - .then(callDefault) - .catch(fail); - shortcut_handler = true; - switch (path[1]) { - case 'compare': - import('./pages/projects/compare') - .then(callDefault) - .catch(fail); - break; - case 'create': - case 'new': - import('./pages/projects/new') - .then(callDefault) - .catch(fail); - break; - case 'wikis': - import('./pages/projects/wikis') - .then(callDefault) - .catch(fail); - shortcut_handler = true; - break; - } - break; - } - // If we haven't installed a custom shortcut handler, install the default one - if (!shortcut_handler) { - new Shortcuts(); - } +function initPageShortcuts(page) { + const pagesWithCustomShortcuts = [ + 'projects:activity', + 'projects:artifacts:browse', + 'projects:artifacts:file', + 'projects:blame:show', + 'projects:blob:show', + 'projects:commit:show', + 'projects:commits:show', + 'projects:find_file:show', + 'projects:issues:edit', + 'projects:issues:index', + 'projects:issues:new', + 'projects:issues:show', + 'projects:merge_requests:creations:diffs', + 'projects:merge_requests:creations:new', + 'projects:merge_requests:edit', + 'projects:merge_requests:index', + 'projects:merge_requests:show', + 'projects:network:show', + 'projects:show', + 'projects:tree:show', + 'groups:show', + ]; - if (document.querySelector('#peek')) { - import('./performance_bar') - .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap - .catch(fail); - } - }; + if (pagesWithCustomShortcuts.indexOf(page) === -1) { + new Shortcuts(); + } +} - Dispatcher.prototype.initSearch = function() { - // Only when search form is present - if ($('.search').length) { - return new SearchAutocomplete(); - } - }; +function initGFMInput() { + $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { + const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); + const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); + gfm.setup($(el), { + emojis: true, + members: enableGFM, + issues: enableGFM, + milestones: enableGFM, + mergeRequests: enableGFM, + labels: enableGFM, + }); + }); +} - Dispatcher.prototype.initFieldErrors = function() { - $('.gl-show-field-errors').each((i, form) => { - new GlFieldErrors(form); - }); - }; +function initPerformanceBar() { + if (document.querySelector('#peek')) { + import('./performance_bar') + .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap + .catch(() => Flash('Error loading performance bar module')); + } +} - return Dispatcher; - })(); -})(); +export default () => { + initSearch(); + initFieldErrors(); -export default function initDispatcher() { - return new Dispatcher(); -} + const page = $('body').attr('data-page'); + if (page) { + initPageShortcuts(page); + initGFMInput(); + initPerformanceBar(); + } +}; diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index b4eca47957e..22863e926d4 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -2,8 +2,8 @@ /** * Render environments table. */ +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import environmentItem from './environment_item.vue'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { components: { diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 5d2d14c7682..de0fbdb2e91 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -5,7 +5,7 @@ import Translate from '../../vue_shared/translate'; Vue.use(Translate); -document.addEventListener('DOMContentLoaded', () => new Vue({ +export default () => new Vue({ el: '#environments-folder-list-view', components: { environmentsFolderApp, @@ -32,4 +32,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ }, }); }, -})); +}); diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 110918872fb..cbbab765e1c 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -1,9 +1,9 @@ <script> import { mapState } from 'vuex'; - import timeAgoMixin from '../../vue_shared/mixins/timeago'; - import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; + import timeAgoMixin from '~/vue_shared/mixins/timeago'; + import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; + import fileIcon from '~/vue_shared/components/file_icon.vue'; import newDropdown from './new_dropdown/index.vue'; - import fileIcon from '../../vue_shared/components/file_icon.vue'; export default { components: { diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index 5ed7bddf6ae..5656081c598 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -1,6 +1,6 @@ <script> import { mapActions } from 'vuex'; - import fileIcon from '../../vue_shared/components/file_icon.vue'; + import fileIcon from '~/vue_shared/components/file_icon.vue'; export default { components: { diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 7151ac05a09..89a246f56cf 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -242,10 +242,16 @@ export default class LabelsSelect { filterable: true, selected: $dropdown.data('selected') || [], toggleLabel: function(selected, el) { + var $dropdownParent = $dropdown.parent(); + var $dropdownInputField = $dropdownParent.find('.dropdown-input-field'); var isSelected = el !== null ? el.hasClass('is-active') : false; var title = selected.title; var selectedLabels = this.selected; + if ($dropdownInputField.length && $dropdownInputField.val().length) { + $dropdownParent.find('.dropdown-input-clear').trigger('click'); + } + if (selected.id === 0) { this.selected = []; return 'No Label'; diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js deleted file mode 100644 index 129f1724cb8..00000000000 --- a/app/assets/javascripts/network/network_bundle.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, max-len */ - -import ShortcutsNetwork from '../shortcuts_network'; -import Network from './network'; - -$(function() { - if (!$(".network-graph").length) return; - - var network_graph; - network_graph = new Network({ - url: $(".network-graph").attr('data-url'), - commit_url: $(".network-graph").attr('data-commit-url'), - ref: $(".network-graph").attr('data-ref'), - commit_id: $(".network-graph").attr('data-commit-id') - }); - return new ShortcutsNetwork(network_graph.branch_graph); -}); diff --git a/app/assets/javascripts/pages/admin/abuse_reports/index.js b/app/assets/javascripts/pages/admin/abuse_reports/index.js index c0b6e8d4095..d76b1f174fc 100644 --- a/app/assets/javascripts/pages/admin/abuse_reports/index.js +++ b/app/assets/javascripts/pages/admin/abuse_reports/index.js @@ -1,3 +1,3 @@ import AbuseReports from './abuse_reports'; -export default () => new AbuseReports(); +document.addEventListener('DOMContentLoaded', () => new AbuseReports()); diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js index b68ce5d32d8..f92450cbaa7 100644 --- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js +++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js @@ -3,7 +3,7 @@ import axios from '~/lib/utils/axios_utils'; import flash from '~/flash'; import { __ } from '~/locale'; -export default function initBroadcastMessagesForm() { +export default () => { $('input#broadcast_message_color').on('input', function onMessageColorInput() { const previewColor = $(this).val(); $('div.broadcast-message-preview').css('background-color', previewColor); @@ -32,4 +32,4 @@ export default function initBroadcastMessagesForm() { .catch(() => flash(__('An error occurred while rendering preview broadcast message'))); } }, 250)); -} +}; diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/index.js b/app/assets/javascripts/pages/admin/broadcast_messages/index.js index b548c48282a..d6cc6a850eb 100644 --- a/app/assets/javascripts/pages/admin/broadcast_messages/index.js +++ b/app/assets/javascripts/pages/admin/broadcast_messages/index.js @@ -1,3 +1,3 @@ import initBroadcastMessagesForm from './broadcast_message'; -export default () => initBroadcastMessagesForm(); +document.addEventListener('DOMContentLoaded', initBroadcastMessagesForm); diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/admin/cohorts/index.js index 42ef9d38ef7..2d5020dbef4 100644 --- a/app/assets/javascripts/pages/admin/cohorts/index.js +++ b/app/assets/javascripts/pages/admin/cohorts/index.js @@ -1,3 +1,3 @@ import initUsagePing from './usage_ping'; -export default () => initUsagePing(); +document.addEventListener('DOMContentLoaded', initUsagePing); diff --git a/app/assets/javascripts/pages/admin/groups/show/index.js b/app/assets/javascripts/pages/admin/groups/show/index.js index 5defea104d4..b0cdad627a6 100644 --- a/app/assets/javascripts/pages/admin/groups/show/index.js +++ b/app/assets/javascripts/pages/admin/groups/show/index.js @@ -1,3 +1,3 @@ import UsersSelect from '../../../../users_select'; -export default () => new UsersSelect(); +document.addEventListener('DOMContentLoaded', () => new UsersSelect()); diff --git a/app/assets/javascripts/pages/admin/labels/edit/index.js b/app/assets/javascripts/pages/admin/labels/edit/index.js index d7ec6e47f67..5de1d4d6344 100644 --- a/app/assets/javascripts/pages/admin/labels/edit/index.js +++ b/app/assets/javascripts/pages/admin/labels/edit/index.js @@ -1,3 +1,3 @@ import Labels from '../../../../labels'; -export default () => new Labels(); +document.addEventListener('DOMContentLoaded', () => new Labels()); diff --git a/app/assets/javascripts/pages/admin/labels/new/index.js b/app/assets/javascripts/pages/admin/labels/new/index.js index d7ec6e47f67..5de1d4d6344 100644 --- a/app/assets/javascripts/pages/admin/labels/new/index.js +++ b/app/assets/javascripts/pages/admin/labels/new/index.js @@ -1,3 +1,3 @@ import Labels from '../../../../labels'; -export default () => new Labels(); +document.addEventListener('DOMContentLoaded', () => new Labels()); diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js index 71e0ddcd7b6..31c96eb87af 100644 --- a/app/assets/javascripts/pages/admin/projects/index.js +++ b/app/assets/javascripts/pages/admin/projects/index.js @@ -1,9 +1,9 @@ import ProjectsList from '../../../projects_list'; import NamespaceSelect from '../../../namespace_select'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new ProjectsList(); // eslint-disable-line no-new document.querySelectorAll('.js-namespace-select') .forEach(dropdown => new NamespaceSelect({ dropdown })); -}; +}); diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js index 7889704a324..cd923f13ce8 100644 --- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js +++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js @@ -1,8 +1,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; +import initPipelines from '~/commit/pipelines/pipelines_bundle'; document.addEventListener('DOMContentLoaded', () => { new MiniPipelineGraph({ container: '.js-commit-pipeline-graph', }).bindEvents(); $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + initPipelines(); }); diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index 460a54ab504..1aeed197385 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -5,6 +5,7 @@ import ShortcutsNavigation from '~/shortcuts_navigation'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import initNotes from '~/init_notes'; import initChangesDropdown from '~/init_changes_dropdown'; +import initDiffNotes from '~/diff_notes/diff_notes_bundle'; import { fetchCommitMergeRequests } from '~/commit_merge_requests'; document.addEventListener('DOMContentLoaded', () => { @@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => { initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); fetchCommitMergeRequests(); + initDiffNotes(); }); diff --git a/app/assets/javascripts/pages/projects/compare/index.js b/app/assets/javascripts/pages/projects/compare/index.js index 890062eeee6..d1c78bd61db 100644 --- a/app/assets/javascripts/pages/projects/compare/index.js +++ b/app/assets/javascripts/pages/projects/compare/index.js @@ -1,5 +1,3 @@ import initCompareAutocomplete from '~/compare_autocomplete'; -export default () => { - initCompareAutocomplete(); -}; +document.addEventListener('DOMContentLoaded', initCompareAutocomplete); diff --git a/app/assets/javascripts/pages/projects/environments/folder/index.js b/app/assets/javascripts/pages/projects/environments/folder/index.js new file mode 100644 index 00000000000..5feaf944038 --- /dev/null +++ b/app/assets/javascripts/pages/projects/environments/folder/index.js @@ -0,0 +1,3 @@ +import initEnvironmentsFolderBundle from '~/environments/folder/environments_folder_bundle'; + +document.addEventListener('DOMContentLoaded', initEnvironmentsFolderBundle); diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 9b1d52692a3..de1e13de7e9 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,7 +1,7 @@ import Project from './project'; import ShortcutsNavigation from '../../shortcuts_navigation'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new Project(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new -}; +}); diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js index 1e56aa58da2..3950fe0a1fd 100644 --- a/app/assets/javascripts/pages/projects/issues/show/index.js +++ b/app/assets/javascripts/pages/projects/issues/show/index.js @@ -1,4 +1,5 @@ import initIssuableSidebar from '~/init_issuable_sidebar'; +import initSidebarBundle from '~/sidebar/sidebar_bundle'; import Issue from '~/issue'; import ShortcutsIssuable from '~/shortcuts_issuable'; import ZenMode from '~/zen_mode'; @@ -10,4 +11,5 @@ document.addEventListener('DOMContentLoaded', () => { new ShortcutsIssuable(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new initIssuableSidebar(); + initSidebarBundle(); }); diff --git a/app/assets/javascripts/pages/projects/merge_requests/conflicts/index.js b/app/assets/javascripts/pages/projects/merge_requests/conflicts/index.js new file mode 100644 index 00000000000..b92aef53510 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/conflicts/index.js @@ -0,0 +1,3 @@ +import initSidebarBundle from '~/sidebar/sidebar_bundle'; + +document.addEventListener('DOMContentLoaded', initSidebarBundle); diff --git a/app/assets/javascripts/pages/projects/merge_requests/conflicts/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/conflicts/show/index.js new file mode 100644 index 00000000000..b92aef53510 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/conflicts/show/index.js @@ -0,0 +1,3 @@ +import initSidebarBundle from '~/sidebar/sidebar_bundle'; + +document.addEventListener('DOMContentLoaded', initSidebarBundle); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js index 1d5aec4001d..6c9afddefac 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js @@ -1,5 +1,6 @@ import Compare from '~/compare'; import MergeRequest from '~/merge_request'; +import initPipelines from '~/commit/pipelines/pipelines_bundle'; document.addEventListener('DOMContentLoaded', () => { const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); @@ -14,5 +15,6 @@ document.addEventListener('DOMContentLoaded', () => { new MergeRequest({ // eslint-disable-line no-new action: mrNewSubmitNode.dataset.mrSubmitAction, }); + initPipelines(); } }); diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js index 07f3e579c97..b7b6b0b5364 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js @@ -3,18 +3,23 @@ import ZenMode from '~/zen_mode'; import initNotes from '~/init_notes'; import initIssuableSidebar from '~/init_issuable_sidebar'; import initDiffNotes from '~/diff_notes/diff_notes_bundle'; +import initSidebarBundle from '~/sidebar/sidebar_bundle'; import ShortcutsIssuable from '~/shortcuts_issuable'; import Diff from '~/diff'; import { handleLocationHash } from '~/lib/utils/common_utils'; import howToMerge from '~/how_to_merge'; +import initPipelines from '~/commit/pipelines/pipelines_bundle'; +import initWidget from '../../../../vue_merge_request_widget'; document.addEventListener('DOMContentLoaded', () => { new Diff(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new initIssuableSidebar(); + initSidebarBundle(); initNotes(); initDiffNotes(); + initPipelines(); const mrShowNode = document.querySelector('.merge-request'); @@ -25,4 +30,5 @@ document.addEventListener('DOMContentLoaded', () => { new ShortcutsIssuable(true); // eslint-disable-line no-new handleLocationHash(); howToMerge(); + initWidget(); }); diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/pages/projects/network/network.js index a3fd22aff2a..7354243e4c8 100644 --- a/app/assets/javascripts/network/network.js +++ b/app/assets/javascripts/pages/projects/network/network.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */ -import BranchGraph from './branch_graph'; +import BranchGraph from '../../../network/branch_graph'; export default (function() { function Network(opts) { diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js new file mode 100644 index 00000000000..e7dfd2d0128 --- /dev/null +++ b/app/assets/javascripts/pages/projects/network/show/index.js @@ -0,0 +1,16 @@ +import ShortcutsNetwork from '../../../../shortcuts_network'; +import Network from '../network'; + +document.addEventListener('DOMContentLoaded', () => { + if (!$('.network-graph').length) return; + + const networkGraph = new Network({ + url: $('.network-graph').attr('data-url'), + commit_url: $('.network-graph').attr('data-commit-url'), + ref: $('.network-graph').attr('data-ref'), + commit_id: $('.network-graph').attr('data-commit-id'), + }); + + // eslint-disable-next-line no-new + new ShortcutsNetwork(networkGraph.branch_graph); +}); diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js index 71c49deb9d0..ea6fd961393 100644 --- a/app/assets/javascripts/pages/projects/new/index.js +++ b/app/assets/javascripts/pages/projects/new/index.js @@ -2,8 +2,8 @@ import ProjectNew from '../shared/project_new'; import initProjectVisibilitySelector from '../../../project_visibility'; import initProjectNew from '../../../projects/project_new'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new ProjectNew(); // eslint-disable-line no-new initProjectVisibilitySelector(); initProjectNew.bindEvents(); -}; +}); diff --git a/app/assets/javascripts/pipelines/pipelines_bundle.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index ab5596e70f0..25dfa99ad9c 100644 --- a/app/assets/javascripts/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; -import PipelinesStore from './stores/pipelines_store'; -import pipelinesComponent from './components/pipelines.vue'; -import Translate from '../vue_shared/translate'; +import PipelinesStore from '../../../../pipelines/stores/pipelines_store'; +import pipelinesComponent from '../../../../pipelines/components/pipelines.vue'; +import Translate from '../../../../vue_shared/translate'; Vue.use(Translate); diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index eb14c7a0e78..b9f8707fd6e 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -3,9 +3,9 @@ import ShortcutsWiki from '../../../shortcuts_wiki'; import ZenMode from '../../../zen_mode'; import GLForm from '../../../gl_form'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new Wikis(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new new GLForm($('.wiki-form'), true); // eslint-disable-line no-new -}; +}); diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js index e2285494e62..47736fc5f42 100644 --- a/app/assets/javascripts/pipelines/services/pipelines_service.js +++ b/app/assets/javascripts/pipelines/services/pipelines_service.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ import Vue from 'vue'; import VueResource from 'vue-resource'; +import '../../vue_shared/vue_resource_interceptor'; Vue.use(VueResource); diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js index 04c39d7b6b5..377846db70e 100644 --- a/app/assets/javascripts/sidebar/sidebar_bundle.js +++ b/app/assets/javascripts/sidebar/sidebar_bundle.js @@ -1,13 +1,9 @@ import Mediator from './sidebar_mediator'; import { mountSidebar, getSidebarOptions } from './mount_sidebar'; -function domContentLoaded() { +export default () => { const mediator = new Mediator(getSidebarOptions()); mediator.fetch(); mountSidebar(mediator); -} - -document.addEventListener('DOMContentLoaded', domContentLoaded); - -export default domContentLoaded; +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 109a302a172..54a98abf860 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -1,8 +1,8 @@ <script> /* eslint-disable vue/require-default-prop */ - import pipelineStage from '../../pipelines/components/stage.vue'; - import ciIcon from '../../vue_shared/components/ci_icon.vue'; - import icon from '../../vue_shared/components/icon.vue'; + import pipelineStage from '~/pipelines/components/stage.vue'; + import ciIcon from '~/vue_shared/components/ci_icon.vue'; + import icon from '~/vue_shared/components/icon.vue'; export default { name: 'MRWidgetPipeline', diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index 6b9918b65b0..69a9132a2da 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -6,7 +6,7 @@ import Translate from '../vue_shared/translate'; Vue.use(Translate); -document.addEventListener('DOMContentLoaded', () => { +export default () => { gl.mrWidgetData.gitlabLogo = gon.gitlab_logo; const vm = new Vue(mrWidgetOptions); @@ -14,4 +14,4 @@ document.addEventListener('DOMContentLoaded', () => { window.gl.mrWidget = { checkStatus: vm.checkStatus, }; -}); +}; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 17801ed5910..8b680c2dc52 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -196,17 +196,9 @@ @media (min-width: $screen-sm-min) { font-size: 0; - div { - display: inline; - } - .fa-spinner { font-size: 12px; } - - span { - font-size: 6px; - } } .ci-status-link { diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index a94726887d9..cc38608eda5 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -48,7 +48,7 @@ class Admin::GroupsController < Admin::ApplicationController def members_update member_params = params.permit(:user_ids, :access_level, :expires_at) - result = Members::CreateService.new(@group, current_user, member_params.merge(limit: -1)).execute + result = Members::CreateService.new(current_user, member_params.merge(limit: -1)).execute(@group) if result[:status] == :success redirect_to [:admin, @group], notice: 'Users were successfully added.' diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index c6b1e443de6..7a6a00b8e13 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -3,20 +3,31 @@ module MembershipActions def create create_params = params.permit(:user_ids, :access_level, :expires_at) - result = Members::CreateService.new(membershipable, current_user, create_params).execute - - redirect_url = members_page_url + result = Members::CreateService.new(current_user, create_params).execute(membershipable) if result[:status] == :success - redirect_to redirect_url, notice: 'Users were successfully added.' + redirect_to members_page_url, notice: 'Users were successfully added.' else - redirect_to redirect_url, alert: result[:message] + redirect_to members_page_url, alert: result[:message] + end + end + + def update + update_params = params.require(root_params_key).permit(:access_level, :expires_at) + member = membershipable.members_and_requesters.find(params[:id]) + member = Members::UpdateService + .new(current_user, update_params) + .execute(member) + .present(current_user: current_user) + + respond_to do |format| + format.js { render 'shared/members/update', locals: { member: member } } end end def destroy - Members::DestroyService.new(membershipable, current_user, params) - .execute(:all) + member = membershipable.members_and_requesters.find(params[:id]) + Members::DestroyService.new(current_user).execute(member) respond_to do |format| format.html do @@ -36,14 +47,17 @@ module MembershipActions end def approve_access_request - Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute + access_requester = membershipable.requesters.find(params[:id]) + Members::ApproveAccessRequestService + .new(current_user, params) + .execute(access_requester) redirect_to members_page_url end def leave - member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id) - .execute(:all) + member = membershipable.members_and_requesters.find_by!(user_id: current_user.id) + Members::DestroyService.new(current_user).execute(member) notice = if member.request? @@ -62,17 +76,43 @@ module MembershipActions end end + def resend_invite + member = membershipable.members.find(params[:id]) + + if member.invite? + member.resend_invite + + redirect_to members_page_url, notice: 'The invitation was successfully resent.' + else + redirect_to members_page_url, alert: 'The invitation has already been accepted.' + end + end + protected def membershipable raise NotImplementedError end + def root_params_key + case membershipable + when Namespace + :group_member + when Project + :project_member + else + raise "Unknown membershipable type: #{membershipable}!" + end + end + def members_page_url - if membershipable.is_a?(Project) + case membershipable + when Namespace + polymorphic_url([membershipable, :members]) + when Project project_project_members_path(membershipable) else - polymorphic_url([membershipable, :members]) + raise "Unknown membershipable type: #{membershipable}!" end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 4a2bfc1f887..9f3bb60b4cc 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -18,10 +18,6 @@ class Groups::ApplicationController < ApplicationController @projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute end - def group_merge_requests - @group_merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id).execute - end - def authorize_admin_group! unless can?(current_user, :admin_group, group) return render_404 diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 2c371e76313..f210434b2d7 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -27,35 +27,6 @@ class Groups::GroupMembersController < Groups::ApplicationController @group_member = @group.group_members.new end - def update - @group_member = @group.members_and_requesters.find(params[:id]) - .present(current_user: current_user) - - return render_403 unless can?(current_user, :update_group_member, @group_member) - - @group_member.update_attributes(member_params) - end - - def resend_invite - redirect_path = group_group_members_path(@group) - - @group_member = @group.group_members.find(params[:id]) - - if @group_member.invite? - @group_member.resend_invite - - redirect_to redirect_path, notice: 'The invitation was successfully resent.' - else - redirect_to redirect_path, alert: 'The invitation has already been accepted.' - end - end - - protected - - def member_params - params.require(:group_member).permit(:access_level, :user_id, :expires_at) - end - # MembershipActions concern alias_method :membershipable, :group end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 14b9d6c22bd..283c3e5f1e0 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -14,7 +14,6 @@ class GroupsController < Groups::ApplicationController before_action :authorize_create_group!, only: [:new] before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests] - before_action :group_merge_requests, only: [:merge_requests] before_action :event_filter, only: [:activity] before_action :user_actions, only: [:show, :subgroups] diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d7372beb9d3..e9b4679f94c 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -26,29 +26,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_member = @project.project_members.new end - def update - @project_member = @project.members_and_requesters.find(params[:id]) - .present(current_user: current_user) - - return render_403 unless can?(current_user, :update_project_member, @project_member) - - @project_member.update_attributes(member_params) - end - - def resend_invite - redirect_path = project_project_members_path(@project) - - @project_member = @project.project_members.find(params[:id]) - - if @project_member.invite? - @project_member.resend_invite - - redirect_to redirect_path, notice: 'The invitation was successfully resent.' - else - redirect_to redirect_path, alert: 'The invitation has already been accepted.' - end - end - def import @projects = current_user.authorized_projects.order_id_desc end @@ -67,12 +44,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end - protected - - def member_params - params.require(:project_member).permit(:user_id, :access_level, :expires_at) - end - # MembershipActions concern alias_method :membershipable, :project end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 5fbaa17c40e..7910de73c52 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -19,6 +19,20 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end + def group_issues_count(state:) + IssuesFinder + .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) + .execute + .count + end + + def group_merge_requests_count(state:) + MergeRequestsFinder + .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) + .execute + .count + end + def group_icon(group, options = {}) img_path = group_icon_url(group, options) image_tag img_path, options @@ -77,10 +91,6 @@ module GroupsHelper end end - def group_issues(group) - IssuesFinder.new(current_user, group_id: group.id).execute - end - def remove_group_message(group) _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % { group_name: group.name } diff --git a/app/models/chat_name.rb b/app/models/chat_name.rb index f321db75eeb..fbd0f123341 100644 --- a/app/models/chat_name.rb +++ b/app/models/chat_name.rb @@ -1,4 +1,6 @@ class ChatName < ActiveRecord::Base + LAST_USED_AT_INTERVAL = 1.hour + belongs_to :service belongs_to :user @@ -9,4 +11,23 @@ class ChatName < ActiveRecord::Base validates :user_id, uniqueness: { scope: [:service_id] } validates :chat_id, uniqueness: { scope: [:service_id, :team_id] } + + # Updates the "last_used_timestamp" but only if it wasn't already updated + # recently. + # + # The throttling this method uses is put in place to ensure that high chat + # traffic doesn't result in many UPDATE queries being performed. + def update_last_used_at + return unless update_last_used_at? + + obtained = Gitlab::ExclusiveLease + .new("chat_name/last_used_at/#{id}", timeout: LAST_USED_AT_INTERVAL.to_i) + .try_obtain + + touch(:last_used_at) if obtained + end + + def update_last_used_at? + last_used_at.nil? || last_used_at > LAST_USED_AT_INTERVAL.ago + end end diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb index afeae69ba39..1dd0e050ba9 100644 --- a/app/models/ci/group_variable.rb +++ b/app/models/ci/group_variable.rb @@ -6,7 +6,10 @@ module Ci belongs_to :group - validates :key, uniqueness: { scope: :group_id } + validates :key, uniqueness: { + scope: :group_id, + message: "(%{value}) has already been taken" + } scope :unprotected, -> { where(protected: false) } end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 67d3ec81b6f..7c71291de84 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -6,7 +6,10 @@ module Ci belongs_to :project - validates :key, uniqueness: { scope: [:project_id, :environment_scope] } + validates :key, uniqueness: { + scope: [:project_id, :environment_scope], + message: "(%{value}) has already been taken" + } scope :unprotected, -> { where(protected: false) } end diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb index 62bc6b809f4..d502e7e54c6 100644 --- a/app/models/concerns/access_requestable.rb +++ b/app/models/concerns/access_requestable.rb @@ -8,6 +8,6 @@ module AccessRequestable extend ActiveSupport::Concern def request_access(user) - Members::RequestAccessService.new(self, user).execute + Members::RequestAccessService.new(user).execute(self) end end diff --git a/app/models/member.rb b/app/models/member.rb index 2d17795e62d..408e8b2d704 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -128,7 +128,7 @@ class Member < ActiveRecord::Base find_by(invite_token: invite_token) end - def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil) + def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil, ldap: false) # `user` can be either a User object, User ID or an email to be invited member = retrieve_member(source, user, existing_members) access_level = retrieve_access_level(access_level) @@ -143,11 +143,13 @@ class Member < ActiveRecord::Base if member.request? ::Members::ApproveAccessRequestService.new( - source, current_user, - id: member.id, access_level: access_level - ).execute + ).execute( + member, + skip_authorization: ldap, + skip_log_audit_event: ldap + ) else member.save end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index eb4da68bb7e..37ea45109ae 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -30,10 +30,10 @@ class SlashCommandsService < Service def trigger(params) return unless valid_token?(params[:token]) - user = find_chat_user(params) + chat_user = find_chat_user(params) - if user - Gitlab::SlashCommands::Command.new(project, user, params).execute + if chat_user&.user + Gitlab::SlashCommands::Command.new(project, chat_user, params).execute else url = authorize_chat_name_url(params) Gitlab::SlashCommands::Presenters::Access.new(url).authorize diff --git a/app/models/user.rb b/app/models/user.rb index 8610ca27b7f..982080763d2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -431,7 +431,7 @@ class User < ActiveRecord::Base end def self.non_internal - where(Hash[internal_attributes.zip([[false, nil]] * internal_attributes.size)]) + where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND ")) end # diff --git a/app/services/chat_names/find_user_service.rb b/app/services/chat_names/find_user_service.rb index 4f5c5567b42..d458b814183 100644 --- a/app/services/chat_names/find_user_service.rb +++ b/app/services/chat_names/find_user_service.rb @@ -9,8 +9,8 @@ module ChatNames chat_name = find_chat_name return unless chat_name - chat_name.touch(:last_used_at) - chat_name.user + chat_name.update_last_used_at + chat_name end private diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb index 2a2bb0cae5b..6be08b590bc 100644 --- a/app/services/members/approve_access_request_service.rb +++ b/app/services/members/approve_access_request_service.rb @@ -1,51 +1,20 @@ module Members - class ApproveAccessRequestService < BaseService - include MembersHelper - - attr_accessor :source - - # source - The source object that respond to `#requesters` (i.g. project or group) - # current_user - The user that performs the access request approval - # params - A hash of parameters - # :user_id - User ID used to retrieve the access requester - # :id - Member ID used to retrieve the access requester - # :access_level - Optional access level set when the request is accepted - def initialize(source, current_user, params = {}) - @source = source - @current_user = current_user - @params = params.slice(:user_id, :id, :access_level) - end - - # opts - A hash of options - # :force - Bypass permission check: current_user can be nil in that case - def execute(opts = {}) - condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] } - access_requester = source.requesters.find_by!(condition) - - raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester, opts) + class ApproveAccessRequestService < Members::BaseService + def execute(access_requester, skip_authorization: false, skip_log_audit_event: false) + raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_update_access_requester?(access_requester) access_requester.access_level = params[:access_level] if params[:access_level] access_requester.accept_request + after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event) + access_requester end private - def can_update_access_requester?(access_requester, opts = {}) - access_requester && ( - opts[:force] || - can?(current_user, update_member_permission(access_requester), access_requester) - ) - end - - def update_member_permission(member) - case member - when GroupMember - :update_group_member - when ProjectMember - :update_project_member - end + def can_update_access_requester?(access_requester) + can?(current_user, update_member_permission(access_requester), access_requester) end end end diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb deleted file mode 100644 index 2e89f00dad8..00000000000 --- a/app/services/members/authorized_destroy_service.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Members - class AuthorizedDestroyService < BaseService - attr_accessor :member, :user - - def initialize(member, user = nil) - @member, @user = member, user - end - - def execute - return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user) - - Member.transaction do - unassign_issues_and_merge_requests(member) unless member.invite? - member.notification_setting&.destroy - - member.destroy - end - - if member.request? && member.user != user - notification_service.decline_access_request(member) - end - - member - end - - private - - def unassign_issues_and_merge_requests(member) - if member.is_a?(GroupMember) - issues = Issue.unscoped.select(1) - .joins(:project) - .where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id) - - # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...) - IssueAssignee.unscoped - .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues) - .delete_all - - MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id) - .execute - .update_all(assignee_id: nil) - else - project = member.source - - # SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X - issues = Issue.unscoped.select(1) - .where('issues.id = issue_assignees.issue_id') - .where(project_id: project.id) - - # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...) - IssueAssignee.unscoped - .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues) - .delete_all - - project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil) - end - - member.user.invalidate_cache_counts - end - end -end diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb new file mode 100644 index 00000000000..74556fb20cf --- /dev/null +++ b/app/services/members/base_service.rb @@ -0,0 +1,49 @@ +module Members + class BaseService < ::BaseService + # current_user - The user that performs the action + # params - A hash of parameters + def initialize(current_user = nil, params = {}) + @current_user = current_user + @params = params + end + + def after_execute(args) + # overriden in EE::Members modules + end + + private + + def update_member_permission(member) + case member + when GroupMember + :update_group_member + when ProjectMember + :update_project_member + else + raise "Unknown member type: #{member}!" + end + end + + def override_member_permission(member) + case member + when GroupMember + :override_group_member + when ProjectMember + :override_project_member + else + raise "Unknown member type: #{member}!" + end + end + + def action_member_permission(action, member) + case action + when :update + update_member_permission(member) + when :override + override_member_permission(member) + else + raise "Unknown action '#{action}' on #{member}!" + end + end + end +end diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index 26906ae7167..bc6a9405aac 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -1,15 +1,8 @@ module Members - class CreateService < BaseService + class CreateService < Members::BaseService DEFAULT_LIMIT = 100 - def initialize(source, current_user, params = {}) - @source = source - @current_user = current_user - @params = params - @error = nil - end - - def execute + def execute(source) return error('No users specified.') if params[:user_ids].blank? user_ids = params[:user_ids].split(',').uniq @@ -17,13 +10,15 @@ module Members return error("Too many users specified (limit is #{user_limit})") if user_limit && user_ids.size > user_limit - @source.add_users( + members = source.add_users( user_ids, params[:access_level], expires_at: params[:expires_at], current_user: current_user ) + members.each { |member| after_execute(member: member) } + success end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 05b93ac8fdb..b141bfd5fbc 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -1,42 +1,30 @@ module Members - class DestroyService < BaseService - include MembersHelper + class DestroyService < Members::BaseService + def execute(member, skip_authorization: false) + raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member) - attr_accessor :source + return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user) - ALLOWED_SCOPES = %i[members requesters all].freeze + Member.transaction do + unassign_issues_and_merge_requests(member) unless member.invite? + member.notification_setting&.destroy - def initialize(source, current_user, params = {}) - @source = source - @current_user = current_user - @params = params - end - - def execute(scope = :members) - raise "scope :#{scope} is not allowed!" unless ALLOWED_SCOPES.include?(scope) + member.destroy + end - member = find_member!(scope) + if member.request? && member.user != current_user + notification_service.decline_access_request(member) + end - raise Gitlab::Access::AccessDeniedError unless can_destroy_member?(member) + after_execute(member: member) - AuthorizedDestroyService.new(member, current_user).execute + member end private - def find_member!(scope) - condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] } - case scope - when :all - source.members.find_by(condition) || - source.requesters.find_by!(condition) - else - source.public_send(scope).find_by!(condition) # rubocop:disable GitlabSecurity/PublicSend - end - end - def can_destroy_member?(member) - member && can?(current_user, destroy_member_permission(member), member) + can?(current_user, destroy_member_permission(member), member) end def destroy_member_permission(member) @@ -45,7 +33,42 @@ module Members :destroy_group_member when ProjectMember :destroy_project_member + else + raise "Unknown member type: #{member}!" end end + + def unassign_issues_and_merge_requests(member) + if member.is_a?(GroupMember) + issues = Issue.unscoped.select(1) + .joins(:project) + .where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id) + + # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...) + IssueAssignee.unscoped + .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues) + .delete_all + + MergeRequestsFinder.new(current_user, group_id: member.source_id, assignee_id: member.user_id) + .execute + .update_all(assignee_id: nil) + else + project = member.source + + # SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X + issues = Issue.unscoped.select(1) + .where('issues.id = issue_assignees.issue_id') + .where(project_id: project.id) + + # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...) + IssueAssignee.unscoped + .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues) + .delete_all + + project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil) + end + + member.user.invalidate_cache_counts + end end end diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb index 2614153d900..24293b30005 100644 --- a/app/services/members/request_access_service.rb +++ b/app/services/members/request_access_service.rb @@ -1,13 +1,6 @@ module Members - class RequestAccessService < BaseService - attr_accessor :source - - def initialize(source, current_user) - @source = source - @current_user = current_user - end - - def execute + class RequestAccessService < Members::BaseService + def execute(source) raise Gitlab::Access::AccessDeniedError unless can_request_access?(source) source.members.create( @@ -19,7 +12,7 @@ module Members private def can_request_access?(source) - source && can?(current_user, :request_access, source) + can?(current_user, :request_access, source) end end end diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb new file mode 100644 index 00000000000..48b3d59f7bd --- /dev/null +++ b/app/services/members/update_service.rb @@ -0,0 +1,16 @@ +module Members + class UpdateService < Members::BaseService + # returns the updated member + def execute(member, permission: :update) + raise Gitlab::Access::AccessDeniedError unless can?(current_user, action_member_permission(permission, member), member) + + old_access_level = member.human_access + + if member.update_attributes(params) + after_execute(action: permission, old_access_level: old_access_level, member: member) + end + + member + end + end +end diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb index 4bfa3c45303..72660be6c43 100644 --- a/app/validators/variable_duplicates_validator.rb +++ b/app/validators/variable_duplicates_validator.rb @@ -5,6 +5,8 @@ # - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model class VariableDuplicatesValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) + return if record.errors.include?(:"#{attribute}.key") + if options[:scope] scoped = value.group_by do |variable| Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml deleted file mode 100644 index 9d05bff6c4e..00000000000 --- a/app/views/groups/group_members/update.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); - $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); - gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@group_member)}")); diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index f2ae7c52031..ca3f018c5e6 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,12 +1,13 @@ - page_title "Issues" -- group_issues_exists = group_issues(@group).exists? = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' -- if group_issues_exists +- if group_issues_count(state: 'all').zero? + = render 'shared/empty_states/issues', project_select_button: true +- else .top-area = render 'shared/issuable/nav', type: :issues .nav-controls @@ -19,5 +20,3 @@ = render 'shared/issuable/search_bar', type: :issues = render 'shared/issues' -- else - = render 'shared/empty_states/issues', project_select_button: true diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 046b92bd9fb..520fe217ae1 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -3,7 +3,7 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' -- if @group_merge_requests.empty? +- if group_merge_requests_count(state: 'all').zero? = render 'shared/empty_states/merge_requests', project_select_button: true - else .top-area diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 47ae79b7a69..b520f28123f 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,6 +1,5 @@ -- 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 - +- issues_count = group_issues_count(state: 'opened') +- merge_requests_count = group_merge_requests_count(state: 'opened') - issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index'] .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 3f699882c5f..a3fed25af28 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -9,4 +9,3 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') - = webpack_bundle_tag('commit_pipelines') diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 4058e61eb9a..ba86d943967 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -8,7 +8,6 @@ - page_description @commit.description - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') - = webpack_bundle_tag('diff_notes') .container-fluid{ class: [limited_container_width, container_class] } = render "commit_box" diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 6ff7bcae54f..078bd0eee63 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -20,7 +20,7 @@ .avatar-cell.hidden-xs = author_avatar(commit, size: 36) - .commit-detail + .commit-detail.flex-list .commit-content = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title") %span.commit-row-message.visible-xs-inline diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index eca10d99908..d8054dbc372 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -3,7 +3,6 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') - = webpack_bundle_tag("environments_folder") #environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json), "folder-name" => @folder, diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index e29f21b3bec..7cbc984ef19 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -23,9 +23,6 @@ #js-vue-mr-widget.mr-widget - - content_for :page_specific_javascripts do - = webpack_bundle_tag 'vue_merge_request_widget' - .content-block.content-block-small.emoji-list-container = render 'award_emoji/awards_block', awardable: @merge_request, inline: true diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 97be8950db0..4b7be9a223f 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,7 +1,5 @@ - breadcrumb_title "Graph" - page_title "Graph", @ref -- content_for :page_specific_javascripts do - = webpack_bundle_tag('network') = render "head" %div{ class: container_class } .project-network diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index fdcc60f48a5..cf95cdbfec2 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -12,6 +12,3 @@ "has-ci" => @repository.gitlab_ci_yml, "ci-lint-path" => ci_lint_path, "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } } - - = webpack_bundle_tag('common_vue') - = webpack_bundle_tag('pipelines') diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml deleted file mode 100644 index d15f4310ff5..00000000000 --- a/app/views/projects/project_members/update.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); - $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); - gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@project_member)}")); diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml new file mode 100644 index 00000000000..2cc2a6b2b5b --- /dev/null +++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml @@ -0,0 +1,26 @@ +%h4 + = s_('PrometheusService|Auto configuration') + +- if service.manual_configuration? + .well + = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below') +- else + .container-fluid + .row + - if service.prometheus_installed? + .col-sm-2 + .svg-container + = image_tag 'illustrations/monitoring/getting_started.svg' + .col-sm-10 + %p.text-success.prepend-top-default + = s_('PrometheusService|Prometheus is being automatically managed on your clusters') + = link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn' + - else + .col-sm-2 + = image_tag 'illustrations/monitoring/loading.svg' + .col-sm-10 + %p.prepend-top-default + = s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments') + = link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'btn btn-success' + +%hr diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml index 5e320a252d8..88acb824ba7 100644 --- a/app/views/projects/services/prometheus/_help.html.haml +++ b/app/views/projects/services/prometheus/_help.html.haml @@ -1,29 +1,5 @@ -%h4 - = s_('PrometheusService|Auto configuration') - -- if @service.manual_configuration? - .well - = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below') -- else - .container-fluid - .row - - if @service.prometheus_installed? - .col-sm-2 - .svg-container - = image_tag 'illustrations/monitoring/getting_started.svg' - .col-sm-10 - %p.text-success.prepend-top-default - = s_('PrometheusService|Prometheus is being automatically managed on your clusters') - = link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn' - - else - .col-sm-2 - = image_tag 'illustrations/monitoring/loading.svg' - .col-sm-10 - %p.prepend-top-default - = s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments') - = link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success' - -%hr +- if @project + = render 'projects/services/prometheus/configuration_banner', project: @project, service: @service %h4.append-bottom-default = s_('PrometheusService|Manual configuration') diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index dc583d3eb3b..c1589027898 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,7 +1,6 @@ - todo = issuable_todo(issuable) - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') - = webpack_bundle_tag('sidebar') %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } } diff --git a/app/views/shared/members/update.js.haml b/app/views/shared/members/update.js.haml new file mode 100644 index 00000000000..55050bd8a15 --- /dev/null +++ b/app/views/shared/members/update.js.haml @@ -0,0 +1,6 @@ +- member = local_assigns.fetch(:member) + +:plain + var $listItem = $('#{escape_javascript(render('shared/members/member', member: member))}'); + $("##{dom_id(member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(member)}")); diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb index d80b3b15840..68960f72bf6 100644 --- a/app/workers/remove_expired_members_worker.rb +++ b/app/workers/remove_expired_members_worker.rb @@ -5,7 +5,7 @@ class RemoveExpiredMembersWorker def perform Member.expired.find_each do |member| begin - Members::AuthorizedDestroyService.new(member).execute + Members::DestroyService.new.execute(member, skip_authorization: true) rescue => ex logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}") end |