summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorClement Ho <clemmakesapps@gmail.com>2018-04-18 15:28:02 +0000
committerClement Ho <clemmakesapps@gmail.com>2018-04-18 15:28:02 +0000
commitcad4eb989e87c2e1ce996cfb5817d2c1729111c8 (patch)
treec22d766e5f9dd8a299a3d68cd70f8a04df7e754a /app
parent02579d6aeba3673c950896bdb511df8c678e20ca (diff)
parentd8dd75ca775f66fd756e43ddd73ac75d39fc3e64 (diff)
downloadgitlab-ce-cad4eb989e87c2e1ce996cfb5817d2c1729111c8.tar.gz
Merge branch 'master' into 'bootstrap4'
# Conflicts: # app/views/projects/issues/_nav_btns.html.haml # app/views/projects/merge_requests/creations/_new_compare.html.haml
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue58
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue130
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue88
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue30
-rw-r--r--app/assets/javascripts/ide/constants.js3
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js100
-rw-r--r--app/assets/stylesheets/pages/repo.scss76
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/concerns/notes_actions.rb4
-rw-r--r--app/controllers/dashboard/todos_controller.rb2
-rw-r--r--app/controllers/groups/variables_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/variables_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/diff_helper.rb2
-rw-r--r--app/helpers/safe_params_helper.rb11
-rw-r--r--app/mailers/emails/issues.rb6
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb30
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/notification_recipient.rb12
-rw-r--r--app/models/notification_setting.rb3
-rw-r--r--app/models/project.rb2
-rw-r--r--app/services/ci/register_job_service.rb23
-rw-r--r--app/services/clusters/gcp/verify_provision_status_service.rb2
-rw-r--r--app/services/create_deployment_service.rb4
-rw-r--r--app/services/import_export_clean_up_service.rb2
-rw-r--r--app/services/notification_recipient_service.rb11
-rw-r--r--app/services/notification_service.rb14
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/repository_archive_clean_up_service.rb2
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/test_hooks/base_service.rb2
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/issues.html.haml4
-rw-r--r--app/views/groups/issues.atom.builder2
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/layouts/mailer.text.erb2
-rw-r--r--app/views/layouts/notify.text.erb2
-rw-r--r--app/views/notify/issue_due_email.html.haml12
-rw-r--r--app/views/notify/issue_due_email.text.erb7
-rw-r--r--app/views/projects/blob/_viewer.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/diffs/_collapsed.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml8
-rw-r--r--app/workers/all_queues.yml3
-rw-r--r--app/workers/concerns/mail_scheduler_queue.rb7
-rw-r--r--app/workers/issue_due_scheduler_worker.rb10
-rw-r--r--app/workers/mail_scheduler/issue_due_worker.rb14
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
59 files changed, 523 insertions, 219 deletions
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 2cbd982af19..45321df191c 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -1,41 +1,27 @@
<script>
- import { mapState } from 'vuex';
- import { sprintf, __ } from '~/locale';
- import * as consts from '../../stores/modules/commit/constants';
- import RadioGroup from './radio_group.vue';
+import { mapState } from 'vuex';
+import { sprintf, __ } from '~/locale';
+import * as consts from '../../stores/modules/commit/constants';
+import RadioGroup from './radio_group.vue';
- export default {
- components: {
- RadioGroup,
+export default {
+ components: {
+ RadioGroup,
+ },
+ computed: {
+ ...mapState(['currentBranchId']),
+ commitToCurrentBranchText() {
+ return sprintf(
+ __('Commit to %{branchName} branch'),
+ { branchName: `<strong class="monospace">${this.currentBranchId}</strong>` },
+ false,
+ );
},
- computed: {
- ...mapState([
- 'currentBranchId',
- ]),
- newMergeRequestHelpText() {
- return sprintf(
- __('Creates a new branch from %{branchName} and re-directs to create a new merge request'),
- { branchName: this.currentBranchId },
- );
- },
- commitToCurrentBranchText() {
- return sprintf(
- __('Commit to %{branchName} branch'),
- { branchName: `<strong>${this.currentBranchId}</strong>` },
- false,
- );
- },
- commitToNewBranchText() {
- return sprintf(
- __('Creates a new branch from %{branchName}'),
- { branchName: this.currentBranchId },
- );
- },
- },
- commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
- commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
- commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
- };
+ },
+ commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
+ commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
+ commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
+};
</script>
<template>
@@ -53,13 +39,11 @@
:value="$options.commitToNewBranch"
:label="__('Create a new branch')"
:show-input="true"
- :help-text="commitToNewBranchText"
/>
<radio-group
:value="$options.commitToNewBranchMR"
:label="__('Create a new branch and merge request')"
:show-input="true"
- :help-text="newMergeRequestHelpText"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
new file mode 100644
index 00000000000..dcd934f76b7
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -0,0 +1,130 @@
+<script>
+import { __, sprintf } from '../../../locale';
+import Icon from '../../../vue_shared/components/icon.vue';
+import popover from '../../../vue_shared/directives/popover';
+import { MAX_TITLE_LENGTH, MAX_BODY_LENGTH } from '../../constants';
+
+export default {
+ directives: {
+ popover,
+ },
+ components: {
+ Icon,
+ },
+ props: {
+ text: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ scrollTop: 0,
+ isFocused: false,
+ };
+ },
+ computed: {
+ allLines() {
+ return this.text.split('\n').map((line, i) => ({
+ text: line.substr(0, this.getLineLength(i)) || ' ',
+ highlightedText: line.substr(this.getLineLength(i)),
+ }));
+ },
+ },
+ methods: {
+ handleScroll() {
+ if (this.$refs.textarea) {
+ this.$nextTick(() => {
+ this.scrollTop = this.$refs.textarea.scrollTop;
+ });
+ }
+ },
+ getLineLength(i) {
+ return i === 0 ? MAX_TITLE_LENGTH : MAX_BODY_LENGTH;
+ },
+ onInput(e) {
+ this.$emit('input', e.target.value);
+ },
+ updateIsFocused(isFocused) {
+ this.isFocused = isFocused;
+ },
+ },
+ popoverOptions: {
+ trigger: 'hover',
+ placement: 'top',
+ content: sprintf(
+ __(`
+ The character highligher helps you keep the subject line to %{titleLength} characters
+ and wrap the body at %{bodyLength} so they are readable in git.
+ `),
+ { titleLength: MAX_TITLE_LENGTH, bodyLength: MAX_BODY_LENGTH },
+ ),
+ },
+};
+</script>
+
+<template>
+ <fieldset class="common-note-form ide-commit-message-field">
+ <div
+ class="md-area"
+ :class="{
+ 'is-focused': isFocused
+ }"
+ >
+ <div
+ v-once
+ class="md-header"
+ >
+ <ul class="nav-links">
+ <li>
+ {{ __('Commit Message') }}
+ <span
+ v-popover="$options.popoverOptions"
+ class="help-block prepend-left-10"
+ >
+ <icon
+ name="question"
+ />
+ </span>
+ </li>
+ </ul>
+ </div>
+ <div class="ide-commit-message-textarea-container">
+ <div class="ide-commit-message-highlights-container">
+ <div
+ class="note-textarea highlights monospace"
+ :style="{
+ transform: `translate3d(0, ${-scrollTop}px, 0)`
+ }"
+ >
+ <div
+ v-for="(line, index) in allLines"
+ :key="index"
+ >
+ <span
+ v-text="line.text"
+ >
+ </span><mark
+ v-show="line.highlightedText"
+ v-text="line.highlightedText"
+ >
+ </mark>
+ </div>
+ </div>
+ </div>
+ <textarea
+ class="note-textarea ide-commit-message-textarea"
+ name="commit-message"
+ :placeholder="__('Write a commit message...')"
+ :value="text"
+ @scroll="handleScroll"
+ @input="onInput"
+ @focus="updateIsFocused(true)"
+ @blur="updateIsFocused(false)"
+ ref="textarea"
+ >
+ </textarea>
+ </div>
+ </div>
+ </fieldset>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 4310d762c78..b660a2961cb 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -1,52 +1,40 @@
<script>
- import { mapActions, mapState, mapGetters } from 'vuex';
- import tooltip from '~/vue_shared/directives/tooltip';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import tooltip from '~/vue_shared/directives/tooltip';
- export default {
- directives: {
- tooltip,
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ value: {
+ type: String,
+ required: true,
},
- props: {
- value: {
- type: String,
- required: true,
- },
- label: {
- type: String,
- required: false,
- default: null,
- },
- checked: {
- type: Boolean,
- required: false,
- default: false,
- },
- showInput: {
- type: Boolean,
- required: false,
- default: false,
- },
- helpText: {
- type: String,
- required: false,
- default: null,
- },
+ label: {
+ type: String,
+ required: false,
+ default: null,
},
- computed: {
- ...mapState('commit', [
- 'commitAction',
- ]),
- ...mapGetters('commit', [
- 'newBranchName',
- ]),
+ checked: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- methods: {
- ...mapActions('commit', [
- 'updateCommitAction',
- 'updateBranchName',
- ]),
+ showInput: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- };
+ },
+ computed: {
+ ...mapState('commit', ['commitAction']),
+ ...mapGetters('commit', ['newBranchName']),
+ },
+ methods: {
+ ...mapActions('commit', ['updateCommitAction', 'updateBranchName']),
+ },
+};
</script>
<template>
@@ -65,18 +53,6 @@
{{ label }}
</template>
<slot v-else></slot>
- <span
- v-if="helpText"
- v-tooltip
- class="help-block inline"
- :title="helpText"
- >
- <i
- class="fa fa-question-circle"
- aria-hidden="true"
- >
- </i>
- </span>
</span>
</label>
<div
@@ -85,7 +61,7 @@
>
<input
type="text"
- class="form-control"
+ class="form-control monospace"
:placeholder="newBranchName"
@input="updateBranchName($event.target.value)"
/>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 121babf1b2b..1e4adf1a373 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -5,6 +5,7 @@ import icon from '~/vue_shared/components/icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import commitFilesList from './commit_sidebar/list.vue';
+import CommitMessageField from './commit_sidebar/message_field.vue';
import * as consts from '../stores/modules/commit/constants';
import Actions from './commit_sidebar/actions.vue';
@@ -15,6 +16,7 @@ export default {
commitFilesList,
Actions,
LoadingButton,
+ CommitMessageField,
},
directives: {
tooltip,
@@ -38,15 +40,9 @@ export default {
'changedFiles',
]),
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
- ...mapGetters('commit', [
- 'commitButtonDisabled',
- 'discardDraftButtonDisabled',
- 'branchName',
- ]),
+ ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']),
statusSvg() {
- return this.lastCommitMsg
- ? this.committedStateSvgPath
- : this.noChangesStateSvgPath;
+ return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
},
},
methods: {
@@ -64,9 +60,7 @@ export default {
});
},
forceCreateNewBranch() {
- return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() =>
- this.commitChanges(),
- );
+ return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges());
},
},
};
@@ -105,16 +99,10 @@ export default {
@submit.prevent.stop="commitChanges"
v-if="!rightPanelCollapsed"
>
- <div class="multi-file-commit-fieldset">
- <textarea
- class="form-control multi-file-commit-message"
- name="commit-message"
- :value="commitMessage"
- :placeholder="__('Write a commit message...')"
- @input="updateCommitMessage($event.target.value)"
- >
- </textarea>
- </div>
+ <commit-message-field
+ :text="commitMessage"
+ @input="updateCommitMessage"
+ />
<div class="clearfix prepend-top-15">
<actions />
<loading-button
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
new file mode 100644
index 00000000000..b60d042e0be
--- /dev/null
+++ b/app/assets/javascripts/ide/constants.js
@@ -0,0 +1,3 @@
+// Fuzzy file finder
+export const MAX_TITLE_LENGTH = 50;
+export const MAX_BODY_LENGTH = 72;
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index b3882cb8d21..4eb23b2ee0f 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -5,45 +5,71 @@ import * as types from '../mutation_types';
export const getProjectData = (
{ commit, state, dispatch },
{ namespace, projectId, force = false } = {},
-) => new Promise((resolve, reject) => {
- if (!state.projects[`${namespace}/${projectId}`] || force) {
- commit(types.TOGGLE_LOADING, { entry: state });
- service.getProjectData(namespace, projectId)
- .then(res => res.data)
- .then((data) => {
+) =>
+ new Promise((resolve, reject) => {
+ if (!state.projects[`${namespace}/${projectId}`] || force) {
commit(types.TOGGLE_LOADING, { entry: state });
- commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
- if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
- resolve(data);
- })
- .catch(() => {
- flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
- reject(new Error(`Project not loaded ${namespace}/${projectId}`));
- });
- } else {
- resolve(state.projects[`${namespace}/${projectId}`]);
- }
-});
+ service
+ .getProjectData(namespace, projectId)
+ .then(res => res.data)
+ .then(data => {
+ commit(types.TOGGLE_LOADING, { entry: state });
+ commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
+ if (!state.currentProjectId)
+ commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
+ resolve(data);
+ })
+ .catch(() => {
+ flash(
+ 'Error loading project data. Please try again.',
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+ reject(new Error(`Project not loaded ${namespace}/${projectId}`));
+ });
+ } else {
+ resolve(state.projects[`${namespace}/${projectId}`]);
+ }
+ });
export const getBranchData = (
{ commit, state, dispatch },
{ projectId, branchId, force = false } = {},
-) => new Promise((resolve, reject) => {
- if ((typeof state.projects[`${projectId}`] === 'undefined' ||
- !state.projects[`${projectId}`].branches[branchId])
- || force) {
- service.getBranchData(`${projectId}`, branchId)
- .then(({ data }) => {
- const { id } = data.commit;
- commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
- commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
- resolve(data);
- })
- .catch(() => {
- flash('Error loading branch data. Please try again.', 'alert', document, null, false, true);
- reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
- });
- } else {
- resolve(state.projects[`${projectId}`].branches[branchId]);
- }
-});
+) =>
+ new Promise((resolve, reject) => {
+ if (
+ typeof state.projects[`${projectId}`] === 'undefined' ||
+ !state.projects[`${projectId}`].branches[branchId] ||
+ force
+ ) {
+ service
+ .getBranchData(`${projectId}`, branchId)
+ .then(({ data }) => {
+ const { id } = data.commit;
+ commit(types.SET_BRANCH, {
+ projectPath: `${projectId}`,
+ branchName: branchId,
+ branch: data,
+ });
+ commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+ commit(types.SET_CURRENT_BRANCH, branchId);
+ resolve(data);
+ })
+ .catch(() => {
+ flash(
+ 'Error loading branch data. Please try again.',
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+ reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
+ });
+ } else {
+ resolve(state.projects[`${projectId}`].branches[branchId]);
+ }
+ });
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 5015298e8ba..ae6cd3ab3f7 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -662,11 +662,6 @@
}
}
-.multi-file-commit-message.form-control {
- height: 160px;
- resize: none;
-}
-
.dirty-diff {
// !important need to override monaco inline style
width: 4px !important;
@@ -839,3 +834,74 @@
align-items: center;
font-weight: $gl-font-weight-bold;
}
+
+.ide-commit-message-field {
+ height: 200px;
+ background-color: $white-light;
+
+ .md-area {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+
+ .nav-links {
+ height: 30px;
+ }
+
+ .help-block {
+ margin-top: 2px;
+ color: $blue-500;
+ cursor: pointer;
+ }
+}
+
+.ide-commit-message-textarea-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ .note-textarea {
+ font-family: $monospace_font;
+ }
+}
+
+.ide-commit-message-highlights-container {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: -100px;
+ bottom: 0;
+ padding-right: 100px;
+ pointer-events: none;
+ z-index: 1;
+
+ .highlights {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ color: transparent;
+ }
+
+ mark {
+ margin-left: -1px;
+ padding: 0 2px;
+ border-radius: $border-radius-small;
+ background-color: $orange-200;
+ color: transparent;
+ opacity: 0.6;
+ }
+}
+
+.ide-commit-message-textarea {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2;
+ background: transparent;
+ resize: none;
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 24651dd392c..0fdd4d2cb47 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
+ include SafeParamsHelper
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index ad4e936a3d4..0c34e49206a 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -217,7 +217,7 @@ module NotesActions
def note_project
strong_memoize(:note_project) do
- return nil unless project
+ next nil unless project
note_project_id = params[:note_project_id]
@@ -228,7 +228,7 @@ module NotesActions
project
end
- return access_denied! unless can?(current_user, :create_note, the_project)
+ next access_denied! unless can?(current_user, :create_note, the_project)
the_project
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index e89eaf7edda..f9e8fe624e8 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -86,7 +86,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
out_of_range = todos.current_page > total_pages
if out_of_range
- redirect_to url_for(params.merge(page: total_pages, only_path: true))
+ redirect_to url_for(safe_params.merge(page: total_pages, only_path: true))
end
out_of_range
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
index 6142e75b4c1..4d8a20de017 100644
--- a/app/controllers/groups/variables_controller.rb
+++ b/app/controllers/groups/variables_controller.rb
@@ -15,7 +15,7 @@ module Groups
def update
if @group.update(group_variables_params)
respond_to do |format|
- format.json { return render_group_variables }
+ format.json { render_group_variables }
end
else
respond_to do |format|
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 5ac4b8710e2..79fa5818359 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -189,6 +189,6 @@ class GroupsController < Groups::ApplicationController
params[:id] = group.to_param
- url_for(params)
+ url_for(safe_params)
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 54e7d81de6a..62b739918e6 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -60,13 +60,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
format.patch do
- return render_404 unless @merge_request.diff_refs
+ break render_404 unless @merge_request.diff_refs
send_git_patch @project.repository, @merge_request.diff_refs
end
format.diff do
- return render_404 unless @merge_request.diff_refs
+ break render_404 unless @merge_request.diff_refs
send_git_diff @project.repository, @merge_request.diff_refs
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 517d0b026c2..bf09ea7e4d8 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -12,7 +12,7 @@ class Projects::VariablesController < Projects::ApplicationController
def update
if @project.update(variables_params)
respond_to do |format|
- format.json { return render_variables }
+ format.json { render_variables }
end
else
respond_to do |format|
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 37f14230196..a93b116c6fe 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -404,7 +404,7 @@ class ProjectsController < Projects::ApplicationController
params[:namespace_id] = project.namespace.to_param
params[:id] = project.to_param
- url_for(params)
+ url_for(safe_params)
end
def project_export_enabled
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 956df4a0a16..31f47a7aa7c 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -146,6 +146,6 @@ class UsersController < ApplicationController
end
def build_canonical_path(user)
- url_for(params.merge(username: user.to_param))
+ url_for(safe_params.merge(username: user.to_param))
end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 866b8773db6..fef29789832 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -259,7 +259,7 @@ module BlobHelper
options = []
if error == :collapsed
- options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, expanded: true, format: nil)))
+ options << link_to('load it anyway', url_for(safe_params.merge(viewer: viewer.type, expanded: true, format: nil)))
end
# If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b5ca39711bc..1bb82fd8150 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -180,7 +180,7 @@ module DiffHelper
private
def diff_btn(title, name, selected)
- params_copy = params.dup
+ params_copy = safe_params.dup
params_copy[:view] = name
# Always use HTML to handle case where JSON diff rendered this button
diff --git a/app/helpers/safe_params_helper.rb b/app/helpers/safe_params_helper.rb
new file mode 100644
index 00000000000..b568e8810cc
--- /dev/null
+++ b/app/helpers/safe_params_helper.rb
@@ -0,0 +1,11 @@
+module SafeParamsHelper
+ # Rails 5.0 requires to permit `params` if they're used in url helpers.
+ # Use this helper when generating links with `params.merge(...)`
+ def safe_params
+ if params.respond_to?(:permit!)
+ params.except(:host, :port, :protocol).permit!
+ else
+ params
+ end
+ end
+end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index b33131becd3..392cc0bee03 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -6,6 +6,12 @@ module Emails
mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end
+ def issue_due_email(recipient_id, issue_id, reason = nil)
+ setup_issue_mail(issue_id, recipient_id)
+
+ mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
+ end
+
def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 4aa65bf4273..1c8d9ca4aa5 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -162,7 +162,7 @@ module Ci
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
end
- before_transition pending: :running do |build|
+ after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
end
@@ -479,7 +479,7 @@ module Ci
def user_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- return variables if user.blank?
+ break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
@@ -594,7 +594,7 @@ module Ci
def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- return variables unless persisted?
+ break variables unless persisted?
variables
.append(key: 'CI_JOB_ID', value: id.to_s)
@@ -643,7 +643,7 @@ module Ci
def persisted_environment_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- return variables unless persisted? && persisted_environment.present?
+ break variables unless persisted? && persisted_environment.present?
variables.concat(persisted_environment.predefined_variables)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3469d5d795c..b6276c2fb50 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -87,7 +87,7 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :pending, :running, :manual] => :canceled
end
- before_transition created: [:pending, :running] do |commit_status|
+ before_transition [:created, :skipped, :manual] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 4ae5dd8c677..db8cf322ef7 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,7 +11,9 @@ module CacheMarkdownField
extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output
- CACHE_VERSION = 3
+ CACHE_REDCARPET_VERSION = 3
+ CACHE_COMMONMARK_VERSION_START = 10
+ CACHE_COMMONMARK_VERSION = 11
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
@@ -49,12 +51,14 @@ module CacheMarkdownField
# Always include a project key, or Banzai complains
project = self.project if self.respond_to?(:project)
- group = self.group if self.respond_to?(:group)
+ group = self.group if self.respond_to?(:group)
context = cached_markdown_fields[field].merge(project: project, group: group)
# Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author)
+ context[:markdown_engine] = markdown_engine
+
context
end
@@ -69,7 +73,7 @@ module CacheMarkdownField
Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
]
end.to_h
- updates['cached_markdown_version'] = CacheMarkdownField::CACHE_VERSION
+ updates['cached_markdown_version'] = latest_cached_markdown_version
updates.each {|html_field, data| write_attribute(html_field, data) }
end
@@ -90,7 +94,7 @@ module CacheMarkdownField
markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false
- CacheMarkdownField::CACHE_VERSION == cached_markdown_version &&
+ latest_cached_markdown_version == cached_markdown_version &&
(html_changed || markdown_changed == html_changed)
end
@@ -109,6 +113,24 @@ module CacheMarkdownField
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
+ def latest_cached_markdown_version
+ return CacheMarkdownField::CACHE_REDCARPET_VERSION unless cached_markdown_version
+
+ if cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
+ CacheMarkdownField::CACHE_REDCARPET_VERSION
+ else
+ CacheMarkdownField::CACHE_COMMONMARK_VERSION
+ end
+ end
+
+ def markdown_engine
+ if latest_cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
+ :redcarpet
+ else
+ :common_mark
+ end
+ end
+
included do
cattr_reader :cached_markdown_fields do
FieldData.new
diff --git a/app/models/issue.rb b/app/models/issue.rb
index c1ffe6512ea..702bfc77803 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -49,6 +49,7 @@ class Issue < ActiveRecord::Base
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
+ scope :due_tomorrow, -> { where(due_date: Date.tomorrow) }
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index b3ffad00a07..2c3580bbdc6 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -83,14 +83,14 @@ class NotificationRecipient
def has_access?
DeclarativePolicy.subject_scope do
- return false unless user.can?(:receive_notifications)
- return true if @skip_read_ability
+ break false unless user.can?(:receive_notifications)
+ break true if @skip_read_ability
- return false if @target && !user.can?(:read_cross_project)
- return false if @project && !user.can?(:read_project, @project)
+ break false if @target && !user.can?(:read_cross_project)
+ break false if @project && !user.can?(:read_project, @project)
- return true unless read_ability
- return true unless DeclarativePolicy.has_policy?(@target)
+ break true unless read_ability
+ break true unless DeclarativePolicy.has_policy?(@target)
user.can?(read_ability, @target)
end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index f6d9b0215fc..9195408551f 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -47,7 +47,8 @@ class NotificationSetting < ActiveRecord::Base
].freeze
EXCLUDED_WATCHER_EVENTS = [
- :push_to_merge_request
+ :push_to_merge_request,
+ :issue_due
].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
def self.find_or_create_for(source)
diff --git a/app/models/project.rb b/app/models/project.rb
index ffd78d3ab70..38139a9b137 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1637,7 +1637,7 @@ class Project < ActiveRecord::Base
def container_registry_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- return variables unless Gitlab.config.registry.enabled
+ break variables unless Gitlab.config.registry.enabled
variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index e09b445636f..c289b44f885 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -4,6 +4,9 @@ module Ci
class RegisterJobService
attr_reader :runner
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
+ JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
+
Result = Struct.new(:build, :valid?)
def initialize(runner)
@@ -30,7 +33,7 @@ module Ci
end
end
- builds.find do |build|
+ builds.auto_include(false).find do |build|
next unless runner.can_pick?(build)
begin
@@ -41,7 +44,7 @@ module Ci
build.run!
register_success(build)
- return Result.new(build, true)
+ return Result.new(build, true) # rubocop:disable Cop/AvoidReturnFromBlocks
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
end
@@ -104,10 +107,22 @@ module Ci
end
def register_success(job)
- job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
+ labels = { shared_runner: runner.shared?,
+ jobs_running_for_project: jobs_running_for_project(job) }
+
+ job_queue_duration_seconds.observe(labels, Time.now - job.queued_at) unless job.queued_at.nil?
attempt_counter.increment
end
+ def jobs_running_for_project(job)
+ return '+Inf' unless runner.shared?
+
+ # excluding currently started job
+ running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
+ .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
+ running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
+ end
+
def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end
@@ -117,7 +132,7 @@ module Ci
end
def job_queue_duration_seconds
- @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
+ @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
end
end
end
diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb
index f994aacd086..7cc4324677e 100644
--- a/app/services/clusters/gcp/verify_provision_status_service.rb
+++ b/app/services/clusters/gcp/verify_provision_status_service.rb
@@ -17,7 +17,7 @@ module Clusters
when 'DONE'
finalize_creation
else
- return provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
+ provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
end
end
end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index 88dfb7a4a90..7e5a77fb056 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -19,8 +19,8 @@ class CreateDeploymentService
environment.fire_state_event(action)
- return unless environment.save
- return if environment.stopped?
+ break unless environment.save
+ break if environment.stopped?
deploy.tap(&:update_merge_request_metrics!)
end
diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb
index 6442406d77e..74088b970c9 100644
--- a/app/services/import_export_clean_up_service.rb
+++ b/app/services/import_export_clean_up_service.rb
@@ -10,7 +10,7 @@ class ImportExportCleanUpService
def execute
Gitlab::Metrics.measure(:import_export_clean_up) do
- return unless File.directory?(path)
+ next unless File.directory?(path)
clean_up_export_files
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index b82d9c64296..83e59a649b6 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -203,10 +203,11 @@ module NotificationRecipientService
attr_reader :action
attr_reader :previous_assignee
attr_reader :skip_current_user
- def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
+ def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true)
@target = target
@current_user = current_user
@action = action
+ @custom_action = custom_action
@previous_assignee = previous_assignee
@skip_current_user = skip_current_user
end
@@ -236,7 +237,13 @@ module NotificationRecipientService
add_mentions(current_user, target: target)
# Add the assigned users, if any
- assignees = custom_action == :new_issue ? target.assignees : target.assignee
+ assignees = case custom_action
+ when :new_issue
+ target.assignees
+ else
+ target.assignee
+ end
+
# We use the `:participating` notification level in order to match existing legacy behavior as captured
# in existing specs (notification_service_spec.rb ~ line 507)
add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index f94c76cf3ac..274161df946 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -373,6 +373,20 @@ class NotificationService
end
end
+ def issue_due(issue)
+ recipients = NotificationRecipientService.build_recipients(
+ issue,
+ issue.author,
+ action: 'due',
+ custom_action: :issue_due,
+ skip_current_user: false
+ )
+
+ recipients.each do |recipient|
+ mailer.send(:issue_due_email, recipient.user.id, issue.id, recipient.reason).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, method)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index aa14206db3b..44e869851ca 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -137,7 +137,7 @@ module Projects
return true unless Gitlab.config.registry.enabled
ContainerRepository.build_root_repository(project).tap do |repository|
- return repository.has_tags? ? repository.delete_tags! : true
+ break repository.has_tags? ? repository.delete_tags! : true
end
end
diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb
index aa84d36a206..9a88459b511 100644
--- a/app/services/repository_archive_clean_up_service.rb
+++ b/app/services/repository_archive_clean_up_service.rb
@@ -10,7 +10,7 @@ class RepositoryArchiveCleanUpService
def execute
Gitlab::Metrics.measure(:repository_archive_clean_up) do
- return unless File.directory?(path)
+ next unless File.directory?(path)
clean_up_old_archives
clean_up_empty_directories
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 00bf5434b7f..958ef065012 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -159,7 +159,7 @@ module SystemNoteService
body = if noteable.time_estimate == 0
"removed time estimate"
else
- "changed time estimate to #{parsed_time}"
+ "changed time estimate to #{parsed_time},"
end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index e9aefb1fb75..aadc1ea644b 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -19,7 +19,7 @@ module TestHooks
error_message = catch(:validation_error) do
sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
- return hook.execute(sample_data, trigger_key)
+ return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks
end
error(error_message)
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 70ec6bc6257..d7b6fb9a4a1 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,5 +1,5 @@
xml.title "#{current_user.name} issues"
-xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index bb472b4c900..4bf04dadf01 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -2,12 +2,12 @@
- page_title _("Issues")
- @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
+ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
- = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
+ = link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index a239ea8caf0..2a385b661e5 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -1,5 +1,5 @@
xml.title "#{@group.name} issues"
-xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 36df03302e8..bbfbea4ac7a 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,6 +1,6 @@
- page_title "Issues"
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
+ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
- if group_issues_count(state: 'all').zero?
= render 'shared/empty_states/issues', project_select_button: true
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 198f30a1dc4..8e20c4a4b2a 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -1,4 +1,4 @@
<%= yield -%>
----
+-- <%# signature marker %>
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index de48f548a1b..9dc490efa9a 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -1,6 +1,6 @@
<%= yield -%>
----
+-- <%# signature marker %>
<% if @target_url -%>
<% if @reply_by_email -%>
<%= "Reply to this email directly or view it on GitLab: #{@target_url}" -%>
diff --git a/app/views/notify/issue_due_email.html.haml b/app/views/notify/issue_due_email.html.haml
new file mode 100644
index 00000000000..e81144b8fcb
--- /dev/null
+++ b/app/views/notify/issue_due_email.html.haml
@@ -0,0 +1,12 @@
+%p.details
+ #{link_to @issue.author_name, user_url(@issue.author)}'s issue is due soon.
+
+- if @issue.assignees.any?
+ %p
+ Assignee: #{@issue.assignee_list}
+%p
+ This issue is due on: #{@issue.due_date.to_s(:medium)}
+
+- if @issue.description
+ %div
+ = markdown(@issue.description, pipeline: :email, author: @issue.author)
diff --git a/app/views/notify/issue_due_email.text.erb b/app/views/notify/issue_due_email.text.erb
new file mode 100644
index 00000000000..3c7a57a8a2e
--- /dev/null
+++ b/app/views/notify/issue_due_email.text.erb
@@ -0,0 +1,7 @@
+The following issue is due on <%= @issue.due_date %>:
+
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Author: <%= @issue.author_name %>
+Assignee: <%= @issue.assignee_list %>
+
+<%= @issue.description %>
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index 9c760c81527..b9663bbba15 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -4,7 +4,7 @@
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- external_embed = local_assigns.fetch(:external_embed, false)
-- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
+- viewer_url = local_assigns.fetch(:viewer_url) { url_for(safe_params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
- if render_error
= render 'projects/blob/render_error', viewer: viewer
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 8077a559a99..54767bdf757 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -29,7 +29,7 @@
= s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref
- .divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
+ .divergence-graph.hidden-xs{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
default_branch: @repository.root_ref,
number_commits_ahead: diverging_count_label(number_commits_ahead) } }
.graph-side
diff --git a/app/views/projects/diffs/_collapsed.html.haml b/app/views/projects/diffs/_collapsed.html.haml
index 8772bd4705f..5762f4d86d7 100644
--- a/app/views/projects/diffs/_collapsed.html.haml
+++ b/app/views/projects/diffs/_collapsed.html.haml
@@ -1,5 +1,5 @@
- diff_file = viewer.diff_file
-- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
+- url = url_for(safe_params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
.nothing-here-block.diff-collapsed{ data: { diff_for_path: url } }
This diff is collapsed.
%a.click-to-expand Click to expand it.
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index ba75a6b0e29..2cad6f47754 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,4 +1,4 @@
-= link_to params.merge(rss_url_options), class: 'btn btn-secondary append-right-10 has-tooltip', title: 'Subscribe' do
+= link_to safe_params.merge(rss_url_options), class: 'btn btn-secondary append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
- if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 4029926f373..6330245954e 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,5 +1,5 @@
xml.title "#{@project.name} issues"
-xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
+xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html"
xml.id project_issues_url(@project)
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index c427a9eedc2..1e7737aeb97 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -5,7 +5,7 @@
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
+ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
- if project_issues(@project).exists?
%div{ class: (container_class) }
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 813f8a34f71..73454a91ed8 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -3,7 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.d-none.alert.alert-danger.mr-compare-errors
- .merge-request-branches.js-merge-request-new-compare.row.col-md-12{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
+ .js-merge-request-new-compare.row.col-md-12{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6.pl-0
.card.card-new-merge-request
.card-header
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 8f6adf56b3b..9d95f1c7b7f 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -26,16 +26,16 @@
- else
%ul.merge-request-tabs.nav-links.no-top.no-bottom
%li.commits-tab.active
- = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
+ = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
Commits
%span.badge.badge-pill= @commits.size
- if @pipelines.any?
%li.builds-tab
- = link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
+ = link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
Pipelines
%span.badge.badge-pill= @pipelines.size
%li.diffs-tab
- = link_to url_for(params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+ = link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge.badge-pill= @merge_request.diff_size
@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
- = render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true
+ = render 'projects/merge_requests/pipelines', endpoint: url_for(safe_params.merge(action: 'pipelines', format: :json)), disable_initialization: true
.mr-loading-status
= spinner
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 9a11cdb121e..9aea3bad27b 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -18,6 +18,7 @@
- cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs
- cronjob:trending_projects
+- cronjob:issue_due_scheduler
- gcp_cluster:cluster_install_app
- gcp_cluster:cluster_provision
@@ -39,6 +40,8 @@
- github_importer:github_import_stage_import_pull_requests
- github_importer:github_import_stage_import_repository
+- mail_scheduler:mail_scheduler_issue_due
+
- object_storage_upload
- object_storage:object_storage_background_move
- object_storage:object_storage_migrate_uploads
diff --git a/app/workers/concerns/mail_scheduler_queue.rb b/app/workers/concerns/mail_scheduler_queue.rb
new file mode 100644
index 00000000000..9df55ad9522
--- /dev/null
+++ b/app/workers/concerns/mail_scheduler_queue.rb
@@ -0,0 +1,7 @@
+module MailSchedulerQueue
+ extend ActiveSupport::Concern
+
+ included do
+ queue_namespace :mail_scheduler
+ end
+end
diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb
new file mode 100644
index 00000000000..16ab5d069e0
--- /dev/null
+++ b/app/workers/issue_due_scheduler_worker.rb
@@ -0,0 +1,10 @@
+class IssueDueSchedulerWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ def perform
+ project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
+
+ MailScheduler::IssueDueWorker.bulk_perform_async(project_ids)
+ end
+end
diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb
new file mode 100644
index 00000000000..b06079d68ca
--- /dev/null
+++ b/app/workers/mail_scheduler/issue_due_worker.rb
@@ -0,0 +1,14 @@
+module MailScheduler
+ class IssueDueWorker
+ include ApplicationWorker
+ include MailSchedulerQueue
+
+ def perform(project_id)
+ notification_service = NotificationService.new
+
+ Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
+ notification_service.issue_due(issue)
+ end
+ end
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3909dbf7d7f..f88b3fdbfb1 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -33,7 +33,7 @@ class PostReceive
unless @user
log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
- return false
+ return false # rubocop:disable Cop/AvoidReturnFromBlocks
end
if Gitlab::Git.tag_ref?(ref)
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index fb26fa4c515..7ebf69bdc39 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -38,7 +38,7 @@ class StuckCiJobsWorker
def drop_stuck(status, timeout)
search(status, timeout) do |build|
- return unless build.stuck?
+ break unless build.stuck?
drop_build :stuck, build, status, timeout
end