summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-03-07 18:42:57 +0000
committerFilipa Lacerda <filipa@gitlab.com>2018-03-07 18:42:57 +0000
commit318aeffcd735486ff3bbb99325825788918373c7 (patch)
tree27a916e9bfa9a7f31ead5f5c53c6a366ea8730f9 /app
parentb9f9e6fa4e7a54d6caee81799ed88ee40dfb645b (diff)
parent67185097734bb88979df020123cf7327bc5d32c5 (diff)
downloadgitlab-ce-318aeffcd735486ff3bbb99325825788918373c7.tar.gz
[ci skip] Merge branch 'master' into 43770-change-clear-runners-cache-ujs-action-to-an-axios-request
* master: (68 commits) Upgrade Workhorse to 4.0.0 naming things Update GitLab Pages to v0.7.0 Minor fixes in API doc Use Project#full_name instead of name_with_namespace Fix tests not completely disabling Gitaly Move OperationService#UserRemoveBranch Move OperationService#UserCreateBranch Move CommitService#Languages to OPT_OUT Move RefService#CreateBranch to OPT_OUT Move RefService#DeleteBranch to OPT_OUT Move OperationService#UserRevert to OPT_OUT Move OperationService#UserAddTag to OPT_OUT Move CommitService::CommitPatch to OPT_OUT Change to Pacific Time Zone Merge branch 'pages-6-1-gitlab-10-5' into 'security-10-5' Merge branch 'sh-fix-otp-backup-invalidation-10-5' into 'security-10-5' Remove wrong assumption about Runners cache GC Add CommonMark markdown engine add nginx_status monitoring details ...
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue2
-rw-r--r--app/assets/javascripts/notes/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue64
-rw-r--r--app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js84
-rw-r--r--app/assets/javascripts/pages/milestones/shared/index.js89
-rw-r--r--app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js82
-rw-r--r--app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue79
-rw-r--r--app/assets/javascripts/pages/projects/labels/event_hub.js3
-rw-r--r--app/assets/javascripts/pages/projects/labels/index/index.js90
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue1
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue1
-rw-r--r--app/assets/javascripts/terminal/terminal.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss9
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb1
-rw-r--r--app/controllers/projects/labels_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/milestones_controller.rb14
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/models/concerns/deployment_platform.rb3
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/environment.rb6
-rw-r--r--app/models/event.rb8
-rw-r--r--app/models/merge_request.rb20
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user_interacted_project.rb59
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/services/notes/build_service.rb9
-rw-r--r--app/services/notes/post_process_service.rb2
-rw-r--r--app/services/notification_recipient_service.rb2
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/todo_service.rb3
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/admin/hooks/_form.html.haml42
-rw-r--r--app/views/admin/hooks/index.html.haml58
-rw-r--r--app/views/projects/labels/index.html.haml1
-rw-r--r--app/views/projects/milestones/index.html.haml1
-rw-r--r--app/views/projects/milestones/show.html.haml11
-rw-r--r--app/views/shared/_label.html.haml12
-rw-r--r--app/views/shared/milestones/_milestone.html.haml11
-rw-r--r--app/views/shared/plugins/_index.html.haml23
-rw-r--r--app/workers/emails_on_push_worker.rb2
46 files changed, 673 insertions, 181 deletions
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 27136c7289f..f8dcdf3f60a 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -186,7 +186,7 @@
<clipboard-button
:text="ingressExternalIp"
:title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
- css-class="btn btn-default js-clipboard-btn"
+ class="js-clipboard-btn"
/>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/diff_file_header.vue b/app/assets/javascripts/notes/components/diff_file_header.vue
index fe5baa3537f..3bcde17f07c 100644
--- a/app/assets/javascripts/notes/components/diff_file_header.vue
+++ b/app/assets/javascripts/notes/components/diff_file_header.vue
@@ -35,6 +35,7 @@
<clipboard-button
title="Copy file path to clipboard"
:text="diffFile.submoduleLink"
+ css-class="btn-default btn-transparent btn-clipboard"
/>
</span>
</div>
@@ -79,6 +80,7 @@
<clipboard-button
title="Copy file path to clipboard"
:text="diffFile.filePath"
+ css-class="btn-default btn-transparent btn-clipboard"
/>
<small
diff --git a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
new file mode 100644
index 00000000000..22248418c41
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
@@ -0,0 +1,64 @@
+<script>
+ import axios from '~/lib/utils/axios_utils';
+ import createFlash from '~/flash';
+ import GlModal from '~/vue_shared/components/gl_modal.vue';
+ import { s__, sprintf } from '~/locale';
+ import { visitUrl } from '~/lib/utils/url_utility';
+ import eventHub from '../event_hub';
+
+ export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ milestoneTitle: {
+ type: String,
+ required: true,
+ },
+ url: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle });
+ },
+ text() {
+ return s__(`Milestones|Promoting this milestone will make it available for all projects inside the group.
+ Existing project milestones with the same title will be merged.
+ This action cannot be reversed.`);
+ },
+ },
+ methods: {
+ onSubmit() {
+ eventHub.$emit('promoteMilestoneModal.requestStarted', this.url);
+ return axios.post(this.url, { params: { format: 'json' } })
+ .then((response) => {
+ eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: true });
+ visitUrl(response.data.url);
+ })
+ .catch((error) => {
+ eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: false });
+ createFlash(error);
+ });
+ },
+ },
+ };
+</script>
+<template>
+ <gl-modal
+ id="promote-milestone-modal"
+ footer-primary-button-variant="warning"
+ :footer-primary-button-text="s__('Milestones|Promote Milestone')"
+ @submit="onSubmit"
+ >
+ <template
+ slot="title"
+ >
+ {{ title }}
+ </template>
+ {{ text }}
+ </gl-modal>
+</template>
+
diff --git a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
new file mode 100644
index 00000000000..d51b5c221e3
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import deleteMilestoneModal from './components/delete_milestone_modal.vue';
+import eventHub from './event_hub';
+
+export default () => {
+ Vue.use(Translate);
+
+ const onRequestFinished = ({ milestoneUrl, successful }) => {
+ const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+
+ if (!successful) {
+ button.removeAttribute('disabled');
+ }
+
+ button.querySelector('.js-loading-icon').classList.add('hidden');
+ };
+
+ const onRequestStarted = (milestoneUrl) => {
+ const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+ button.setAttribute('disabled', '');
+ button.querySelector('.js-loading-icon').classList.remove('hidden');
+ eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
+ };
+
+ const onDeleteButtonClick = (event) => {
+ const button = event.currentTarget;
+ const modalProps = {
+ milestoneId: parseInt(button.dataset.milestoneId, 10),
+ milestoneTitle: button.dataset.milestoneTitle,
+ milestoneUrl: button.dataset.milestoneUrl,
+ issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
+ mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
+ };
+ eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
+ eventHub.$emit('deleteMilestoneModal.props', modalProps);
+ };
+
+ const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
+ deleteMilestoneButtons.forEach((button) => {
+ button.addEventListener('click', onDeleteButtonClick);
+ });
+
+ eventHub.$once('deleteMilestoneModal.mounted', () => {
+ deleteMilestoneButtons.forEach((button) => {
+ button.removeAttribute('disabled');
+ });
+ });
+
+ return new Vue({
+ el: '#delete-milestone-modal',
+ components: {
+ deleteMilestoneModal,
+ },
+ data() {
+ return {
+ modalProps: {
+ milestoneId: -1,
+ milestoneTitle: '',
+ milestoneUrl: '',
+ issueCount: -1,
+ mergeRequestCount: -1,
+ },
+ };
+ },
+ mounted() {
+ eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
+ eventHub.$emit('deleteMilestoneModal.mounted');
+ },
+ beforeDestroy() {
+ eventHub.$off('deleteMilestoneModal.props', this.setModalProps);
+ },
+ methods: {
+ setModalProps(modalProps) {
+ this.modalProps = modalProps;
+ },
+ },
+ render(createElement) {
+ return createElement(deleteMilestoneModal, {
+ props: this.modalProps,
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/milestones/shared/index.js b/app/assets/javascripts/pages/milestones/shared/index.js
index 327e2cf569c..dabfe32848b 100644
--- a/app/assets/javascripts/pages/milestones/shared/index.js
+++ b/app/assets/javascripts/pages/milestones/shared/index.js
@@ -1,88 +1,7 @@
-import Vue from 'vue';
-
-import Translate from '~/vue_shared/translate';
-
-import deleteMilestoneModal from './components/delete_milestone_modal.vue';
-import eventHub from './event_hub';
+import initDeleteMilestoneModal from './delete_milestone_modal_init';
+import initPromoteMilestoneModal from './promote_milestone_modal_init';
export default () => {
- Vue.use(Translate);
-
- const onRequestFinished = ({ milestoneUrl, successful }) => {
- const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
-
- if (!successful) {
- button.removeAttribute('disabled');
- }
-
- button.querySelector('.js-loading-icon').classList.add('hidden');
- };
-
- const onRequestStarted = (milestoneUrl) => {
- const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
- button.setAttribute('disabled', '');
- button.querySelector('.js-loading-icon').classList.remove('hidden');
- eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
- };
-
- const onDeleteButtonClick = (event) => {
- const button = event.currentTarget;
- const modalProps = {
- milestoneId: parseInt(button.dataset.milestoneId, 10),
- milestoneTitle: button.dataset.milestoneTitle,
- milestoneUrl: button.dataset.milestoneUrl,
- issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
- mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
- };
- eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
- eventHub.$emit('deleteMilestoneModal.props', modalProps);
- };
-
- const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
- for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
- const button = deleteMilestoneButtons[i];
- button.addEventListener('click', onDeleteButtonClick);
- }
-
- eventHub.$once('deleteMilestoneModal.mounted', () => {
- for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
- const button = deleteMilestoneButtons[i];
- button.removeAttribute('disabled');
- }
- });
-
- return new Vue({
- el: '#delete-milestone-modal',
- components: {
- deleteMilestoneModal,
- },
- data() {
- return {
- modalProps: {
- milestoneId: -1,
- milestoneTitle: '',
- milestoneUrl: '',
- issueCount: -1,
- mergeRequestCount: -1,
- },
- };
- },
- mounted() {
- eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
- eventHub.$emit('deleteMilestoneModal.mounted');
- },
- beforeDestroy() {
- eventHub.$off('deleteMilestoneModal.props', this.setModalProps);
- },
- methods: {
- setModalProps(modalProps) {
- this.modalProps = modalProps;
- },
- },
- render(createElement) {
- return createElement(deleteMilestoneModal, {
- props: this.modalProps,
- });
- },
- });
+ initDeleteMilestoneModal();
+ initPromoteMilestoneModal();
};
diff --git a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
new file mode 100644
index 00000000000..d00f81c9094
--- /dev/null
+++ b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import PromoteMilestoneModal from './components/promote_milestone_modal.vue';
+import eventHub from './event_hub';
+
+Vue.use(Translate);
+
+export default () => {
+ const onRequestFinished = ({ milestoneUrl, successful }) => {
+ const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+
+ if (!successful) {
+ button.removeAttribute('disabled');
+ }
+ };
+
+ const onRequestStarted = (milestoneUrl) => {
+ const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+ button.setAttribute('disabled', '');
+ eventHub.$once('promoteMilestoneModal.requestFinished', onRequestFinished);
+ };
+
+ const onDeleteButtonClick = (event) => {
+ const button = event.currentTarget;
+ const modalProps = {
+ milestoneTitle: button.dataset.milestoneTitle,
+ url: button.dataset.url,
+ };
+ eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted);
+ eventHub.$emit('promoteMilestoneModal.props', modalProps);
+ };
+
+ const promoteMilestoneButtons = document.querySelectorAll('.js-promote-project-milestone-button');
+ promoteMilestoneButtons.forEach((button) => {
+ button.addEventListener('click', onDeleteButtonClick);
+ });
+
+ eventHub.$once('promoteMilestoneModal.mounted', () => {
+ promoteMilestoneButtons.forEach((button) => {
+ button.removeAttribute('disabled');
+ });
+ });
+
+ const promoteMilestoneModal = document.getElementById('promote-milestone-modal');
+ let promoteMilestoneComponent;
+
+ if (promoteMilestoneModal) {
+ promoteMilestoneComponent = new Vue({
+ el: promoteMilestoneModal,
+ components: {
+ PromoteMilestoneModal,
+ },
+ data() {
+ return {
+ modalProps: {
+ milestoneTitle: '',
+ url: '',
+ },
+ };
+ },
+ mounted() {
+ eventHub.$on('promoteMilestoneModal.props', this.setModalProps);
+ eventHub.$emit('promoteMilestoneModal.mounted');
+ },
+ beforeDestroy() {
+ eventHub.$off('promoteMilestoneModal.props', this.setModalProps);
+ },
+ methods: {
+ setModalProps(modalProps) {
+ this.modalProps = modalProps;
+ },
+ },
+ render(createElement) {
+ return createElement('promote-milestone-modal', {
+ props: this.modalProps,
+ });
+ },
+ });
+ }
+
+ return promoteMilestoneComponent;
+};
diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
new file mode 100644
index 00000000000..54695dfeb99
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
@@ -0,0 +1,79 @@
+<script>
+ import axios from '~/lib/utils/axios_utils';
+ import createFlash from '~/flash';
+ import GlModal from '~/vue_shared/components/gl_modal.vue';
+ import { s__, sprintf } from '~/locale';
+ import { visitUrl } from '~/lib/utils/url_utility';
+ import eventHub from '../event_hub';
+
+ export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
+ },
+ labelTitle: {
+ type: String,
+ required: true,
+ },
+ labelColor: {
+ type: String,
+ required: true,
+ },
+ labelTextColor: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ return s__(`Milestones|Promoting this label will make it available for all projects inside the group.
+ Existing project labels with the same title will be merged. This action cannot be reversed.`);
+ },
+ title() {
+ const label = `<span
+ class="label color-label"
+ style="background-color: ${this.labelColor}; color: ${this.labelTextColor};"
+ >${this.labelTitle}</span>`;
+
+ return sprintf(s__('Labels|Promote label %{labelTitle} to Group Label?'), {
+ labelTitle: label,
+ }, false);
+ },
+ },
+ methods: {
+ onSubmit() {
+ eventHub.$emit('promoteLabelModal.requestStarted', this.url);
+ return axios.post(this.url, { params: { format: 'json' } })
+ .then((response) => {
+ eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: true });
+ visitUrl(response.data.url);
+ })
+ .catch((error) => {
+ eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: false });
+ createFlash(error);
+ });
+ },
+ },
+ };
+</script>
+<template>
+ <gl-modal
+ id="promote-label-modal"
+ footer-primary-button-variant="warning"
+ :footer-primary-button-text="s__('Labels|Promote Label')"
+ @submit="onSubmit"
+ >
+ <div
+ slot="title"
+ v-html="title"
+ >
+ {{ title }}
+ </div>
+
+ {{ text }}
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/pages/projects/labels/event_hub.js b/app/assets/javascripts/pages/projects/labels/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
index 6e45de2a724..2abcbfab1ed 100644
--- a/app/assets/javascripts/pages/projects/labels/index/index.js
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -1,3 +1,91 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
import initLabels from '~/init_labels';
+import eventHub from '../event_hub';
+import PromoteLabelModal from '../components/promote_label_modal.vue';
-document.addEventListener('DOMContentLoaded', initLabels);
+Vue.use(Translate);
+
+const initLabelIndex = () => {
+ initLabels();
+
+ const onRequestFinished = ({ labelUrl, successful }) => {
+ const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+
+ if (!successful) {
+ button.removeAttribute('disabled');
+ }
+ };
+
+ const onRequestStarted = (labelUrl) => {
+ const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+ button.setAttribute('disabled', '');
+ eventHub.$once('promoteLabelModal.requestFinished', onRequestFinished);
+ };
+
+ const onDeleteButtonClick = (event) => {
+ const button = event.currentTarget;
+ const modalProps = {
+ labelTitle: button.dataset.labelTitle,
+ labelColor: button.dataset.labelColor,
+ labelTextColor: button.dataset.labelTextColor,
+ url: button.dataset.url,
+ };
+ eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
+ eventHub.$emit('promoteLabelModal.props', modalProps);
+ };
+
+ const promoteLabelButtons = document.querySelectorAll('.js-promote-project-label-button');
+ promoteLabelButtons.forEach((button) => {
+ button.addEventListener('click', onDeleteButtonClick);
+ });
+
+ eventHub.$once('promoteLabelModal.mounted', () => {
+ promoteLabelButtons.forEach((button) => {
+ button.removeAttribute('disabled');
+ });
+ });
+
+ const promoteLabelModal = document.getElementById('promote-label-modal');
+ let promoteLabelModalComponent;
+
+ if (promoteLabelModal) {
+ promoteLabelModalComponent = new Vue({
+ el: promoteLabelModal,
+ components: {
+ PromoteLabelModal,
+ },
+ data() {
+ return {
+ modalProps: {
+ labelTitle: '',
+ labelColor: '',
+ labelTextColor: '',
+ url: '',
+ },
+ };
+ },
+ mounted() {
+ eventHub.$on('promoteLabelModal.props', this.setModalProps);
+ eventHub.$emit('promoteLabelModal.mounted');
+ },
+ beforeDestroy() {
+ eventHub.$off('promoteLabelModal.props', this.setModalProps);
+ },
+ methods: {
+ setModalProps(modalProps) {
+ this.modalProps = modalProps;
+ },
+ },
+ render(createElement) {
+ return createElement('promote-label-modal', {
+ props: this.modalProps,
+ });
+ },
+ });
+ }
+
+ return promoteLabelModalComponent;
+};
+
+document.addEventListener('DOMContentLoaded', initLabelIndex);
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index b4906ba4ee5..a03180e80e6 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -86,6 +86,7 @@
v-if="repo.location"
:text="clipboardText"
:title="repo.location"
+ css-class="btn-default btn-transparent btn-clipboard"
/>
<div class="controls hidden-xs pull-right">
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index bef850eddc0..ee4eb3581f3 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -90,6 +90,7 @@
v-if="item.location"
:title="item.location"
:text="clipboardText(item.location)"
+ css-class="btn-default btn-transparent btn-clipboard"
/>
</td>
<td>
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 6b9422b1816..904b0093f7b 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -6,8 +6,14 @@
constructor(options) {
this.options = options || {};
- this.options.cursorBlink = options.cursorBlink || true;
- this.options.screenKeys = options.screenKeys || true;
+ if (!Object.prototype.hasOwnProperty.call(this.options, 'cursorBlink')) {
+ this.options.cursorBlink = true;
+ }
+
+ if (!Object.prototype.hasOwnProperty.call(this.options, 'screenKeys')) {
+ this.options.screenKeys = true;
+ }
+
this.container = document.querySelector(options.selector);
this.setSocketUrl();
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 18a3787857d..3d886e7d628 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -67,6 +67,7 @@
<clipboard-button
:text="branchNameClipboardData"
:title="__('Copy branch name to clipboard')"
+ css-class="btn-default btn-transparent btn-clipboard"
/>
{{ s__("mrWidget|into") }}
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 3b6c2da1664..cab126a7eca 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -31,7 +31,7 @@
cssClass: {
type: String,
required: false,
- default: 'btn btn-default btn-transparent btn-clipboard',
+ default: 'btn-default',
},
},
};
@@ -40,6 +40,7 @@
<template>
<button
type="button"
+ class="btn"
:class="cssClass"
:title="title"
:data-clipboard-text="text"
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 1d7b0b602cc..127583626cf 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -272,7 +272,7 @@
.divider {
height: 1px;
- margin: 6px 0;
+ margin: #{$grid-size / 2} 0;
padding: 0;
background-color: $dropdown-divider-color;
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index a6b1bf9b099..48b981dd31f 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -2,14 +2,17 @@
background-color: $modal-body-bg;
padding: #{3 * $grid-size} #{2 * $grid-size};
- .page-title {
- margin-top: 0;
-
+ .page-title,
+ .modal-title {
.color-label {
font-size: $gl-font-size;
padding: $gl-vert-padding $label-padding-modal;
}
}
+
+ .page-title {
+ margin-top: 0;
+ }
}
.modal-body {
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index db8c362f125..2753f83c3cf 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -56,6 +56,7 @@ module AuthenticatesWithTwoFactor
session.delete(:otp_user_id)
remember_me(user) if user_params[:remember_me] == '1'
+ user.save!
sign_in(user)
else
user.increment_failed_attempts!
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index e0f4710175f..99790b8e7e8 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -112,12 +112,14 @@ class Projects::LabelsController < Projects::ApplicationController
begin
return render_404 unless promote_service.execute(@label)
+ flash[:notice] = "#{@label.title} promoted to group label."
respond_to do |format|
format.html do
- redirect_to(project_labels_path(@project),
- notice: 'Label was promoted to a Group Label')
+ redirect_to(project_labels_path(@project), status: 303)
+ end
+ format.json do
+ render json: { url: project_labels_path(@project) }
end
- format.js
end
rescue ActiveRecord::RecordInvalid => e
Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index a1af125547c..54e7d81de6a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -187,7 +187,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
begin
@merge_request.environments_for(current_user).map do |environment|
project = environment.project
- deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
+ deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
stop_url =
if environment.stop_action? && can?(current_user, :create_deployment, environment)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 75b17d05e22..ff93147d00f 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -70,9 +70,17 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def promote
- promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
- flash[:notice] = "Milestone has been promoted to group milestone."
- redirect_to group_milestone_path(project.group, promoted_milestone.iid)
+ Milestones::PromoteService.new(project, current_user).execute(milestone)
+
+ flash[:notice] = "#{milestone.title} promoted to group milestone"
+ respond_to do |format|
+ format.html do
+ redirect_to project_milestones_path(project)
+ end
+ format.json do
+ render json: { url: project_milestones_path(project) }
+ end
+ end
rescue Milestones::PromoteService::PromoteMilestoneError => error
redirect_to milestone, alert: error.message
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 45d4fb451d8..e4212775956 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -117,7 +117,7 @@ class Notify < BaseMailer
if Gitlab::IncomingEmail.enabled? && @sent_notification
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
- address.display_name = @project.name_with_namespace
+ address.display_name = @project.full_name
headers['Reply-To'] = address
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 89d0474a596..faa94204e33 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -1,5 +1,6 @@
module DeploymentPlatform
- def deployment_platform
+ # EE would override this and utilize the extra argument
+ def deployment_platform(environment: nil)
@deployment_platform ||=
find_cluster_platform_kubernetes ||
find_kubernetes_service_integration ||
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 4560bc23193..5a566f3ac02 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -223,6 +223,10 @@ module Issuable
def to_ability_name
model_name.singular
end
+
+ def parent_class
+ ::Project
+ end
end
def today?
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 24d4f1d8761..2b0a88ac5b4 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -99,8 +99,8 @@ class Environment < ActiveRecord::Base
folder_name == "production"
end
- def first_deployment_for(commit)
- ref = project.repository.ref_name_for_sha(ref_path, commit.sha)
+ def first_deployment_for(commit_sha)
+ ref = project.repository.ref_name_for_sha(ref_path, commit_sha)
return nil unless ref
@@ -225,7 +225,7 @@ class Environment < ActiveRecord::Base
end
def deployment_platform
- project.deployment_platform
+ project.deployment_platform(environment: self)
end
private
diff --git a/app/models/event.rb b/app/models/event.rb
index be0fc7efa9a..17a198d52c7 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -65,6 +65,7 @@ class Event < ActiveRecord::Base
# Callbacks
after_create :reset_project_activity
after_create :set_last_repository_updated_at, if: :push?
+ after_create :track_user_interacted_projects
# Scopes
scope :recent, -> { reorder(id: :desc) }
@@ -389,4 +390,11 @@ class Event < ActiveRecord::Base
Project.unscoped.where(id: project_id)
.update_all(last_repository_updated_at: created_at)
end
+
+ def track_user_interacted_projects
+ # Note the call to .available? is due to earlier migrations
+ # that would otherwise conflict with the call to .track
+ # (because the table does not exist yet).
+ UserInteractedProject.track(self) if UserInteractedProject.available?
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5bec68ce4f6..9a7e66a9cbb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -375,15 +375,27 @@ class MergeRequest < ActiveRecord::Base
end
def diff_start_sha
- diff_start_commit.try(:sha)
+ if persisted?
+ merge_request_diff.start_commit_sha
+ else
+ target_branch_head.try(:sha)
+ end
end
def diff_base_sha
- diff_base_commit.try(:sha)
+ if persisted?
+ merge_request_diff.base_commit_sha
+ else
+ branch_merge_base_commit.try(:sha)
+ end
end
def diff_head_sha
- diff_head_commit.try(:sha)
+ if persisted?
+ merge_request_diff.head_commit_sha
+ else
+ source_branch_head.try(:sha)
+ end
end
# When importing a pull request from GitHub, the old and new branches may no
@@ -646,7 +658,7 @@ class MergeRequest < ActiveRecord::Base
!ProtectedBranch.protected?(source_project, source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.allowed?(current_user, :push_code, source_project) &&
- diff_head_commit == source_branch_head
+ diff_head_sha == source_branch_head.try(:sha)
end
def should_remove_source_branch?
diff --git a/app/models/note.rb b/app/models/note.rb
index d7a67ec277c..787a80f0196 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -81,7 +81,7 @@ class Note < ActiveRecord::Base
validates :author, presence: true
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
- validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
+ validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
unless note.noteable.try(:project) == note.project
errors.add(:project, 'does not match noteable project')
end
@@ -228,7 +228,7 @@ class Note < ActiveRecord::Base
end
def skip_project_check?
- for_personal_snippet?
+ !for_project_noteable?
end
def commit
@@ -308,6 +308,11 @@ class Note < ActiveRecord::Base
self.noteable.supports_discussions? && !part_of_discussion?
end
+ def can_create_todo?
+ # Skip system notes, and notes on project snippet
+ !system? && !for_snippet?
+ end
+
def discussion_class(noteable = nil)
# When commit notes are rendered on an MR's Discussion page, they are
# displayed in one discussion instead of individually.
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e6b88320110..461dfbec2bc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -651,14 +651,15 @@ class Repository
end
def last_commit_for_path(sha, path)
- commit_by(oid: last_commit_id_for_path(sha, path))
+ commit = raw_repository.last_commit_for_path(sha, path)
+ ::Commit.new(commit, @project) if commit
end
def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do
- raw_repository.last_commit_id_for_path(sha, path)
+ last_commit_for_path(sha, path)&.id
end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index a58c208279e..644120453cf 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -168,5 +168,9 @@ class Snippet < ActiveRecord::Base
def search_code(query)
fuzzy_search(query, [:content])
end
+
+ def parent_class
+ ::Project
+ end
end
end
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
new file mode 100644
index 00000000000..dd55a6acb79
--- /dev/null
+++ b/app/models/user_interacted_project.rb
@@ -0,0 +1,59 @@
+class UserInteractedProject < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :project
+
+ validates :project_id, presence: true
+ validates :user_id, presence: true
+
+ CACHE_EXPIRY_TIME = 1.day
+
+ # Schema version required for this model
+ REQUIRED_SCHEMA_VERSION = 20180223120443
+
+ class << self
+ def track(event)
+ # For events without a project, we simply don't care.
+ # An example of this is the creation of a snippet (which
+ # is not related to any project).
+ return unless event.project_id
+
+ attributes = {
+ project_id: event.project_id,
+ user_id: event.author_id
+ }
+
+ cached_exists?(attributes) do
+ transaction(requires_new: true) do
+ begin
+ where(attributes).select(1).first || create!(attributes)
+ true # not caching the whole record here for now
+ rescue ActiveRecord::RecordNotUnique
+ # Note, above queries are not atomic and prone
+ # to race conditions (similar like #find_or_create!).
+ # In the case where we hit this, the record we want
+ # already exists - shortcut and return.
+ true
+ end
+ end
+ end
+ end
+
+ # Check if we can safely call .track (table exists)
+ def available?
+ @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization
+ end
+
+ # Flushes cached information about schema
+ def reset_column_information
+ @available_flag = nil
+ super
+ end
+
+ private
+
+ def cached_exists?(project_id:, user_id:, &block)
+ cache_key = "user_interacted_projects:#{project_id}:#{user_id}"
+ Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRY_TIME, &block)
+ end
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 4e8ef320af2..a3ebec0efc6 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -38,7 +38,7 @@ class MergeRequestWidgetEntity < IssuableEntity
# Diff sha's
expose :diff_head_sha do |merge_request|
- merge_request.diff_head_sha if merge_request.diff_head_commit
+ merge_request.diff_head_sha.presence
end
expose :merge_commit_message
diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb
index abf25bb778b..77e7b8a5ea7 100644
--- a/app/services/notes/build_service.rb
+++ b/app/services/notes/build_service.rb
@@ -26,14 +26,19 @@ module Notes
if project
project.notes.find_discussion(discussion_id)
else
- # only PersonalSnippets can have discussions without project association
discussion = Note.find_discussion(discussion_id)
noteable = discussion.noteable
- return nil unless noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
+ return nil unless noteable_without_project?(noteable)
discussion
end
end
+
+ def noteable_without_project?(noteable)
+ return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
+
+ false
+ end
end
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index 6a10e172483..ad3dcc5010b 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -11,7 +11,7 @@ module Notes
unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
- return if @note.for_personal_snippet?
+ return unless @note.for_project_noteable?
@note.create_cross_references!
execute_note_hooks
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 6835b14648b..e4be953e810 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -280,7 +280,7 @@ module NotificationRecipientService
add_participants(note.author)
add_mentions(note.author, target: note)
- unless note.for_personal_snippet?
+ if note.for_project_noteable?
# Merge project watchers
add_project_watchers
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 01838ec6b5d..7fa1387084c 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -85,7 +85,7 @@ module Projects
end
def after_create_actions
- log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
+ log_info("#{@project.owner.name} created a new project \"#{@project.full_name}\"")
unless @project.gitlab_project_import?
@project.write_repository_config
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index c2ca404b179..ffd48e842c2 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -241,8 +241,7 @@ class TodoService
end
def handle_note(note, author, skip_users = [])
- # Skip system notes, and notes on project snippet
- return if note.system? || note.for_snippet?
+ return unless note.can_create_todo?
project = note.project
target = note.noteable
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 68788134b8e..81d7db04a3c 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -657,9 +657,11 @@
.checkbox
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
- Version check enabled
+ Enable version check
.help-block
- Let GitLab inform you when an update is available.
+ GitLab will inform you if a new version is available.
+ = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")
+ about what information is shared with GitLab Inc.
.form-group
.col-sm-offset-2.col-sm-10
- can_be_configured = @application_setting.usage_ping_can_be_configured?
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index d8f96ed5b0d..a6324a97fd5 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -1,21 +1,20 @@
= form_errors(hook)
.form-group
- = form.label :url, 'URL', class: 'control-label'
- .col-sm-10
- = form.text_field :url, class: 'form-control'
+ = form.label :url, 'URL', class: 'label-light'
+ = form.text_field :url, class: 'form-control'
.form-group
- = form.label :token, 'Secret Token', class: 'control-label'
- .col-sm-10
- = form.text_field :token, class: 'form-control'
- %p.help-block
- Use this token to validate received payloads
+ = form.label :token, 'Secret Token', class: 'label-light'
+ = form.text_field :token, class: 'form-control'
+ %p.help-block
+ Use this token to validate received payloads
.form-group
- = form.label :url, 'Trigger', class: 'control-label'
- .col-sm-10.prepend-top-10
- %div
- System hook will be triggered on set of events like creating project
- or adding ssh key. But you can also enable extra triggers like Push events.
+ = form.label :url, 'Trigger', class: 'label-light'
+ %ul.list-unstyled
+ %li
+ .help-block
+ System hook will be triggered on set of events like creating project
+ or adding ssh key. But you can also enable extra triggers like Push events.
.prepend-top-default
= form.check_box :repository_update_events, class: 'pull-left'
@@ -24,21 +23,21 @@
%strong Repository update events
%p.light
This URL will be triggered when repository is updated
- %div
+ %li
= form.check_box :push_events, class: 'pull-left'
.prepend-left-20
= form.label :push_events, class: 'list-label' do
%strong Push events
%p.light
This URL will be triggered for each branch updated to the repository
- %div
+ %li
= form.check_box :tag_push_events, class: 'pull-left'
.prepend-left-20
= form.label :tag_push_events, class: 'list-label' do
%strong Tag push events
%p.light
This URL will be triggered when a new tag is pushed to the repository
- %div
+ %li
= form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= form.label :merge_requests_events, class: 'list-label' do
@@ -46,9 +45,8 @@
%p.light
This URL will be triggered when a merge request is created/updated/merged
.form-group
- = form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
- .col-sm-10
- .checkbox
- = form.label :enable_ssl_verification do
- = form.check_box :enable_ssl_verification
- %strong Enable SSL verification
+ = form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
+ .checkbox
+ = form.label :enable_ssl_verification do
+ = form.check_box :enable_ssl_verification
+ %strong Enable SSL verification
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index bc02d9969d6..d9e2ce5e74c 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,33 +1,35 @@
- page_title 'System Hooks'
-%h3.page-title
- System hooks
+.row.prepend-top-default
+ .col-lg-4
+ %h4.prepend-top-0
+ = page_title
+ %p
+ #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
+ used for binding events when GitLab creates a User or Project.
-%p.light
- #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
- used for binding events when GitLab creates a User or Project.
+ .col-lg-8.append-bottom-default
+ = form_for @hook, as: :hook, url: admin_hooks_path do |f|
+ = render partial: 'form', locals: { form: f, hook: @hook }
+ = f.submit 'Add system hook', class: 'btn btn-create'
-%hr
+ %hr
-= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
- = render partial: 'form', locals: { form: f, hook: @hook }
- .form-actions
- = f.submit 'Add system hook', class: 'btn btn-create'
-%hr
+ - if @hooks.any?
+ .panel.panel-default
+ .panel-heading
+ System hooks (#{@hooks.count})
+ %ul.content-list
+ - @hooks.each do |hook|
+ %li
+ .controls
+ = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
+ = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
+ = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
+ .monospace= hook.url
+ %div
+ - SystemHook.triggers.each_value do |event|
+ - if hook.public_send(event)
+ %span.label.label-gray= event.to_s.titleize
+ %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
-- if @hooks.any?
- .panel.panel-default
- .panel-heading
- System hooks (#{@hooks.count})
- %ul.content-list
- - @hooks.each do |hook|
- %li
- .controls
- = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
- = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
- = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
- .monospace= hook.url
- %div
- - SystemHook.triggers.each_value do |event|
- - if hook.public_send(event)
- %span.label.label-gray= event.to_s.titleize
- %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
+= render 'shared/plugins/index'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 80e4dce1a80..9c78bade254 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -4,6 +4,7 @@
- can_admin_label = can?(current_user, :admin_label, @project)
- if @labels.exists? || @prioritized_labels.exists?
+ #promote-label-modal
%div{ class: container_class }
.top-area.adjust
.nav-text
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 6a7bc4b1888..5b0197ed58c 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -13,6 +13,7 @@
.milestones
#delete-milestone-modal
+ #promote-milestone-modal
%ul.content-list
= render @milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index de381d489c6..b423888c875 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -27,8 +27,15 @@
Edit
- if @project.group
- = link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "Promoting #{@milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
- Promote
+ %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
+ target: '#promote-milestone-modal',
+ milestone_title: @milestone.title,
+ url: promote_project_milestone_path(@milestone.project, @milestone),
+ container: 'body' },
+ disabled: true,
+ type: 'button' }
+ = _('Promote')
+ #promote-milestone-modal
- if @milestone.active?
= link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 8847d11f623..5afbc78df53 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -48,8 +48,16 @@
.pull-right.hidden-xs.hidden-sm
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
- = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting #{label.title} will make it available for all projects inside #{label.project.group.name}. Existing project labels with the same name will be merged. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
- %span.sr-only Promote to Group
+ %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
+ disabled: true,
+ type: 'button',
+ data: { url: promote_project_label_path(label.project, label),
+ label_title: label.title,
+ label_color: label.color,
+ label_text_color: label.text_color,
+ target: '#promote-label-modal',
+ container: 'body',
+ toggle: 'modal' } }
= sprite_icon('level-up')
- if can?(current_user, :admin_label, label)
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index da01fc02d07..9db2a321526 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -51,8 +51,15 @@
\
- if @project.group
- = link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "Promoting #{milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
- Promote
+ %button.js-promote-project-milestone-button.btn.btn-xs.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
+ disabled: true,
+ type: 'button',
+ data: { url: promote_project_milestone_path(milestone.project, milestone),
+ milestone_title: milestone.title,
+ target: '#promote-milestone-modal',
+ container: 'body',
+ toggle: 'modal' } }
+ = _('Promote')
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml
new file mode 100644
index 00000000000..fc643c3ecc2
--- /dev/null
+++ b/app/views/shared/plugins/_index.html.haml
@@ -0,0 +1,23 @@
+- plugins = Gitlab::Plugin.files
+
+.row.prepend-top-default
+ .col-lg-4
+ %h4.prepend-top-0
+ Plugins
+ %p
+ #{link_to 'Plugins', help_page_path('administration/plugins')} are similar to
+ system hooks but are executed as files instead of sending data to a URL.
+
+ .col-lg-8.append-bottom-default
+ - if plugins.any?
+ .panel.panel-default
+ .panel-heading
+ Plugins (#{plugins.count})
+ %ul.content-list
+ - plugins.each do |file|
+ %li
+ .monospace
+ = File.basename(file)
+ - else
+ %p.light-well.text-center
+ No plugins found.
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 21da27973fe..2a4d65b5cb3 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -66,7 +66,7 @@ class EmailsOnPushWorker
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
- logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
+ logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
end
end
ensure