diff options
64 files changed, 431 insertions, 141 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml index 333a70f71d3..216ecf43beb 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -45,3 +45,4 @@ exclude_paths: - log/ - backups/ - coverage-javascript/ +- plugins/ diff --git a/.gitignore b/.gitignore index 2004c2a09b4..fa39ae01ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ eslint-report.html /locale/**/LC_MESSAGES /locale/**/*.time_stamp /.rspec +/plugins/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ae762e7aa6e..8a0c9802c15 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6" +image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6" .dedicated-runner: &dedicated-runner retry: 1 diff --git a/.rubocop.yml b/.rubocop.yml index 24edb641657..293f61fb725 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,6 +17,7 @@ AllCops: - 'bin/**/*' - 'generator_templates/**/*' - 'builds/**/*' + - 'plugins/**/*' CacheRootDirectory: tmp # This cop checks whether some constant value isn't a diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index 0df1f7a6f82..57a7cc4ca30 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -4,7 +4,9 @@ import FilteredSearchManager from '../filtered_search/filtered_search_manager'; export default class FilteredSearchBoards extends FilteredSearchManager { constructor(store, updateUrl = false, cantEdit = []) { - super('boards'); + super({ + page: 'boards', + }); this.store = store; this.updateUrl = updateUrl; diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/index.js index 2e0a4001b7c..afc4aba6554 100644 --- a/app/assets/javascripts/environments/environments_bundle.js +++ b/app/assets/javascripts/environments/index.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-list-view', components: { environmentsComponent, @@ -36,4 +36,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ }, }); }, -})); +}); diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js deleted file mode 100644 index 293154917fa..00000000000 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ /dev/null @@ -1,10 +0,0 @@ -import './dropdown_emoji'; -import './dropdown_hint'; -import './dropdown_non_user'; -import './dropdown_user'; -import './dropdown_utils'; -import './filtered_search_dropdown_manager'; -import './filtered_search_dropdown'; -import './filtered_search_manager'; -import './filtered_search_tokenizer'; -import './filtered_search_visual_tokens'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index c64553a1b92..ee49a7be0b2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -10,13 +10,22 @@ import DropdownUser from './dropdown_user'; import FilteredSearchVisualTokens from './filtered_search_visual_tokens'; export default class FilteredSearchDropdownManager { - constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) { + constructor({ + baseEndpoint = '', + tokenizer, + page, + isGroup, + isGroupAncestor, + filteredSearchTokenKeys, + }) { this.container = FilteredSearchContainer.container; this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.tokenizer = tokenizer; this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys; this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.page = page; + this.groupsOnly = isGroup; + this.groupAncestor = isGroupAncestor; this.setupMapping(); @@ -59,7 +68,7 @@ export default class FilteredSearchDropdownManager { reference: null, gl: DropdownNonUser, extraArguments: { - endpoint: `${this.baseEndpoint}/milestones.json`, + endpoint: this.getMilestoneEndpoint(), symbol: '%', }, element: this.container.querySelector('#js-dropdown-milestone'), @@ -68,7 +77,7 @@ export default class FilteredSearchDropdownManager { reference: null, gl: DropdownNonUser, extraArguments: { - endpoint: `${this.baseEndpoint}/labels.json`, + endpoint: this.getLabelsEndpoint(), symbol: '~', preprocessing: DropdownUtils.duplicateLabelPreprocessing, }, @@ -90,6 +99,18 @@ export default class FilteredSearchDropdownManager { this.mapping = allowedMappings; } + getMilestoneEndpoint() { + const endpoint = `${this.baseEndpoint}/milestones.json`; + + return endpoint; + } + + getLabelsEndpoint() { + const endpoint = `${this.baseEndpoint}/labels.json`; + + return endpoint; + } + static addWordToInput(tokenName, tokenValue = '', clicked = false) { const input = FilteredSearchContainer.container.querySelector('.filtered-search'); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index e294b629bd0..c6970d7837f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -20,10 +20,13 @@ import DropdownUtils from './dropdown_utils'; export default class FilteredSearchManager { constructor({ page, + isGroup = false, + isGroupAncestor = false, filteredSearchTokenKeys = FilteredSearchTokenKeys, stateFiltersSelector = '.issues-state-filters', }) { - this.isGroup = false; + this.isGroup = isGroup; + this.isGroupAncestor = isGroupAncestor; this.states = ['opened', 'closed', 'merged', 'all']; this.page = page; @@ -75,13 +78,14 @@ export default class FilteredSearchManager { if (this.filteredSearchInput) { this.tokenizer = FilteredSearchTokenizer; - this.dropdownManager = new FilteredSearchDropdownManager( - this.filteredSearchInput.getAttribute('data-base-endpoint') || '', - this.tokenizer, - this.page, - this.isGroup, - this.filteredSearchTokenKeys, - ); + this.dropdownManager = new FilteredSearchDropdownManager({ + baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '', + tokenizer: this.tokenizer, + page: this.page, + isGroup: this.isGroup, + isGroupAncestor: this.isGroupAncestor, + filteredSearchTokenKeys: this.filteredSearchTokenKeys, + }); this.recentSearchesRoot = new RecentSearchesRoot( this.recentSearchesStore, diff --git a/app/assets/javascripts/pages/projects/environments/index.js b/app/assets/javascripts/pages/projects/environments/index.js new file mode 100644 index 00000000000..ace8af00ece --- /dev/null +++ b/app/assets/javascripts/pages/projects/environments/index.js @@ -0,0 +1,3 @@ +import initEnviroments from '~/environments/'; + +document.addEventListener('DOMContentLoaded', initEnviroments); diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js new file mode 100644 index 00000000000..37503cc1542 --- /dev/null +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -0,0 +1,14 @@ +import initIssuableSidebar from '~/init_issuable_sidebar'; +import Issue from '~/issue'; +import ShortcutsIssuable from '~/shortcuts_issuable'; +import ZenMode from '~/zen_mode'; +import '~/notes/index'; +import '~/issue_show/index'; + +export default function () { + new Issue(); // eslint-disable-line no-new + new ShortcutsIssuable(); // eslint-disable-line no-new + new ZenMode(); // eslint-disable-line no-new + initIssuableSidebar(); +} + diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js index 3950fe0a1fd..7968dfd7a12 100644 --- a/app/assets/javascripts/pages/projects/issues/show/index.js +++ b/app/assets/javascripts/pages/projects/issues/show/index.js @@ -1,15 +1,7 @@ -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'; -import '~/notes/index'; -import '~/issue_show/index'; +import initShow from '../show'; document.addEventListener('DOMContentLoaded', () => { - new Issue(); // eslint-disable-line no-new - new ShortcutsIssuable(); // eslint-disable-line no-new - new ZenMode(); // eslint-disable-line no-new - initIssuableSidebar(); + initShow(); initSidebarBundle(); }); diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js new file mode 100644 index 00000000000..da27c22f537 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -0,0 +1,33 @@ +import MergeRequest from '~/merge_request'; +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 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'; + +export default function () { + new Diff(); // eslint-disable-line no-new + new ZenMode(); // eslint-disable-line no-new + + initIssuableSidebar(); + initNotes(); + initDiffNotes(); + initPipelines(); + + const mrShowNode = document.querySelector('.merge-request'); + + window.mergeRequest = new MergeRequest({ + action: mrShowNode.dataset.mrAction, + }); + + new ShortcutsIssuable(true); // eslint-disable-line no-new + handleLocationHash(); + howToMerge(); + initWidget(); +} + 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 b48ba1ef95e..3e72f7a6f37 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js @@ -1,33 +1,7 @@ -import MergeRequest from '~/merge_request'; -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'; +import initShow from '../init_merge_request_show'; document.addEventListener('DOMContentLoaded', () => { - new Diff(); // eslint-disable-line no-new - new ZenMode(); // eslint-disable-line no-new - - initIssuableSidebar(); + initShow(); initSidebarBundle(); - initNotes(); - initDiffNotes(); - initPipelines(); - - const mrShowNode = document.querySelector('.merge-request'); - window.mergeRequest = new MergeRequest({ - action: mrShowNode.dataset.mrAction, - }); - - new ShortcutsIssuable(true); // eslint-disable-line no-new - handleLocationHash(); - howToMerge(); - initWidget(); }); diff --git a/app/assets/javascripts/pages/projects/pipelines/builds/index.js b/app/assets/javascripts/pages/projects/pipelines/builds/index.js index fbe9824c34b..7a57e417b41 100644 --- a/app/assets/javascripts/pages/projects/pipelines/builds/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/builds/index.js @@ -1,3 +1,7 @@ +import initPipelineDetails from '~/pipelines/pipeline_details_bundle'; import initPipelines from '../init_pipelines'; -document.addEventListener('DOMContentLoaded', initPipelines); +document.addEventListener('DOMContentLoaded', () => { + initPipelines(); + initPipelineDetails(); +}); diff --git a/app/assets/javascripts/pages/projects/pipelines/show/index.js b/app/assets/javascripts/pages/projects/pipelines/show/index.js index fbe9824c34b..7a57e417b41 100644 --- a/app/assets/javascripts/pages/projects/pipelines/show/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/show/index.js @@ -1,3 +1,7 @@ +import initPipelineDetails from '~/pipelines/pipeline_details_bundle'; import initPipelines from '../init_pipelines'; -document.addEventListener('DOMContentLoaded', initPipelines); +document.addEventListener('DOMContentLoaded', () => { + initPipelines(); + initPipelineDetails(); +}); diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index 5a6f4138b10..001128ead59 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,7 +1,13 @@ +/* eslint-disable no-new */ + +import ProtectedTagCreate from '~/protected_tags/protected_tag_create'; +import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; import initSettingsPanels from '~/settings_panels'; import initDeployKeys from '~/deploy_keys'; document.addEventListener('DOMContentLoaded', () => { + new ProtectedTagCreate(); + new ProtectedTagEditList(); initDeployKeys(); initSettingsPanels(); }); diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js index de8d4168d71..57f08701a4f 100644 --- a/app/assets/javascripts/pages/search/init_filtered_search.js +++ b/app/assets/javascripts/pages/search/init_filtered_search.js @@ -1,9 +1,21 @@ import FilteredSearchManager from '~/filtered_search/filtered_search_manager'; -export default ({ page }) => { +export default ({ + page, + filteredSearchTokenKeys, + isGroup, + isGroupAncestor, + stateFiltersSelector, +}) => { const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search'); if (filteredSearchEnabled) { - const filteredSearchManager = new FilteredSearchManager({ page }); + const filteredSearchManager = new FilteredSearchManager({ + page, + isGroup, + isGroupAncestor, + filteredSearchTokenKeys, + stateFiltersSelector, + }); filteredSearchManager.setup(); } }; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 705a60b3ba2..6b26708148c 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -9,7 +9,7 @@ import eventHub from './event_hub'; Vue.use(Translate); -document.addEventListener('DOMContentLoaded', () => { +export default () => { const dataset = document.querySelector('.js-pipeline-details-vue').dataset; const mediator = new PipelinesMediator({ endpoint: dataset.endpoint }); @@ -70,4 +70,4 @@ document.addEventListener('DOMContentLoaded', () => { }); }, }); -}); +}; diff --git a/app/assets/javascripts/protected_tags/index.js b/app/assets/javascripts/protected_tags/index.js deleted file mode 100644 index b1618e24e49..00000000000 --- a/app/assets/javascripts/protected_tags/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import ProtectedTagCreate from './protected_tag_create'; -import ProtectedTagEditList from './protected_tag_edit_list'; - -$(() => { - const protectedtTagCreate = new ProtectedTagCreate(); - const protectedtTagEditList = new ProtectedTagEditList(); -}); diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue index 3595a9389e9..c943c8d98a4 100644 --- a/app/assets/javascripts/vue_shared/components/expand_button.vue +++ b/app/assets/javascripts/vue_shared/components/expand_button.vue @@ -39,7 +39,7 @@ @click="onClick"> ... </button> - <span v-show="!isCollapsed"> + <span v-if="!isCollapsed"> <slot name="expanded"></slot> </span> </span> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 1413dd69f24..3fcacd156c5 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -14,6 +14,11 @@ collapsedCalendarIcon, }, props: { + blockClass: { + type: String, + required: false, + default: '', + }, collapsed: { type: Boolean, required: false, @@ -91,7 +96,10 @@ </script> <template> - <div class="block"> + <div + class="block" + :class="blockClass" + > <div class="issuable-sidebar-header"> <toggle-sidebar :collapsed="collapsed" diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index a6b7a6e1416..af8c02a10b7 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -11,6 +11,8 @@ class SystemHooksService SystemHook.hooks_for(hooks_scope).find_each do |hook| hook.async_execute(data, 'system_hooks') end + + Gitlab::Plugin.execute_all_async(data) end private diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 520fe217ae1..4ccd16f3e11 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,8 +1,5 @@ - page_title "Merge Requests" -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' - - if group_merge_requests_count(state: 'all').zero? = render 'shared/empty_states/merge_requests', project_select_button: true - else diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index e9d8fc75142..c7fc5a98ca8 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -28,4 +28,5 @@ .form-actions = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' +-# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index a3fed25af28..68b35072f26 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -6,6 +6,3 @@ "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), } } - -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_vue') diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index ba86d943967..abb292f8f27 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -6,8 +6,6 @@ - @content_class = limited_container_width - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_description @commit.description -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_vue') .container-fluid{ class: [limited_container_width, container_class] } = render "commit_box" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index b947b91322d..a96485ab155 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -70,6 +70,7 @@ Enable or disable certain project features and choose access levels. .settings-content = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f| + -# haml-lint:disable InlineJavaScript %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project) .js-project-permissions-form = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index d8054dbc372..1ac7dab6775 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -1,9 +1,6 @@ - @no_container = true - page_title "Environments" -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_vue') - #environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json), "folder-name" => @folder, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 31cf173fa9c..0d656b25bc8 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -4,7 +4,6 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag("common_vue") - = webpack_bundle_tag("environments") #environments-list-view{ data: { environments_data: environments_list_data, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index 91b3743e9e7..9d9759ebc5f 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -1,7 +1,5 @@ - @no_container = true - page_title "Metrics for environment", @environment.name -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' .prometheus-container{ class: container_class } .top-area diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index d4b4a6203f3..14c47a5d91c 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -74,6 +74,7 @@ = _("Commits per day hour (UTC)") %canvas#hour-chart +-# haml-lint:disable InlineJavaScript %script#projectChartData{ type: "application/json" } - projectChartData = {}; - projectChartData['hour'] = @commits_per_time diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 77e6cb4d2cd..ec7e87219f5 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -55,6 +55,7 @@ .issue-details.issuable-details .detail-page-description.content-block + -# haml-lint:disable InlineJavaScript %script#js-issuable-app-initial-data{ type: "application/json" }= issuable_initial_data(@issue).to_json #js-issuable-app %h2.title= markdown_field(@issue, :title) diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml index 510697c2ae9..c23fe6ff170 100644 --- a/app/views/projects/pipelines/charts/_pipeline_times.haml +++ b/app/views/projects/pipelines/charts/_pipeline_times.haml @@ -4,4 +4,5 @@ %canvas#build_timesChart{ height: 200 } +-# haml-lint:disable InlineJavaScript %script#pipelinesTimesChartsData{ type: "application/json" }= { :labels => @charts[:pipeline_times].labels, :values => @charts[:pipeline_times].pipeline_times }.to_json.html_safe diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml index 2f4b6def155..14b3d47a9c2 100644 --- a/app/views/projects/pipelines/charts/_pipelines.haml +++ b/app/views/projects/pipelines/charts/_pipelines.haml @@ -26,6 +26,7 @@ = _("Pipelines for last year") %canvas#yearChart.padded{ height: 250 } +-# haml-lint:disable InlineJavaScript %script#pipelinesChartsData{ type: "application/json" } - chartData = [] - [:week, :month, :year].each do |scope| diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 4ad37d0e882..877101b05ca 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -20,4 +20,5 @@ = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel' +-# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 2174154b207..ffb0ae95f9b 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -13,4 +13,3 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') - = webpack_bundle_tag('pipelines_details') diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml index 74f7f63c941..6b284fda35c 100644 --- a/app/views/projects/protected_tags/_index.html.haml +++ b/app/views/projects/protected_tags/_index.html.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('protected_tags') - - content_for :create_protected_tag do = render 'projects/protected_tags/create_protected_tag' diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 6e105a5521a..1827a3d323c 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -43,4 +43,5 @@ .form-actions = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create' = link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel' +-# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index 3312254f5fb..014b8de1dc9 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -7,6 +7,7 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' + -# haml-lint:disable InlineJavaScript %script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board" %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index c1589027898..adaddda13eb 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,6 +1,4 @@ - todo = issuable_todo(issuable) -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_vue') %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)}" } } @@ -119,10 +117,12 @@ = render partial: "shared/issuable/label_page_create" - if issuable.has_attribute?(:confidential) + -# haml-lint:disable InlineJavaScript %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe #js-confidential-entry-point - if issuable.has_attribute?(:discussion_locked) + -# haml-lint:disable InlineJavaScript %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe #js-lock-entry-point @@ -159,4 +159,5 @@ = _('Move') = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon') + -# haml-lint:disable InlineJavaScript %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index cd4188daf5b..a942ebc328b 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -1,7 +1,5 @@ - affix_offset = local_assigns.fetch(:affix_offset, "50") - project = local_assigns[:project] -- content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('common_vue') %aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } .issuable-sidebar.milestone-sidebar diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml index 0ceca367883..1db7c4e67cf 100644 --- a/app/views/shared/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -35,4 +35,5 @@ is locked. Only %b project members can comment. +-# haml-lint:disable InlineJavaScript %script.js-notes-data{ type: "application/json" }= initial_notes_data(autocomplete).to_json.html_safe diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index f878bece2fa..7eb221620ad 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -1,6 +1,7 @@ #js-authenticate-u2f %a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code +-# haml-lint:disable InlineJavaScript %script#js-authenticate-u2f-not-supported{ type: "text/template" } %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer). diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml index 79e8f8d0e89..cc0e93c0755 100644 --- a/app/views/u2f/_register.html.haml +++ b/app/views/u2f/_register.html.haml @@ -1,5 +1,6 @@ #js-register-u2f +-# haml-lint:disable InlineJavaScript %script#js-register-u2f-not-supported{ type: "text/template" } %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer). diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 28a5e5da037..a9415410f8a 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -84,6 +84,7 @@ - new_note - pages - pages_domain_verification +- plugin - post_receive - process_commit - project_cache diff --git a/app/workers/plugin_worker.rb b/app/workers/plugin_worker.rb new file mode 100644 index 00000000000..bfcc683d99a --- /dev/null +++ b/app/workers/plugin_worker.rb @@ -0,0 +1,15 @@ +class PluginWorker + include ApplicationWorker + + sidekiq_options retry: false + + def perform(file_name, data) + success, message = Gitlab::Plugin.execute(file_name, data) + + unless success + Gitlab::PluginLogger.error("Plugin Error => #{file_name}: #{message}") + end + + true + end +end diff --git a/changelogs/unreleased/dz-system-hooks-plugins.yml b/changelogs/unreleased/dz-system-hooks-plugins.yml new file mode 100644 index 00000000000..e6eb1dfb03b --- /dev/null +++ b/changelogs/unreleased/dz-system-hooks-plugins.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to use external plugins as an alternative to system hooks +merge_request: 17003 +author: +type: added diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index f037e3d1221..4845dc28a4a 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -68,3 +68,4 @@ - [project_migrate_hashed_storage, 1] - [storage_migrator, 1] - [pages_domain_verification, 1] + - [plugin, 1] diff --git a/config/webpack.config.js b/config/webpack.config.js index fb0a2c84ccc..e806083bf16 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,33 +1,33 @@ 'use strict'; -var crypto = require('crypto'); -var fs = require('fs'); -var path = require('path'); -var glob = require('glob'); -var webpack = require('webpack'); -var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; -var CopyWebpackPlugin = require('copy-webpack-plugin'); -var CompressionPlugin = require('compression-webpack-plugin'); -var NameAllModulesPlugin = require('name-all-modules-plugin'); -var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); - -var ROOT_PATH = path.resolve(__dirname, '..'); -var IS_PRODUCTION = process.env.NODE_ENV === 'production'; -var IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; -var DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; -var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; -var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; -var WEBPACK_REPORT = process.env.WEBPACK_REPORT; -var NO_COMPRESSION = process.env.NO_COMPRESSION; - -var autoEntriesCount = 0; -var watchAutoEntries = []; +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); +const webpack = require('webpack'); +const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const NameAllModulesPlugin = require('name-all-modules-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); + +const ROOT_PATH = path.resolve(__dirname, '..'); +const IS_PRODUCTION = process.env.NODE_ENV === 'production'; +const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; +const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; +const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; +const DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; +const WEBPACK_REPORT = process.env.WEBPACK_REPORT; +const NO_COMPRESSION = process.env.NO_COMPRESSION; + +let autoEntriesCount = 0; +let watchAutoEntries = []; function generateEntries() { // generate automatic entry points - var autoEntries = {}; - var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') }); + const autoEntries = {}; + const pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') }); watchAutoEntries = [ path.join(ROOT_PATH, 'app/assets/javascripts/pages/'), ]; @@ -44,22 +44,15 @@ function generateEntries() { const manualEntries = { balsamiq_viewer: './blob/balsamiq_viewer.js', - environments: './environments/environments_bundle.js', - filtered_search: './filtered_search/filtered_search_bundle.js', - help: './help/help.js', monitoring: './monitoring/monitoring_bundle.js', mr_notes: './mr_notes/index.js', notebook_viewer: './blob/notebook_viewer.js', pdf_viewer: './blob/pdf_viewer.js', - pipelines_details: './pipelines/pipeline_details_bundle.js', - project_import_gl: './projects/project_import_gitlab_project.js', protected_branches: './protected_branches', - protected_tags: './protected_tags', registry_list: './registry/index.js', sketch_viewer: './blob/sketch_viewer.js', stl_viewer: './blob/stl_viewer.js', terminal: './terminal/terminal_bundle.js', - ui_development_kit: './ui_development_kit.js', two_factor_auth: './two_factor_auth.js', common: './commons/index.js', @@ -76,7 +69,7 @@ function generateEntries() { return Object.assign(manualEntries, autoEntries); } -var config = { +const config = { context: path.join(ROOT_PATH, 'app/assets/javascripts'), entry: generateEntries, @@ -168,7 +161,7 @@ var config = { new StatsWriterPlugin({ filename: 'manifest.json', transform: function(data, opts) { - var stats = opts.compiler.getStats().toJson({ + const stats = opts.compiler.getStats().toJson({ chunkModules: false, source: false, chunks: false, diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md new file mode 100644 index 00000000000..c91ac3012b9 --- /dev/null +++ b/doc/administration/plugins.md @@ -0,0 +1,66 @@ +# Plugins + +**Note:** Plugins must be configured on the filesystem of the GitLab +server. Only GitLab server administrators will be able to complete these tasks. +Please explore [system hooks] or [webhooks] as an option if you do not +have filesystem access. + +Introduced in GitLab 10.6. + +A plugin will run on each event so it's up to you to filter events or projects within a plugin code. You can have as many plugins as you want. Each plugin will be triggered by GitLab asynchronously in case of an event. For a list of events please see [system hooks] documentation. + +## Setup + +Plugins must be placed directly into `plugins` directory, subdirectories will be ignored. +There is an `example` directory inside `plugins` where you can find some basic examples. + +Follow the steps below to set up a custom hook: + +1. On the GitLab server, navigate to the project's plugin directory. + For an installation from source the path is usually + `/home/git/gitlab/plugins/`. For Omnibus installs the path is + usually `/opt/gitlab/embedded/service/gitlab-rails/plugins`. +1. Inside the `plugins` directory, create a file with a name of your choice, but without spaces or special characters. +1. Make the hook file executable and make sure it's owned by the git user. +1. Write the code to make the plugin function as expected. Plugin can be + in any language. Ensure the 'shebang' at the top properly reflects the language + type. For example, if the script is in Ruby the shebang will probably be + `#!/usr/bin/env ruby`. +1. The data to the plugin will be provided as JSON on STDIN. It will be exactly same as one for [system hooks] + +That's it! Assuming the plugin code is properly implemented the hook will fire +as appropriate. Plugins file list is updated for each event. There is no need to restart GitLab to apply a new plugin. + +If a plugin executes with non-zero exit code or GitLab fails to execute it, a +message will be logged to `plugin.log`. + +## Validation + +Writing own plugin can be tricky and its easier if you can check it without altering the system. +We provided a rake task you can use with staging environment to test your plugin before using it in production. +The rake task will use a sample data and execute each of plugins. By output you should be able to determine if +system sees your plugin and if it was executed without errors. + +```bash +# Omnibus installations +sudo gitlab-rake plugins:validate + +# Installations from source +bundle exec rake plugins:validate RAILS_ENV=production +``` + +Example of output can be next: + +``` +-> bundle exec rake plugins:validate RAILS_ENV=production +Validating plugins from /plugins directory +* /home/git/gitlab/plugins/save_to_file.clj succeed (zero exit code) +* /home/git/gitlab/plugins/save_to_file.rb failure (non-zero exit code) +``` + +[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks +[system hooks]: ../system_hooks/system_hooks.md +[webhooks]: ../user/project/integrations/webhooks.md +[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073 +[93]: https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/93 + diff --git a/lib/gitlab/plugin.rb b/lib/gitlab/plugin.rb new file mode 100644 index 00000000000..0d1cb16b378 --- /dev/null +++ b/lib/gitlab/plugin.rb @@ -0,0 +1,26 @@ +module Gitlab + module Plugin + def self.files + Dir.glob(Rails.root.join('plugins/*')).select do |entry| + File.file?(entry) + end + end + + def self.execute_all_async(data) + args = files.map { |file| [file, data] } + + PluginWorker.bulk_perform_async(args) + end + + def self.execute(file, data) + result = Gitlab::Popen.popen_with_detail([file]) do |stdin| + stdin.write(data.to_json) + end + + exit_status = result.status&.exitstatus + [exit_status.zero?, result.stderr] + rescue => e + [false, e.message] + end + end +end diff --git a/lib/gitlab/plugin_logger.rb b/lib/gitlab/plugin_logger.rb new file mode 100644 index 00000000000..c4f6ec3e21d --- /dev/null +++ b/lib/gitlab/plugin_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class PluginLogger < Gitlab::Logger + def self.file_name_noext + 'plugin' + end + end +end diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb index f5485eb89fa..4f776330e80 100644 --- a/lib/haml_lint/inline_javascript.rb +++ b/lib/haml_lint/inline_javascript.rb @@ -12,6 +12,12 @@ unless Rails.env.production? record_lint(node, 'Inline JavaScript is discouraged (https://docs.gitlab.com/ee/development/gotchas.html#do-not-use-inline-javascript-in-views)') end + + def visit_tag(node) + return unless node.tag_name == 'script' + + record_lint(node, 'Inline JavaScript is discouraged (https://docs.gitlab.com/ee/development/gotchas.html#do-not-use-inline-javascript-in-views)') + end end end end diff --git a/lib/tasks/plugins.rake b/lib/tasks/plugins.rake new file mode 100644 index 00000000000..e73dd7e68df --- /dev/null +++ b/lib/tasks/plugins.rake @@ -0,0 +1,16 @@ +namespace :plugins do + desc 'Validate existing plugins' + task validate: :environment do + puts 'Validating plugins from /plugins directory' + + Gitlab::Plugin.files.each do |file| + success, message = Gitlab::Plugin.execute(file, Gitlab::DataBuilder::Push::SAMPLE_DATA) + + if success + puts "* #{file} succeed (zero exit code)." + else + puts "* #{file} failure (non-zero exit code). #{message}" + end + end + end +end diff --git a/plugins/examples/save_to_file.clj b/plugins/examples/save_to_file.clj new file mode 100755 index 00000000000..a59d83749d3 --- /dev/null +++ b/plugins/examples/save_to_file.clj @@ -0,0 +1,3 @@ +#!/usr/bin/env clojure +(let [in (slurp *in*)] + (spit "/tmp/clj-data.txt" in)) diff --git a/plugins/examples/save_to_file.rb b/plugins/examples/save_to_file.rb new file mode 100755 index 00000000000..61b0df9bfd6 --- /dev/null +++ b/plugins/examples/save_to_file.rb @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +x = STDIN.read +File.write('/tmp/rb-data.txt', x) diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 94fcc6c7f2b..fb4946aeeea 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -126,7 +126,6 @@ if (process.env.BABEL_ENV === 'coverage') { './diff_notes/components/resolve_count.js', './dispatcher.js', './environments/environments_bundle.js', - './filtered_search/filtered_search_bundle.js', './graphs/graphs_bundle.js', './issuable/time_tracking/time_tracking_bundle.js', './main.js', diff --git a/spec/lib/gitlab/plugin_spec.rb b/spec/lib/gitlab/plugin_spec.rb new file mode 100644 index 00000000000..33dd4f79130 --- /dev/null +++ b/spec/lib/gitlab/plugin_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe Gitlab::Plugin do + describe '.execute' do + let(:data) { Gitlab::DataBuilder::Push::SAMPLE_DATA } + let(:plugin) { Rails.root.join('plugins', 'test.rb') } + let(:tmp_file) { Tempfile.new('plugin-dump') } + let(:result) { described_class.execute(plugin.to_s, data) } + let(:success) { result.first } + let(:message) { result.last } + + let(:plugin_source) do + <<~EOS + #!/usr/bin/env ruby + x = STDIN.read + File.write('#{tmp_file.path}', x) + EOS + end + + before do + File.write(plugin, plugin_source) + end + + after do + FileUtils.rm(plugin) + end + + context 'successful execution' do + before do + File.chmod(0o777, plugin) + end + + after do + tmp_file.close! + end + + it { expect(success).to be true } + it { expect(message).to be_empty } + + it 'ensures plugin received data via stdin' do + result + + expect(File.read(tmp_file.path)).to eq(data.to_json) + end + end + + context 'non-executable' do + it { expect(success).to be false } + it { expect(message).to include('Permission denied') } + end + + context 'non-zero exit' do + let(:plugin_source) do + <<~EOS + #!/usr/bin/env ruby + exit 1 + EOS + end + + before do + File.chmod(0o777, plugin) + end + + it { expect(success).to be false } + it { expect(message).to be_empty } + end + end +end diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb index 79d2708f9ad..ce35276cbf5 100644 --- a/spec/migrations/migrate_stages_statuses_spec.rb +++ b/spec/migrations/migrate_stages_statuses_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20170711145558_migrate_stages_statuses.rb') -describe MigrateStagesStatuses, :migration do +describe MigrateStagesStatuses, :sidekiq, :migration do let(:jobs) { table(:ci_builds) } let(:stages) { table(:ci_stages) } let(:pipelines) { table(:ci_pipelines) } diff --git a/spec/migrations/schedule_build_stage_migration_spec.rb b/spec/migrations/schedule_build_stage_migration_spec.rb index 06657410173..e2ca35447fb 100644 --- a/spec/migrations/schedule_build_stage_migration_spec.rb +++ b/spec/migrations/schedule_build_stage_migration_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20180212101928_schedule_build_stage_migration') -describe ScheduleBuildStageMigration, :migration do +describe ScheduleBuildStageMigration, :sidekiq, :migration do let(:projects) { table(:projects) } let(:pipelines) { table(:ci_pipelines) } let(:stages) { table(:ci_stages) } diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 4021e537efc..72cafac3f90 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -700,7 +700,7 @@ describe API::Runner do context 'when tace is given' do it 'creates a trace artifact' do - allow_any_instance_of(BuildFinishedWorker).to receive(:perform).with(job.id) do + allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do CreateTraceArtifactWorker.new.perform(job.id) end diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs index 507e4ce785a..ea50e4ad3f6 100644 --- a/spec/support/gitlab-git-test.git/packed-refs +++ b/spec/support/gitlab-git-test.git/packed-refs @@ -1,4 +1,4 @@ -# pack-refs with: peeled fully-peeled +# pack-refs with: peeled fully-peeled sorted 0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature 12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix 6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path diff --git a/spec/workers/plugin_worker_spec.rb b/spec/workers/plugin_worker_spec.rb new file mode 100644 index 00000000000..9238a8199bc --- /dev/null +++ b/spec/workers/plugin_worker_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe PluginWorker do + include RepoHelpers + + let(:filename) { 'my_plugin.rb' } + let(:data) { { 'event_name' => 'project_create' } } + + subject { described_class.new } + + describe '#perform' do + it 'executes Gitlab::Plugin with expected values' do + allow(Gitlab::Plugin).to receive(:execute).with(filename, data).and_return([true, '']) + + expect(subject.perform(filename, data)).to be_truthy + end + + it 'logs message in case of plugin execution failure' do + allow(Gitlab::Plugin).to receive(:execute).with(filename, data).and_return([false, 'permission denied']) + + expect(Gitlab::PluginLogger).to receive(:error) + expect(subject.perform(filename, data)).to be_truthy + end + end +end |