Merge branch 'master' into fl-document-key-vue
* master: (53 commits) init sidebar bundle separately to allow EE override Add support for custom container class Update class init config Update FilteredSearchManager init config Add group related config and endpoint methods to be consistent with EE Tag migration specs with :sidekiq to clear queues between tests Add update for packed-refs with git v2.16 Bump GitLab CI test image to use git v2.16 Ignore InlineJavaScript linter in existing script tag locations. Update inline_javascript.rb to lint uses of the javascript filter as well as script tags. Update inline_javascript.rb Forbid all inline script tags in Linter::InlineJavaScript Remove webpack bundle tag for enviroments remove a bunch of superfluous common_vue bundles Remove use of any_instance_of in runner spec Remove trailing line from plugin logger Improve plugins documentation and remove unnecessary rake task Refactor plugins feature and make some doc improvements prefer let and const in webpack config remove common_vue from CommonsChunkPlugin ...
This commit is contained in:
commit
edbf6204c0
|
@ -45,3 +45,4 @@ exclude_paths:
|
|||
- log/
|
||||
- backups/
|
||||
- coverage-javascript/
|
||||
- plugins/
|
||||
|
|
|
@ -66,3 +66,4 @@ eslint-report.html
|
|||
/locale/**/LC_MESSAGES
|
||||
/locale/**/*.time_stamp
|
||||
/.rspec
|
||||
/plugins/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,6 +17,7 @@ AllCops:
|
|||
- 'bin/**/*'
|
||||
- 'generator_templates/**/*'
|
||||
- 'builds/**/*'
|
||||
- 'plugins/**/*'
|
||||
CacheRootDirectory: tmp
|
||||
|
||||
# This cop checks whether some constant value isn't a
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
|||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
});
|
|
@ -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';
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import initEnviroments from '~/environments/';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initEnviroments);
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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', () => {
|
|||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -39,7 +39,7 @@
|
|||
@click="onClick">
|
||||
...
|
||||
</button>
|
||||
<span v-show="!isCollapsed">
|
||||
<span v-if="!isCollapsed">
|
||||
<slot name="expanded"></slot>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,4 +13,3 @@
|
|||
|
||||
- content_for :page_specific_javascripts do
|
||||
= webpack_bundle_tag('common_vue')
|
||||
= webpack_bundle_tag('pipelines_details')
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
- new_note
|
||||
- pages
|
||||
- pages_domain_verification
|
||||
- plugin
|
||||
- post_receive
|
||||
- process_commit
|
||||
- project_cache
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ability to use external plugins as an alternative to system hooks
|
||||
merge_request: 17003
|
||||
author:
|
||||
type: added
|
|
@ -68,3 +68,4 @@
|
|||
- [project_migrate_hashed_storage, 1]
|
||||
- [storage_migrator, 1]
|
||||
- [pages_domain_verification, 1]
|
||||
- [plugin, 1]
|
||||
|
|
|
@ -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');
|
||||
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');
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
var autoEntriesCount = 0;
|
||||
var watchAutoEntries = [];
|
||||
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,
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
module Gitlab
|
||||
class PluginLogger < Gitlab::Logger
|
||||
def self.file_name_noext
|
||||
'plugin'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env clojure
|
||||
(let [in (slurp *in*)]
|
||||
(spit "/tmp/clj-data.txt" in))
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
x = STDIN.read
|
||||
File.write('/tmp/rb-data.txt', x)
|
|
@ -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',
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue