summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/dispatcher.js2
-rw-r--r--app/assets/javascripts/dropzone_input.js1
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue189
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue7
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue79
-rw-r--r--app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue23
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue54
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue111
-rw-r--r--app/assets/javascripts/issue_show/components/fields/project_move.vue83
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue31
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue104
-rw-r--r--app/assets/javascripts/issue_show/components/locked_warning.vue20
-rw-r--r--app/assets/javascripts/issue_show/event_hub.js3
-rw-r--r--app/assets/javascripts/issue_show/index.js81
-rw-r--r--app/assets/javascripts/issue_show/mixins/animate.js2
-rw-r--r--app/assets/javascripts/issue_show/mixins/update.js10
-rw-r--r--app/assets/javascripts/issue_show/services/index.js17
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js22
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue24
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js4
-rw-r--r--app/assets/javascripts/task_list.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue107
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue113
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue33
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue58
-rw-r--r--app/assets/javascripts/vue_shared/mixins/tooltip.js4
-rw-r--r--app/assets/stylesheets/framework/filters.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss6
-rw-r--r--app/controllers/admin/jobs_controller.rb (renamed from app/controllers/admin/builds_controller.rb)4
-rw-r--r--app/controllers/concerns/issuable_actions.rb11
-rw-r--r--app/controllers/concerns/renders_blob.rb4
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb6
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb55
-rw-r--r--app/controllers/projects/builds_controller.rb122
-rw-r--r--app/controllers/projects/issues_controller.rb5
-rw-r--r--app/controllers/projects/jobs_controller.rb131
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/finders/projects_finder.rb33
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb16
-rw-r--r--app/helpers/builds_helper.rb8
-rw-r--r--app/helpers/diff_helper.rb12
-rw-r--r--app/helpers/gitlab_routing_helper.rb16
-rw-r--r--app/helpers/issuables_helper.rb21
-rw-r--r--app/models/application_setting.rb14
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/blob.rb12
-rw-r--r--app/models/blob_viewer/auxiliary.rb4
-rw-r--r--app/models/blob_viewer/base.rb26
-rw-r--r--app/models/blob_viewer/client_side.rb4
-rw-r--r--app/models/blob_viewer/server_side.rb4
-rw-r--r--app/models/blob_viewer/text.rb4
-rw-r--r--app/models/ci/build.rb10
-rw-r--r--app/models/ci/pipeline.rb15
-rw-r--r--app/models/ci/pipeline_schedule.rb4
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/concerns/noteable.rb7
-rw-r--r--app/models/diff_note.rb6
-rw-r--r--app/models/discussion.rb3
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb6
-rw-r--r--app/models/legacy_diff_note.rb2
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_team.rb9
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/service.rb2
-rw-r--r--app/models/user.rb9
-rw-r--r--app/policies/ci/build_policy.rb2
-rw-r--r--app/serializers/analytics_build_entity.rb2
-rw-r--r--app/serializers/build_action_entity.rb2
-rw-r--r--app/serializers/build_artifact_entity.rb2
-rw-r--r--app/serializers/build_entity.rb6
-rw-r--r--app/serializers/issue_entity.rb6
-rw-r--r--app/serializers/pipeline_entity.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb3
-rw-r--r--app/services/ci/create_trigger_request_service.rb2
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb2
-rw-r--r--app/views/admin/dashboard/_head.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml6
-rw-r--r--app/views/admin/jobs/index.html.haml (renamed from app/views/admin/builds/index.html.haml)6
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/notify/links/ci/builds/_build.html.haml2
-rw-r--r--app/views/notify/links/ci/builds/_build.text.erb2
-rw-r--r--app/views/projects/artifacts/_tree_directory.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml8
-rw-r--r--app/views/projects/artifacts/file.html.haml8
-rw-r--r--app/views/projects/ci/builds/_build.html.haml10
-rw-r--r--app/views/projects/deployments/_actions.haml1
-rw-r--r--app/views/projects/diffs/_content.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--app/views/projects/jobs/_header.html.haml (renamed from app/views/projects/builds/_header.html.haml)6
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml (renamed from app/views/projects/builds/_sidebar.html.haml)12
-rw-r--r--app/views/projects/jobs/_table.html.haml (renamed from app/views/projects/builds/_table.html.haml)0
-rw-r--r--app/views/projects/jobs/_user.html.haml (renamed from app/views/projects/builds/_user.html.haml)0
-rw-r--r--app/views/projects/jobs/index.html.haml (renamed from app/views/projects/builds/index.html.haml)4
-rw-r--r--app/views/projects/jobs/show.html.haml (renamed from app/views/projects/builds/show.html.haml)6
-rw-r--r--app/views/projects/pipelines/_head.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/pipeline_schedule_worker.rb2
118 files changed, 1567 insertions, 399 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 6e2f06112dd..53b25da18e5 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -118,7 +118,7 @@ import ShortcutsBlob from './shortcuts_blob';
shortcut_handler = new ShortcutsNavigation();
new UsersSelect();
break;
- case 'projects:builds:show':
+ case 'projects:jobs:show':
new Build();
break;
case 'projects:merge_requests:index':
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 266cd3966c6..111449bb8f7 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -194,6 +194,7 @@ window.DropzoneInput = (function() {
$(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
+ formTextarea.get(0).dispatchEvent(new Event('input'));
return formTextarea.trigger('input');
};
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 770a0dcd27e..800bb9f1fe8 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,10 +1,14 @@
<script>
+/* global Flash */
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
+import eventHub from '../event_hub';
import Service from '../services/index';
import Store from '../stores';
import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
+import formComponent from './form.vue';
+import '../../lib/utils/url_utility';
export default {
props: {
@@ -12,15 +16,27 @@ export default {
required: true,
type: String,
},
+ canMove: {
+ required: true,
+ type: Boolean,
+ },
canUpdate: {
required: true,
type: Boolean,
},
+ canDestroy: {
+ required: true,
+ type: Boolean,
+ },
issuableRef: {
type: String,
required: true,
},
- initialTitle: {
+ initialTitleHtml: {
+ type: String,
+ required: true,
+ },
+ initialTitleText: {
type: String,
required: true,
},
@@ -34,10 +50,40 @@ export default {
required: false,
default: '',
},
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ isConfidential: {
+ type: Boolean,
+ required: true,
+ },
+ markdownPreviewUrl: {
+ type: String,
+ required: true,
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ projectsAutocompleteUrl: {
+ type: String,
+ required: true,
+ },
},
data() {
const store = new Store({
- titleHtml: this.initialTitle,
+ titleHtml: this.initialTitleHtml,
+ titleText: this.initialTitleText,
descriptionHtml: this.initialDescriptionHtml,
descriptionText: this.initialDescriptionText,
});
@@ -45,19 +91,97 @@ export default {
return {
store,
state: store.state,
+ showForm: false,
};
},
+ computed: {
+ formState() {
+ return this.store.formState;
+ },
+ },
components: {
descriptionComponent,
titleComponent,
+ formComponent,
+ },
+ methods: {
+ openForm() {
+ if (!this.showForm) {
+ this.showForm = true;
+ this.store.setFormState({
+ title: this.state.titleText,
+ confidential: this.isConfidential,
+ description: this.state.descriptionText,
+ lockedWarningVisible: false,
+ move_to_project_id: 0,
+ updateLoading: false,
+ });
+ }
+ },
+ closeForm() {
+ this.showForm = false;
+ },
+ updateIssuable() {
+ const canPostUpdate = this.store.formState.move_to_project_id !== 0 ?
+ confirm('Are you sure you want to move this issue to another project?') : true; // eslint-disable-line no-alert
+
+ if (!canPostUpdate) {
+ this.store.setFormState({
+ updateLoading: false,
+ });
+ return;
+ }
+
+ this.service.updateIssuable(this.store.formState)
+ .then(res => res.json())
+ .then((data) => {
+ if (location.pathname !== data.web_url) {
+ gl.utils.visitUrl(data.web_url);
+ } else if (data.confidential !== this.isConfidential) {
+ gl.utils.visitUrl(location.pathname);
+ }
+
+ return this.service.getData();
+ })
+ .then(res => res.json())
+ .then((data) => {
+ this.store.updateState(data);
+ eventHub.$emit('close.form');
+ })
+ .catch(() => {
+ eventHub.$emit('close.form');
+ return new Flash('Error updating issue');
+ });
+ },
+ deleteIssuable() {
+ this.service.deleteIssuable()
+ .then(res => res.json())
+ .then((data) => {
+ // Stop the poll so we don't get 404's with the issue not existing
+ this.poll.stop();
+
+ gl.utils.visitUrl(data.web_url);
+ })
+ .catch(() => {
+ eventHub.$emit('close.form');
+ return new Flash('Error deleting issue');
+ });
+ },
},
created() {
- const resource = new Service(this.endpoint);
- const poll = new Poll({
- resource,
+ this.service = new Service(this.endpoint);
+ this.poll = new Poll({
+ resource: this.service,
method: 'getData',
successCallback: (res) => {
- this.store.updateState(res.json());
+ const data = res.json();
+ const shouldUpdate = this.store.stateShouldUpdate(data);
+
+ this.store.updateState(data);
+
+ if (this.showForm && (shouldUpdate.title || shouldUpdate.description)) {
+ this.store.formState.lockedWarningVisible = true;
+ }
},
errorCallback(err) {
throw new Error(err);
@@ -65,32 +189,57 @@ export default {
});
if (!Visibility.hidden()) {
- poll.makeRequest();
+ this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
- poll.restart();
+ this.poll.restart();
} else {
- poll.stop();
+ this.poll.stop();
}
});
+
+ eventHub.$on('delete.issuable', this.deleteIssuable);
+ eventHub.$on('update.issuable', this.updateIssuable);
+ eventHub.$on('close.form', this.closeForm);
+ eventHub.$on('open.form', this.openForm);
+ },
+ beforeDestroy() {
+ eventHub.$off('delete.issuable', this.deleteIssuable);
+ eventHub.$off('update.issuable', this.updateIssuable);
+ eventHub.$off('close.form', this.closeForm);
+ eventHub.$off('open.form', this.openForm);
},
};
</script>
<template>
<div>
- <title-component
- :issuable-ref="issuableRef"
- :title-html="state.titleHtml"
- :title-text="state.titleText" />
- <description-component
- v-if="state.descriptionHtml"
- :can-update="canUpdate"
- :description-html="state.descriptionHtml"
- :description-text="state.descriptionText"
- :updated-at="state.updatedAt"
- :task-status="state.taskStatus" />
+ <form-component
+ v-if="canUpdate && showForm"
+ :form-state="formState"
+ :can-move="canMove"
+ :can-destroy="canDestroy"
+ :issuable-templates="issuableTemplates"
+ :markdown-docs="markdownDocs"
+ :markdown-preview-url="markdownPreviewUrl"
+ :project-path="projectPath"
+ :project-namespace="projectNamespace"
+ :projects-autocomplete-url="projectsAutocompleteUrl"
+ />
+ <div v-else>
+ <title-component
+ :issuable-ref="issuableRef"
+ :title-html="state.titleHtml"
+ :title-text="state.titleText" />
+ <description-component
+ v-if="state.descriptionHtml"
+ :can-update="canUpdate"
+ :description-html="state.descriptionHtml"
+ :description-text="state.descriptionText"
+ :updated-at="state.updatedAt"
+ :task-status="state.taskStatus" />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 4ad3eb7dfd7..3281ec6b172 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -18,11 +18,13 @@
},
updatedAt: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
taskStatus: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
},
data() {
@@ -83,6 +85,7 @@
<template>
<div
+ v-if="descriptionHtml"
class="description"
:class="{
'js-task-list-container': canUpdate
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
new file mode 100644
index 00000000000..8c81575fe6f
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -0,0 +1,79 @@
+<script>
+ import updateMixin from '../mixins/update';
+ import eventHub from '../event_hub';
+
+ export default {
+ mixins: [updateMixin],
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
+ },
+ formState: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ deleteLoading: false,
+ };
+ },
+ computed: {
+ isSubmitEnabled() {
+ return this.formState.title.trim() !== '';
+ },
+ },
+ methods: {
+ closeForm() {
+ eventHub.$emit('close.form');
+ },
+ deleteIssuable() {
+ // eslint-disable-next-line no-alert
+ if (confirm('Issue will be removed! Are you sure?')) {
+ this.deleteLoading = true;
+
+ eventHub.$emit('delete.issuable');
+ }
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="prepend-top-default append-bottom-default clearfix">
+ <button
+ class="btn btn-save pull-left"
+ :class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
+ type="submit"
+ :disabled="formState.updateLoading || !isSubmitEnabled"
+ @click.prevent="updateIssuable">
+ Save changes
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ v-if="formState.updateLoading">
+ </i>
+ </button>
+ <button
+ class="btn btn-default pull-right"
+ type="button"
+ @click="closeForm">
+ Cancel
+ </button>
+ <button
+ v-if="canDestroy"
+ class="btn btn-danger pull-right append-right-default"
+ :class="{ disabled: deleteLoading }"
+ type="button"
+ :disabled="deleteLoading"
+ @click="deleteIssuable">
+ Delete
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ v-if="deleteLoading">
+ </i>
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue b/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue
new file mode 100644
index 00000000000..a0ff08e9111
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue
@@ -0,0 +1,23 @@
+<script>
+ export default {
+ props: {
+ formState: {
+ type: Object,
+ required: true,
+ },
+ },
+ };
+</script>
+
+<template>
+ <fieldset class="checkbox">
+ <label for="issue-confidential">
+ <input
+ type="checkbox"
+ value="1"
+ id="issue-confidential"
+ v-model="formState.confidential" />
+ This issue is confidential and should only be visible to team members with at least Reporter access.
+ </label>
+ </fieldset>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
new file mode 100644
index 00000000000..30a1be5cb50
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -0,0 +1,54 @@
+<script>
+ /* global Flash */
+ import updateMixin from '../../mixins/update';
+ import markdownField from '../../../vue_shared/components/markdown/field.vue';
+
+ export default {
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
+ },
+ markdownPreviewUrl: {
+ type: String,
+ required: true,
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
+ },
+ components: {
+ markdownField,
+ },
+ mounted() {
+ this.$refs.textarea.focus();
+ },
+ };
+</script>
+
+<template>
+ <div class="common-note-form">
+ <label
+ class="sr-only"
+ for="issue-description">
+ Description
+ </label>
+ <markdown-field
+ :markdown-preview-url="markdownPreviewUrl"
+ :markdown-docs="markdownDocs">
+ <textarea
+ id="issue-description"
+ class="note-textarea js-gfm-input js-autosize markdown-area"
+ data-supports-slash-commands="false"
+ aria-label="Description"
+ v-model="formState.description"
+ ref="textarea"
+ slot="textarea"
+ placeholder="Write a comment or drag your files here..."
+ @keydown.meta.enter="updateIssuable">
+ </textarea>
+ </markdown-field>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
new file mode 100644
index 00000000000..1c40b286513
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -0,0 +1,111 @@
+<script>
+ export default {
+ props: {
+ formState: {
+ type: Object,
+ required: true,
+ },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ issuableTemplatesJson() {
+ return JSON.stringify(this.issuableTemplates);
+ },
+ },
+ mounted() {
+ // Create the editor for the template
+ const editor = document.querySelector('.detail-page-description .note-textarea') || {};
+ editor.setValue = (val) => {
+ this.formState.description = val;
+ };
+ editor.getValue = () => this.formState.description;
+
+ this.issuableTemplate = new gl.IssuableTemplateSelectors({
+ $dropdowns: $(this.$refs.toggle),
+ editor,
+ });
+ },
+ };
+</script>
+
+<template>
+ <div
+ class="dropdown js-issuable-selector-wrap"
+ data-issuable-type="issue">
+ <button
+ class="dropdown-menu-toggle js-issuable-selector"
+ type="button"
+ ref="toggle"
+ data-field-name="issuable_template"
+ data-selected="null"
+ data-toggle="dropdown"
+ :data-namespace-path="projectNamespace"
+ :data-project-path="projectPath"
+ :data-data="issuableTemplatesJson">
+ <span class="dropdown-toggle-text">
+ Choose a template
+ </span>
+ <i
+ aria-hidden="true"
+ class="fa fa-chevron-down">
+ </i>
+ </button>
+ <div class="dropdown-menu dropdown-select">
+ <div class="dropdown-title">
+ Choose a template
+ <button
+ class="dropdown-title-button dropdown-menu-close"
+ aria-label="Close"
+ type="button">
+ <i
+ aria-hidden="true"
+ class="fa fa-times dropdown-menu-close-icon">
+ </i>
+ </button>
+ </div>
+ <div class="dropdown-input">
+ <input
+ type="search"
+ class="dropdown-input-field"
+ placeholder="Filter"
+ autocomplete="off" />
+ <i
+ aria-hidden="true"
+ class="fa fa-search dropdown-input-search">
+ </i>
+ <i
+ role="button"
+ aria-label="Clear templates search input"
+ class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
+ </i>
+ </div>
+ <div class="dropdown-content"></div>
+ <div class="dropdown-footer">
+ <ul class="dropdown-footer-list">
+ <li>
+ <a class="no-template">
+ No template
+ </a>
+ </li>
+ <li>
+ <a class="reset-template">
+ Reset template
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/project_move.vue b/app/assets/javascripts/issue_show/components/fields/project_move.vue
new file mode 100644
index 00000000000..f811fb0de24
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/project_move.vue
@@ -0,0 +1,83 @@
+<script>
+ import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+
+ export default {
+ mixins: [
+ tooltipMixin,
+ ],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
+ },
+ projectsAutocompleteUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ mounted() {
+ const $moveDropdown = $(this.$refs['move-dropdown']);
+
+ $moveDropdown.select2({
+ ajax: {
+ url: this.projectsAutocompleteUrl,
+ quietMillis: 125,
+ data(term, page, context) {
+ return {
+ search: term,
+ offset_id: context,
+ };
+ },
+ results(data) {
+ const more = data.length >= 50;
+ const context = data[data.length - 1] ? data[data.length - 1].id : null;
+
+ return {
+ results: data,
+ more,
+ context,
+ };
+ },
+ },
+ formatResult(project) {
+ return project.name_with_namespace;
+ },
+ formatSelection(project) {
+ return project.name_with_namespace;
+ },
+ })
+ .on('change', (e) => {
+ this.formState.move_to_project_id = parseInt(e.target.value, 10);
+ });
+ },
+ beforeDestroy() {
+ $(this.$refs['move-dropdown']).select2('destroy');
+ },
+ };
+</script>
+
+<template>
+ <fieldset>
+ <label
+ for="issuable-move"
+ class="sr-only">
+ Move
+ </label>
+ <div class="issuable-form-select-holder append-right-5">
+ <input
+ ref="move-dropdown"
+ type="hidden"
+ id="issuable-move"
+ data-placeholder="Move to a different project" />
+ </div>
+ <span
+ data-placement="auto top"
+ title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location."
+ ref="tooltip">
+ <i
+ class="fa fa-question-circle"
+ aria-hidden="true">
+ </i>
+ </span>
+ </fieldset>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
new file mode 100644
index 00000000000..6556bf117e2
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -0,0 +1,31 @@
+<script>
+ import updateMixin from '../../mixins/update';
+
+ export default {
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
+ },
+ },
+ };
+</script>
+
+<template>
+ <fieldset>
+ <label
+ class="sr-only"
+ for="issue-title">
+ Title
+ </label>
+ <input
+ id="issue-title"
+ class="form-control"
+ type="text"
+ placeholder="Issue title"
+ aria-label="Issue title"
+ v-model="formState.title"
+ @keydown.meta.enter="updateIssuable" />
+ </fieldset>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
new file mode 100644
index 00000000000..76ec3dc9a5d
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -0,0 +1,104 @@
+<script>
+ import lockedWarning from './locked_warning.vue';
+ import titleField from './fields/title.vue';
+ import descriptionField from './fields/description.vue';
+ import editActions from './edit_actions.vue';
+ import descriptionTemplate from './fields/description_template.vue';
+ import projectMove from './fields/project_move.vue';
+ import confidentialCheckbox from './fields/confidential_checkbox.vue';
+
+ export default {
+ props: {
+ canMove: {
+ type: Boolean,
+ required: true,
+ },
+ canDestroy: {
+ type: Boolean,
+ required: true,
+ },
+ formState: {
+ type: Object,
+ required: true,
+ },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ markdownPreviewUrl: {
+ type: String,
+ required: true,
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ projectsAutocompleteUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ components: {
+ lockedWarning,
+ titleField,
+ descriptionField,
+ descriptionTemplate,
+ editActions,
+ projectMove,
+ confidentialCheckbox,
+ },
+ computed: {
+ hasIssuableTemplates() {
+ return this.issuableTemplates.length;
+ },
+ },
+ };
+</script>
+
+<template>
+ <form>
+ <locked-warning v-if="formState.lockedWarningVisible" />
+ <div class="row">
+ <div
+ class="col-sm-4 col-lg-3"
+ v-if="hasIssuableTemplates">
+ <description-template
+ :form-state="formState"
+ :issuable-templates="issuableTemplates"
+ :project-path="projectPath"
+ :project-namespace="projectNamespace" />
+ </div>
+ <div
+ :class="{
+ 'col-sm-8 col-lg-9': hasIssuableTemplates,
+ 'col-xs-12': !hasIssuableTemplates,
+ }">
+ <title-field
+ :form-state="formState"
+ :issuable-templates="issuableTemplates" />
+ </div>
+ </div>
+ <description-field
+ :form-state="formState"
+ :markdown-preview-url="markdownPreviewUrl"
+ :markdown-docs="markdownDocs" />
+ <confidential-checkbox
+ :form-state="formState" />
+ <project-move
+ v-if="canMove"
+ :form-state="formState"
+ :projects-autocomplete-url="projectsAutocompleteUrl" />
+ <edit-actions
+ :form-state="formState"
+ :can-destroy="canDestroy" />
+ </form>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue
new file mode 100644
index 00000000000..1c2789f154a
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/locked_warning.vue
@@ -0,0 +1,20 @@
+<script>
+ export default {
+ computed: {
+ currentPath() {
+ return location.pathname;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="alert alert-danger">
+ Someone edited the issue at the same time you did. Please check out
+ <a
+ :href="currentPath"
+ target="_blank"
+ rel="nofollow">the issue</a>
+ and make sure your changes will not unintentionally remove theirs.
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/event_hub.js b/app/assets/javascripts/issue_show/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/issue_show/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index f06e33dee60..faf79471946 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,42 +1,49 @@
import Vue from 'vue';
+import eventHub from './event_hub';
import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor';
-document.addEventListener('DOMContentLoaded', () => new Vue({
- el: document.getElementById('js-issuable-app'),
- components: {
- issuableApp,
- },
- data() {
- const issuableElement = this.$options.el;
- const issuableTitleElement = issuableElement.querySelector('.title');
- const issuableDescriptionElement = issuableElement.querySelector('.wiki');
- const issuableDescriptionTextarea = issuableElement.querySelector('.js-task-list-field');
- const {
- canUpdate,
- endpoint,
- issuableRef,
- } = issuableElement.dataset;
+document.addEventListener('DOMContentLoaded', () => {
+ const initialDataEl = document.getElementById('js-issuable-app-initial-data');
+ const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
- return {
- canUpdate: gl.utils.convertPermissionToBoolean(canUpdate),
- endpoint,
- issuableRef,
- initialTitle: issuableTitleElement.innerHTML,
- initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
- initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
- };
- },
- render(createElement) {
- return createElement('issuable-app', {
- props: {
- canUpdate: this.canUpdate,
- endpoint: this.endpoint,
- issuableRef: this.issuableRef,
- initialTitle: this.initialTitle,
- initialDescriptionHtml: this.initialDescriptionHtml,
- initialDescriptionText: this.initialDescriptionText,
- },
- });
- },
-}));
+ $('.issuable-edit').on('click', (e) => {
+ e.preventDefault();
+
+ eventHub.$emit('open.form');
+ });
+
+ return new Vue({
+ el: document.getElementById('js-issuable-app'),
+ components: {
+ issuableApp,
+ },
+ data() {
+ return {
+ ...initialData,
+ };
+ },
+ render(createElement) {
+ return createElement('issuable-app', {
+ props: {
+ canUpdate: this.canUpdate,
+ canDestroy: this.canDestroy,
+ canMove: this.canMove,
+ endpoint: this.endpoint,
+ issuableRef: this.issuableRef,
+ initialTitleHtml: this.initialTitleHtml,
+ initialTitleText: this.initialTitleText,
+ initialDescriptionHtml: this.initialDescriptionHtml,
+ initialDescriptionText: this.initialDescriptionText,
+ issuableTemplates: this.issuableTemplates,
+ isConfidential: this.isConfidential,
+ markdownPreviewUrl: this.markdownPreviewUrl,
+ markdownDocs: this.markdownDocs,
+ projectPath: this.projectPath,
+ projectNamespace: this.projectNamespace,
+ projectsAutocompleteUrl: this.projectsAutocompleteUrl,
+ },
+ });
+ },
+ });
+});
diff --git a/app/assets/javascripts/issue_show/mixins/animate.js b/app/assets/javascripts/issue_show/mixins/animate.js
index eda6302aa8b..4816393da1f 100644
--- a/app/assets/javascripts/issue_show/mixins/animate.js
+++ b/app/assets/javascripts/issue_show/mixins/animate.js
@@ -4,7 +4,7 @@ export default {
this.preAnimation = true;
this.pulseAnimation = false;
- this.$nextTick(() => {
+ setTimeout(() => {
this.preAnimation = false;
this.pulseAnimation = true;
});
diff --git a/app/assets/javascripts/issue_show/mixins/update.js b/app/assets/javascripts/issue_show/mixins/update.js
new file mode 100644
index 00000000000..72be65b426f
--- /dev/null
+++ b/app/assets/javascripts/issue_show/mixins/update.js
@@ -0,0 +1,10 @@
+import eventHub from '../event_hub';
+
+export default {
+ methods: {
+ updateIssuable() {
+ this.formState.updateLoading = true;
+ eventHub.$emit('update.issuable');
+ },
+ },
+};
diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js
index 348ad8d6813..6f0fd0b1768 100644
--- a/app/assets/javascripts/issue_show/services/index.js
+++ b/app/assets/javascripts/issue_show/services/index.js
@@ -7,10 +7,23 @@ export default class Service {
constructor(endpoint) {
this.endpoint = endpoint;
- this.resource = Vue.resource(this.endpoint);
+ this.resource = Vue.resource(`${this.endpoint}.json`, {}, {
+ realtimeChanges: {
+ method: 'GET',
+ url: `${this.endpoint}/realtime_changes`,
+ },
+ });
}
getData() {
- return this.resource.get();
+ return this.resource.realtimeChanges();
+ }
+
+ deleteIssuable() {
+ return this.resource.delete();
+ }
+
+ updateIssuable(data) {
+ return this.resource.update(data);
}
}
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 8e89a2b7730..4a16c3cb4dc 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -1,17 +1,26 @@
export default class Store {
constructor({
titleHtml,
+ titleText,
descriptionHtml,
descriptionText,
}) {
this.state = {
titleHtml,
- titleText: '',
+ titleText,
descriptionHtml,
descriptionText,
taskStatus: '',
updatedAt: '',
};
+ this.formState = {
+ title: '',
+ confidential: false,
+ description: '',
+ lockedWarningVisible: false,
+ move_to_project_id: 0,
+ updateLoading: false,
+ };
}
updateState(data) {
@@ -22,4 +31,15 @@ export default class Store {
this.state.taskStatus = data.task_status;
this.state.updatedAt = data.updated_at;
}
+
+ stateShouldUpdate(data) {
+ return {
+ title: this.state.titleText !== data.title_text,
+ description: this.state.descriptionText !== data.description_text,
+ };
+ }
+
+ setFormState(state) {
+ this.formState = Object.assign(this.formState, state);
+ }
}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index b43c1c3aac6..601d01e1be1 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -170,7 +170,7 @@ gl.text.init = function(form) {
});
};
gl.text.removeListeners = function(form) {
- return $('.js-md', form).off();
+ return $('.js-md', form).off('click');
};
gl.text.humanize = function(string) {
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 3e8240d10ec..814d2ea92b4 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -30,7 +30,7 @@
|
\\s\\$(?!\\$)
)
- (.+?)
+ ((.|\\n)+?)
(
\\s\\\\end{[a-zA-Z]+}$
|
@@ -45,15 +45,25 @@
let inline = false;
if (typeof katex !== 'undefined') {
- const katexString = text.replace(/\\/g, '\\');
- const matches = new RegExp(katexRegexString, 'gi').exec(katexString);
+ const katexString = text.replace(/&amp;/g, '&')
+ .replace(/&=&/g, '\\space=\\space')
+ .replace(/<(\/?)em>/g, '_');
+ const regex = new RegExp(katexRegexString, 'gi');
+ const matchLocation = katexString.search(regex);
+ const numberOfMatches = katexString.match(regex);
- if (matches && matches.length > 0) {
- if (matches[1].trim() === '$' && matches[3].trim() === '$') {
+ if (numberOfMatches && numberOfMatches.length !== 0) {
+ if (matchLocation > 0) {
+ let matches = regex.exec(katexString);
inline = true;
- text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`;
+ while (matches !== null) {
+ const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
+ text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
+ matches = regex.exec(katexString);
+ }
} else {
+ const matches = regex.exec(katexString);
text = katex.renderToString(matches[2]);
}
}
@@ -79,7 +89,7 @@
},
computed: {
markdown() {
- return marked(this.cell.source.join(''));
+ return marked(this.cell.source.join('').replace(/\\/g, '\\\\'));
},
},
};
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index dace03554e8..51448252c0f 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -77,7 +77,9 @@ import './shortcuts_navigation';
ShortcutsIssuable.prototype.editIssue = function() {
var $editBtn;
$editBtn = $('.issuable-edit');
- return gl.utils.visitUrl($editBtn.attr('href'));
+ // Need to click the element as on issues, editing is inline
+ // on merge request, editing is on a different page
+ $editBtn.get(0).click();
};
ShortcutsIssuable.prototype.openSidebarDropdown = function(name) {
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 3392cb9da29..419c458ff34 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -1,6 +1,6 @@
/* global Flash */
-import 'vendor/task_list';
+import 'deckar01-task_list';
class TaskList {
constructor(options = {}) {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
new file mode 100644
index 00000000000..e6977681e96
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -0,0 +1,107 @@
+<script>
+ /* global Flash */
+ import markdownHeader from './header.vue';
+ import markdownToolbar from './toolbar.vue';
+
+ export default {
+ props: {
+ markdownPreviewUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ markdownPreview: '',
+ markdownPreviewLoading: false,
+ previewMarkdown: false,
+ };
+ },
+ components: {
+ markdownHeader,
+ markdownToolbar,
+ },
+ methods: {
+ toggleMarkdownPreview() {
+ this.previewMarkdown = !this.previewMarkdown;
+
+ if (!this.previewMarkdown) {
+ this.markdownPreview = '';
+ } else {
+ this.markdownPreviewLoading = true;
+ this.$http.post(
+ this.markdownPreviewUrl,
+ {
+ /*
+ Can't use `$refs` as the component is technically in the parent component
+ so we access the VNode & then get the element
+ */
+ text: this.$slots.textarea[0].elm.value,
+ },
+ )
+ .then((res) => {
+ const data = res.json();
+
+ this.markdownPreviewLoading = false;
+ this.markdownPreview = data.body;
+
+ this.$nextTick(() => {
+ $(this.$refs['markdown-preview']).renderGFM();
+ });
+ })
+ .catch(() => new Flash('Error loading markdown preview'));
+ }
+ },
+ },
+ mounted() {
+ /*
+ GLForm class handles all the toolbar buttons
+ */
+ return new gl.GLForm($(this.$refs['gl-form']), true);
+ },
+ };
+</script>
+
+<template>
+ <div
+ class="md-area prepend-top-default append-bottom-default js-vue-markdown-field"
+ ref="gl-form">
+ <markdown-header
+ :preview-markdown="previewMarkdown"
+ @toggle-markdown="toggleMarkdownPreview" />
+ <div
+ class="md-write-holder"
+ v-show="!previewMarkdown">
+ <div class="zen-backdrop">
+ <slot name="textarea"></slot>
+ <a
+ class="zen-control zen-control-leave js-zen-leave"
+ href="#"
+ aria-label="Enter zen mode">
+ <i
+ class="fa fa-compress"
+ aria-hidden="true">
+ </i>
+ </a>
+ <markdown-toolbar
+ :markdown-docs="markdownDocs" />
+ </div>
+ </div>
+ <div
+ class="md md-preview-holder md-preview"
+ v-show="previewMarkdown">
+ <div
+ ref="markdown-preview"
+ v-html="markdownPreview">
+ </div>
+ <span v-if="markdownPreviewLoading">
+ Loading...
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
new file mode 100644
index 00000000000..1a11f493b7f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -0,0 +1,113 @@
+<script>
+ import tooltipMixin from '../../mixins/tooltip';
+ import toolbarButton from './toolbar_button.vue';
+
+ export default {
+ mixins: [
+ tooltipMixin,
+ ],
+ props: {
+ previewMarkdown: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ components: {
+ toolbarButton,
+ },
+ methods: {
+ toggleMarkdownPreview(e, form) {
+ if (form && !form.find('.js-vue-markdown-field').length) {
+ return;
+ } else if (e.target.blur) {
+ e.target.blur();
+ }
+
+ this.$emit('toggle-markdown');
+ },
+ },
+ mounted() {
+ $(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
+ $(document).on('markdown-preview:hide.vue', this.toggleMarkdownPreview);
+ },
+ beforeDestroy() {
+ $(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
+ $(document).off('markdown-preview:hide.vue', this.toggleMarkdownPreview);
+ },
+ };
+</script>
+
+<template>
+ <div class="md-header">
+ <ul class="nav-links clearfix">
+ <li :class="{ active: !previewMarkdown }">
+ <a
+ href="#md-write-holder"
+ tabindex="-1"
+ @click.prevent="toggleMarkdownPreview($event)">
+ Write
+ </a>
+ </li>
+ <li :class="{ active: previewMarkdown }">
+ <a
+ href="#md-preview-holder"
+ tabindex="-1"
+ @click.prevent="toggleMarkdownPreview($event)">
+ Preview
+ </a>
+ </li>
+ <li class="pull-right">
+ <div class="toolbar-group">
+ <toolbar-button
+ tag="**"
+ button-title="Add bold text"
+ icon="bold" />
+ <toolbar-button
+ tag="*"
+ button-title="Add italic text"
+ icon="italic" />
+ <toolbar-button
+ tag="> "
+ :prepend="true"
+ button-title="Insert a quote"
+ icon="quote-right" />
+ <toolbar-button
+ tag="`"
+ tag-block="```"
+ button-title="Insert code"
+ icon="code" />
+ <toolbar-button
+ tag="* "
+ :prepend="true"
+ button-title="Add a bullet list"
+ icon="list-ul" />
+ <toolbar-button
+ tag="1. "
+ :prepend="true"
+ button-title="Add a numbered list"
+ icon="list-ol" />
+ <toolbar-button
+ tag="* [ ] "
+ :prepend="true"
+ button-title="Add a task list"
+ icon="check-square-o" />
+ </div>
+ <div class="toolbar-group">
+ <button
+ aria-label="Go full screen"
+ class="toolbar-btn js-zen-enter"
+ data-container="body"
+ tabindex="-1"
+ title="Go full screen"
+ type="button"
+ ref="tooltip">
+ <i
+ aria-hidden="true"
+ class="fa fa-arrows-alt fa-fw">
+ </i>
+ </button>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
new file mode 100644
index 00000000000..93252293ba6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -0,0 +1,33 @@
+<script>
+ export default {
+ props: {
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="comment-toolbar clearfix">
+ <div class="toolbar-text">
+ <a
+ :href="markdownDocs"
+ target="_blank"
+ tabindex="-1">
+ Markdown is supported
+ </a>
+ </div>
+ <button
+ class="toolbar-button markdown-selector"
+ type="button"
+ tabindex="-1">
+ <i
+ class="fa fa-file-image-o toolbar-button-icon"
+ aria-hidden="true">
+ </i>
+ Attach a file
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
new file mode 100644
index 00000000000..096be507625
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -0,0 +1,58 @@
+<script>
+ import tooltipMixin from '../../mixins/tooltip';
+
+ export default {
+ mixins: [
+ tooltipMixin,
+ ],
+ props: {
+ buttonTitle: {
+ type: String,
+ required: true,
+ },
+ icon: {
+ type: String,
+ required: true,
+ },
+ tag: {
+ type: String,
+ required: true,
+ },
+ tagBlock: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ prepend: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ iconClass() {
+ return `fa-${this.icon}`;
+ },
+ },
+ };
+</script>
+
+<template>
+ <button
+ type="button"
+ class="toolbar-btn js-md hidden-xs"
+ tabindex="-1"
+ ref="tooltip"
+ data-container="body"
+ :data-md-tag="tag"
+ :data-md-block="tagBlock"
+ :data-md-prepend="prepend"
+ :title="buttonTitle"
+ :aria-label="buttonTitle">
+ <i
+ aria-hidden="true"
+ class="fa fa-fw"
+ :class="iconClass">
+ </i>
+ </button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/mixins/tooltip.js b/app/assets/javascripts/vue_shared/mixins/tooltip.js
index 9bb948bff66..995c0c98505 100644
--- a/app/assets/javascripts/vue_shared/mixins/tooltip.js
+++ b/app/assets/javascripts/vue_shared/mixins/tooltip.js
@@ -6,4 +6,8 @@ export default {
updated() {
$(this.$refs.tooltip).tooltip('fixTitle');
},
+
+ beforeDestroy() {
+ $(this.$refs.tooltip).tooltip('destroy');
+ },
};
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index d191bbb226c..90051ffe753 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -475,4 +475,5 @@
.filter-dropdown-loading {
padding: 8px 16px;
+ text-align: center;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4db77752c0c..975a4b40383 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -293,7 +293,7 @@ $btn-white-active: #848484;
/*
* Badges
*/
-$badge-bg: #eee;
+$badge-bg: rgba(0, 0, 0, 0.07);
$badge-color: $gl-text-color-secondary;
/*
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index cffd3b6060d..f956e3757bf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -550,13 +550,13 @@ ul.notes {
position: relative;
top: -2px;
display: inline-block;
- padding-left: 4px;
- padding-right: 4px;
+ padding-left: 7px;
+ padding-right: 7px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
border: 1px solid $border-color;
- border-radius: $border-radius-base;
+ border-radius: $label-border-radius;
}
diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/jobs_controller.rb
index 88f3c0e2fd4..5162273ef8a 100644
--- a/app/controllers/admin/builds_controller.rb
+++ b/app/controllers/admin/jobs_controller.rb
@@ -1,4 +1,4 @@
-class Admin::BuildsController < Admin::ApplicationController
+class Admin::JobsController < Admin::ApplicationController
def index
@scope = params[:scope]
@all_builds = Ci::Build
@@ -20,6 +20,6 @@ class Admin::BuildsController < Admin::ApplicationController
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
- redirect_to admin_builds_path
+ redirect_to admin_jobs_path
end
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 4cf645d6341..0c3b68a7ac3 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -14,7 +14,16 @@ module IssuableActions
name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted."
- redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
+ index_path = polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
+
+ respond_to do |format|
+ format.html { redirect_to index_path }
+ format.json do
+ render json: {
+ web_url: index_path
+ }
+ end
+ end
end
def bulk_update
diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb
index 1d37e4cb3bd..54dcd7c61ce 100644
--- a/app/controllers/concerns/renders_blob.rb
+++ b/app/controllers/concerns/renders_blob.rb
@@ -18,7 +18,7 @@ module RendersBlob
}
end
- def override_max_blob_size(blob)
- blob.override_max_size! if params[:override_max_size] == 'true'
+ def conditionally_expand_blob(blob)
+ blob.expand! if params[:expanded] == 'true'
end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 6195121b931..f9c31920302 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events
projects =
if params[:filter] == "starred"
- current_user.viewable_starred_projects
+ ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
else
current_user.authorized_projects
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 1224e9503c9..ea036b1f705 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -27,7 +27,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def file
blob = @entry.blob
- override_max_blob_size(blob)
+ conditionally_expand_blob(blob)
respond_to do |format|
format.html do
@@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def keep
build.keep_artifacts!
- redirect_to namespace_project_build_path(project.namespace, project, build)
+ redirect_to namespace_project_job_path(project.namespace, project, build)
end
def latest_succeeded
@@ -79,7 +79,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
def build_from_id
- project.builds.find_by(id: params[:build_id]) if params[:build_id]
+ project.builds.find_by(id: params[:job_id]) if params[:job_id]
end
def build_from_ref
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 87721fbe2f5..7025c7a1de6 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def show
- override_max_blob_size(@blob)
+ conditionally_expand_blob(@blob)
respond_to do |format|
format.html do
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
new file mode 100644
index 00000000000..f34a198634e
--- /dev/null
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -0,0 +1,55 @@
+class Projects::BuildArtifactsController < Projects::ApplicationController
+ include ExtractsPath
+ include RendersBlob
+
+ before_action :authorize_read_build!
+ before_action :extract_ref_name_and_path
+ before_action :validate_artifacts!
+
+ def download
+ redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ end
+
+ def browse
+ redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ end
+
+ def file
+ redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ end
+
+ def raw
+ redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ end
+
+ def latest_succeeded
+ redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
+ end
+
+ private
+
+ def validate_artifacts!
+ render_404 unless job && job.artifacts?
+ end
+
+ def extract_ref_name_and_path
+ return unless params[:ref_name_and_path]
+
+ @ref_name, @path = extract_ref(params[:ref_name_and_path])
+ end
+
+ def job
+ @job ||= job_from_id || job_from_ref
+ end
+
+ def job_from_id
+ project.builds.find_by(id: params[:build_id]) if params[:build_id]
+ end
+
+ def job_from_ref
+ return unless @ref_name
+
+ jobs = project.latest_successful_builds_for(@ref_name)
+ jobs.find_by(name: params[:job])
+ end
+end
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index dfaaea71b9c..1334a231788 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,131 +1,21 @@
class Projects::BuildsController < Projects::ApplicationController
- before_action :build, except: [:index, :cancel_all]
-
- before_action :authorize_read_build!,
- only: [:index, :show, :status, :raw, :trace]
- before_action :authorize_update_build!,
- except: [:index, :show, :status, :raw, :trace, :cancel_all]
-
- layout 'project'
+ before_action :authorize_read_build!
def index
- @scope = params[:scope]
- @all_builds = project.builds.relevant
- @builds = @all_builds.order('created_at DESC')
- @builds =
- case @scope
- when 'pending'
- @builds.pending.reverse_order
- when 'running'
- @builds.running.reverse_order
- when 'finished'
- @builds.finished
- else
- @builds
- end
- @builds = @builds.includes([
- { pipeline: :project },
- :project,
- :tags
- ])
- @builds = @builds.page(params[:page]).per(30)
- end
-
- def cancel_all
- return access_denied! unless can?(current_user, :update_build, project)
-
- @project.builds.running_or_pending.each do |build|
- build.cancel if can?(current_user, :update_build, build)
- end
-
- redirect_to namespace_project_builds_path(project.namespace, project)
+ redirect_to namespace_project_jobs_path(project.namespace, project)
end
def show
- @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
- @builds = @builds.where("id not in (?)", @build.id)
- @pipeline = @build.pipeline
- end
-
- def trace
- build.trace.read do |stream|
- respond_to do |format|
- format.json do
- result = {
- id: @build.id, status: @build.status, complete: @build.complete?
- }
-
- if stream.valid?
- stream.limit
- state = params[:state].presence
- trace = stream.html_with_state(state)
- result.merge!(trace.to_h)
- end
-
- render json: result
- end
- end
- end
- end
-
- def retry
- return respond_422 unless @build.retryable?
-
- build = Ci::Build.retry(@build, current_user)
- redirect_to build_path(build)
- end
-
- def play
- return respond_422 unless @build.playable?
-
- build = @build.play(current_user)
- redirect_to build_path(build)
- end
-
- def cancel
- return respond_422 unless @build.cancelable?
-
- @build.cancel
- redirect_to build_path(@build)
- end
-
- def status
- render json: BuildSerializer
- .new(project: @project, current_user: @current_user)
- .represent_status(@build)
- end
-
- def erase
- if @build.erase(erased_by: current_user)
- redirect_to namespace_project_build_path(project.namespace, project, @build),
- notice: "Build has been successfully erased!"
- else
- respond_422
- end
+ redirect_to namespace_project_job_path(project.namespace, project, job)
end
def raw
- build.trace.read do |stream|
- if stream.file?
- send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
- else
- render_404
- end
- end
+ redirect_to raw_namespace_project_job_path(project.namespace, project, job)
end
private
- def authorize_update_build!
- return access_denied! unless can?(current_user, :update_build, build)
- end
-
- def build
- @build ||= project.builds.find(params[:id])
- .present(current_user: current_user)
- end
-
- def build_path(build)
- namespace_project_build_path(build.project.namespace, build.project, build)
+ def job
+ @job ||= project.builds.find(params[:id])
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index cbef8fa94d4..59df1e7b86a 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -148,10 +148,7 @@ class Projects::IssuesController < Projects::ApplicationController
format.json do
if @issue.valid?
- render json: @issue.to_json(methods: [:task_status, :task_status_short],
- include: { milestone: {},
- assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
- labels: { methods: :text_color } })
+ render json: IssueSerializer.new.represent(@issue)
else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
new file mode 100644
index 00000000000..d2cd1cfdab8
--- /dev/null
+++ b/app/controllers/projects/jobs_controller.rb
@@ -0,0 +1,131 @@
+class Projects::JobsController < Projects::ApplicationController
+ before_action :build, except: [:index, :cancel_all]
+
+ before_action :authorize_read_build!,
+ only: [:index, :show, :status, :raw, :trace]
+ before_action :authorize_update_build!,
+ except: [:index, :show, :status, :raw, :trace, :cancel_all]
+
+ layout 'project'
+
+ def index
+ @scope = params[:scope]
+ @all_builds = project.builds.relevant
+ @builds = @all_builds.order('created_at DESC')
+ @builds =
+ case @scope
+ when 'pending'
+ @builds.pending.reverse_order
+ when 'running'
+ @builds.running.reverse_order
+ when 'finished'
+ @builds.finished
+ else
+ @builds
+ end
+ @builds = @builds.includes([
+ { pipeline: :project },
+ :project,
+ :tags
+ ])
+ @builds = @builds.page(params[:page]).per(30)
+ end
+
+ def cancel_all
+ return access_denied! unless can?(current_user, :update_build, project)
+
+ @project.builds.running_or_pending.each do |build|
+ build.cancel if can?(current_user, :update_build, build)
+ end
+
+ redirect_to namespace_project_jobs_path(project.namespace, project)
+ end
+
+ def show
+ @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
+ @builds = @builds.where("id not in (?)", @build.id)
+ @pipeline = @build.pipeline
+ end
+
+ def trace
+ build.trace.read do |stream|
+ respond_to do |format|
+ format.json do
+ result = {
+ id: @build.id, status: @build.status, complete: @build.complete?
+ }
+
+ if stream.valid?
+ stream.limit
+ state = params[:state].presence
+ trace = stream.html_with_state(state)
+ result.merge!(trace.to_h)
+ end
+
+ render json: result
+ end
+ end
+ end
+ end
+
+ def retry
+ return respond_422 unless @build.retryable?
+
+ build = Ci::Build.retry(@build, current_user)
+ redirect_to build_path(build)
+ end
+
+ def play
+ return respond_422 unless @build.playable?
+
+ build = @build.play(current_user)
+ redirect_to build_path(build)
+ end
+
+ def cancel
+ return respond_422 unless @build.cancelable?
+
+ @build.cancel
+ redirect_to build_path(@build)
+ end
+
+ def status
+ render json: BuildSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent_status(@build)
+ end
+
+ def erase
+ if @build.erase(erased_by: current_user)
+ redirect_to namespace_project_job_path(project.namespace, project, @build),
+ notice: "Build has been successfully erased!"
+ else
+ respond_422
+ end
+ end
+
+ def raw
+ build.trace.read do |stream|
+ if stream.file?
+ send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
+ else
+ render_404
+ end
+ end
+ end
+
+ private
+
+ def authorize_update_build!
+ return access_denied! unless can?(current_user, :update_build, build)
+ end
+
+ def build
+ @build ||= project.builds.find(params[:id])
+ .present(current_user: current_user)
+ end
+
+ def build_path(build)
+ namespace_project_job_path(build.project.namespace, build.project, build)
+ end
+end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 602d3dd8c1c..87ec0df257a 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -58,7 +58,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def create
@pipeline = Ci::CreatePipelineService
.new(project, current_user, create_params)
- .execute(ignore_skip_ci: true, save_on_errors: false)
+ .execute(:web, ignore_skip_ci: true, save_on_errors: false)
if @pipeline.persisted?
redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 3b2b0d9e502..3a97c1e98af 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -56,7 +56,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def show
blob = @snippet.blob
- override_max_blob_size(blob)
+ conditionally_expand_blob(blob)
respond_to do |format|
format.html do
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 7445f61195d..5b2d143ee79 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -58,7 +58,7 @@ class SnippetsController < ApplicationController
def show
blob = @snippet.blob
- override_max_blob_size(blob)
+ conditionally_expand_blob(blob)
@note = Note.new(noteable: @snippet)
@noteable = @snippet
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index f6d8226bf3f..5bf722d1ec6 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -7,6 +7,7 @@
# project_ids_relation: int[] - project ids to use
# params:
# trending: boolean
+# owned: boolean
# non_public: boolean
# starred: boolean
# sort: string
@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder
def execute
items = init_collection
- items = by_ids(items)
+ items = items.map do |item|
+ item = by_ids(item)
+ item = by_personal(item)
+ item = by_starred(item)
+ item = by_trending(item)
+ item = by_visibilty_level(item)
+ item = by_tags(item)
+ item = by_search(item)
+ by_archived(item)
+ end
items = union(items)
- items = by_personal(items)
- items = by_visibilty_level(items)
- items = by_tags(items)
- items = by_search(items)
- items = by_archived(items)
sort(items)
end
@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder
def init_collection
projects = []
- if params[:trending].present?
- projects << Project.trending
- elsif params[:starred].present? && current_user
- projects << current_user.viewable_starred_projects
+ if params[:owned].present?
+ projects << current_user.owned_projects if current_user
else
projects << current_user.authorized_projects if current_user
projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder
end
def by_ids(items)
- project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items
+ project_ids_relation ? items.where(id: project_ids_relation) : items
end
def union(items)
@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder
(params[:personal].present? && current_user) ? items.personal(current_user) : items
end
+ def by_starred(items)
+ (params[:starred].present? && current_user) ? items.starred_by(current_user) : items
+ end
+
+ def by_trending(items)
+ params[:trending].present? ? items.trending : items
+ end
+
def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e5e64650708..36d9090b3ae 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -276,7 +276,7 @@ module ApplicationHelper
end
def show_user_callout?
- cookies[:user_callout_dismissed] == 'true'
+ cookies[:user_callout_dismissed].nil?
end
def linkedin_url(user)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 622e14e21ff..3efa7c36057 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -120,7 +120,7 @@ module BlobHelper
def blob_raw_url
if @build && @entry
- raw_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: @entry.path)
+ raw_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: @entry.path)
elsif @snippet
if @snippet.project_id
raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
@@ -240,14 +240,10 @@ module BlobHelper
def blob_render_error_reason(viewer)
case viewer.render_error
+ when :collapsed
+ "it is larger than #{number_to_human_size(viewer.collapse_limit)}"
when :too_large
- max_size =
- if viewer.can_override_max_size?
- viewer.overridable_max_size
- else
- viewer.max_size
- end
- "it is larger than #{number_to_human_size(max_size)}"
+ "it is larger than #{number_to_human_size(viewer.size_limit)}"
when :server_side_but_stored_externally
case viewer.blob.external_storage
when :lfs
@@ -264,8 +260,8 @@ module BlobHelper
error = viewer.render_error
options = []
- if error == :too_large && viewer.can_override_max_size?
- options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, override_max_size: true, format: nil)))
+ if error == :collapsed
+ options << link_to('load it anyway', url_for(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/builds_helper.rb b/app/helpers/builds_helper.rb
index 2eb2c6c7389..f0a0d245dc0 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -2,7 +2,7 @@ module BuildsHelper
def build_summary(build, skip: false)
if build.has_trace?
if skip
- link_to "View job trace", pipeline_build_url(build.pipeline, build)
+ link_to "View job trace", pipeline_job_url(build.pipeline, build)
else
build.trace.html(last_lines: 10).html_safe
end
@@ -20,8 +20,8 @@ module BuildsHelper
def javascript_build_options
{
- page_url: namespace_project_build_url(@project.namespace, @project, @build),
- build_url: namespace_project_build_url(@project.namespace, @project, @build, :json),
+ page_url: namespace_project_job_url(@project.namespace, @project, @build),
+ build_url: namespace_project_job_url(@project.namespace, @project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
@@ -31,7 +31,7 @@ module BuildsHelper
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
- description: namespace_project_build_url(@project.namespace, @project, @build)
+ description: namespace_project_job_url(@project.namespace, @project, @build)
}
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 4c4fbdd4d39..2ae3a616933 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -8,8 +8,8 @@ module DiffHelper
[marked_old_line, marked_new_line]
end
- def expand_all_diffs?
- params[:expand_all_diffs].present?
+ def diffs_expanded?
+ params[:expanded].present?
end
def diff_view
@@ -22,10 +22,10 @@ module DiffHelper
end
def diff_options
- options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? }
+ options = { ignore_whitespace_change: hide_whitespace?, expanded: diffs_expanded? }
if action_name == 'diff_for_path'
- options[:no_collapse] = true
+ options[:expanded] = true
options[:paths] = params.values_at(:old_path, :new_path)
end
@@ -66,12 +66,12 @@ module DiffHelper
discussions_left = discussions_right = nil
- if left && (left.unchanged? || left.removed?)
+ if left && (left.unchanged? || left.discussable?)
line_code = diff_file.line_code(left)
discussions_left = @grouped_diff_discussions[line_code]
end
- if right && right.added?
+ if right&.discussable?
line_code = diff_file.line_code(right)
discussions_right = @grouped_diff_discussions[line_code]
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index fc308b3960e..40864bed0ff 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -50,8 +50,8 @@ module GitlabRoutingHelper
namespace_project_cycle_analytics_path(project.namespace, project, *args)
end
- def project_builds_path(project, *args)
- namespace_project_builds_path(project.namespace, project, *args)
+ def project_jobs_path(project, *args)
+ namespace_project_jobs_path(project.namespace, project, *args)
end
def project_ref_path(project, ref_name, *args)
@@ -110,8 +110,8 @@ module GitlabRoutingHelper
namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
end
- def pipeline_build_url(pipeline, build, *args)
- namespace_project_build_url(pipeline.project.namespace, pipeline.project, build.id, *args)
+ def pipeline_job_url(pipeline, build, *args)
+ namespace_project_job_url(pipeline.project.namespace, pipeline.project, build.id, *args)
end
def commits_url(entity, *args)
@@ -215,13 +215,13 @@ module GitlabRoutingHelper
case action
when 'download'
- download_namespace_project_build_artifacts_path(*args)
+ download_namespace_project_job_artifacts_path(*args)
when 'browse'
- browse_namespace_project_build_artifacts_path(*args)
+ browse_namespace_project_job_artifacts_path(*args)
when 'file'
- file_namespace_project_build_artifacts_path(*args)
+ file_namespace_project_job_artifacts_path(*args)
when 'raw'
- raw_namespace_project_build_artifacts_path(*args)
+ raw_namespace_project_job_artifacts_path(*args)
end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 9290e4ec133..c380a10c82d 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -199,6 +199,27 @@ module IssuablesHelper
issuable_filter_params.any? { |k| params.key?(k) }
end
+ def issuable_initial_data(issuable)
+ {
+ endpoint: namespace_project_issue_path(@project.namespace, @project, issuable),
+ canUpdate: can?(current_user, :update_issue, issuable),
+ canDestroy: can?(current_user, :destroy_issue, issuable),
+ canMove: current_user ? issuable.can_move?(current_user) : false,
+ issuableRef: issuable.to_reference,
+ isConfidential: issuable.confidential,
+ markdownPreviewUrl: preview_markdown_path(@project),
+ markdownDocs: help_page_path('user/markdown'),
+ projectsAutocompleteUrl: autocomplete_projects_path(project_id: @project.id),
+ issuableTemplates: issuable_templates(issuable),
+ projectPath: ref_project.path,
+ projectNamespace: ref_project.namespace.full_path,
+ initialTitleHtml: markdown_field(issuable, :title),
+ initialTitleText: issuable.title,
+ initialDescriptionHtml: markdown_field(issuable, :description),
+ initialDescriptionText: issuable.description
+ }.to_json
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 043f57241a3..3d12f3c306b 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -13,13 +13,13 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters
}x
- serialize :restricted_visibility_levels
- serialize :import_sources
- serialize :disabled_oauth_sign_in_sources, Array
- serialize :domain_whitelist, Array
- serialize :domain_blacklist, Array
- serialize :repository_storages
- serialize :sidekiq_throttling_queues, Array
+ serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize
+ serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize
+ serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize
+ serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 967ffd46db0..46d412fbd72 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,5 +1,5 @@
class AuditEvent < ActiveRecord::Base
- serialize :details, Hash
+ serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize
belongs_to :user, foreign_key: :author_id
diff --git a/app/models/blob.rb b/app/models/blob.rb
index e75926241ba..6a42a12891c 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -102,10 +102,6 @@ class Blob < SimpleDelegator
raw_size == 0
end
- def too_large?
- size && truncated?
- end
-
def external_storage_error?
if external_storage == :lfs
!project&.lfs_enabled?
@@ -160,7 +156,7 @@ class Blob < SimpleDelegator
end
def readable_text?
- text? && !stored_externally? && !too_large?
+ text? && !stored_externally? && !truncated?
end
def simple_viewer
@@ -187,9 +183,9 @@ class Blob < SimpleDelegator
rendered_as_text? && rich_viewer
end
- def override_max_size!
- simple_viewer&.override_max_size = true
- rich_viewer&.override_max_size = true
+ def expand!
+ simple_viewer&.expanded = true
+ rich_viewer&.expanded = true
end
private
diff --git a/app/models/blob_viewer/auxiliary.rb b/app/models/blob_viewer/auxiliary.rb
index 07a207730cf..1bea225f17c 100644
--- a/app/models/blob_viewer/auxiliary.rb
+++ b/app/models/blob_viewer/auxiliary.rb
@@ -7,8 +7,8 @@ module BlobViewer
included do
self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary
- self.overridable_max_size = 100.kilobytes
- self.max_size = 100.kilobytes
+ self.collapse_limit = 100.kilobytes
+ self.size_limit = 100.kilobytes
end
def visible_to?(current_user)
diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb
index 26a3778c2a3..e6119d25fab 100644
--- a/app/models/blob_viewer/base.rb
+++ b/app/models/blob_viewer/base.rb
@@ -2,14 +2,14 @@ module BlobViewer
class Base
PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze
- class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :overridable_max_size, :max_size
+ class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :collapse_limit, :size_limit
self.loading_partial_name = 'loading'
delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class
attr_reader :blob
- attr_accessor :override_max_size
+ attr_accessor :expanded
delegate :project, to: :blob
@@ -61,24 +61,16 @@ module BlobViewer
self.class.load_async? && render_error.nil?
end
- def exceeds_overridable_max_size?
- overridable_max_size && blob.raw_size > overridable_max_size
- end
-
- def exceeds_max_size?
- max_size && blob.raw_size > max_size
- end
+ def collapsed?
+ return @collapsed if defined?(@collapsed)
- def can_override_max_size?
- exceeds_overridable_max_size? && !exceeds_max_size?
+ @collapsed = !expanded && collapse_limit && blob.raw_size > collapse_limit
end
def too_large?
- if override_max_size
- exceeds_max_size?
- else
- exceeds_overridable_max_size?
- end
+ return @too_large if defined?(@too_large)
+
+ @too_large = size_limit && blob.raw_size > size_limit
end
# This method is used on the server side to check whether we can attempt to
@@ -95,6 +87,8 @@ module BlobViewer
def render_error
if too_large?
:too_large
+ elsif collapsed?
+ :collapsed
end
end
diff --git a/app/models/blob_viewer/client_side.rb b/app/models/blob_viewer/client_side.rb
index cc68236f92b..079cfbe3616 100644
--- a/app/models/blob_viewer/client_side.rb
+++ b/app/models/blob_viewer/client_side.rb
@@ -4,8 +4,8 @@ module BlobViewer
included do
self.load_async = false
- self.overridable_max_size = 10.megabytes
- self.max_size = 50.megabytes
+ self.collapse_limit = 10.megabytes
+ self.size_limit = 50.megabytes
end
end
end
diff --git a/app/models/blob_viewer/server_side.rb b/app/models/blob_viewer/server_side.rb
index 87884dcd6bf..05a3dd7d913 100644
--- a/app/models/blob_viewer/server_side.rb
+++ b/app/models/blob_viewer/server_side.rb
@@ -4,8 +4,8 @@ module BlobViewer
included do
self.load_async = true
- self.overridable_max_size = 2.megabytes
- self.max_size = 5.megabytes
+ self.collapse_limit = 2.megabytes
+ self.size_limit = 5.megabytes
end
def prepare!
diff --git a/app/models/blob_viewer/text.rb b/app/models/blob_viewer/text.rb
index eddca50b4d4..f68cbb7e212 100644
--- a/app/models/blob_viewer/text.rb
+++ b/app/models/blob_viewer/text.rb
@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'text'
self.binary = false
- self.overridable_max_size = 1.megabyte
- self.max_size = 10.megabytes
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 10.megabytes
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 760ec8e5919..0000ecc5bbf 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -19,8 +19,8 @@ module Ci
)
end
- serialize :options
- serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
+ serialize :options # rubocop:disable Cop/ActiverecordSerialize
+ serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize
delegate :name, to: :project, prefix: true
@@ -51,6 +51,12 @@ module Ci
after_destroy :update_project_statistics
class << self
+ # This is needed for url_for to work,
+ # as the controller is JobsController
+ def model_name
+ ActiveModel::Name.new(self, nil, 'job')
+ end
+
def first_pending
pending.unstarted.order('created_at ASC').first
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 81c30b0e077..425ca9278eb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -30,6 +30,7 @@ module Ci
delegate :id, to: :project, prefix: true
+ validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? }
@@ -37,6 +38,16 @@ module Ci
after_create :keep_around_commits, unless: :importing?
+ enum source: {
+ unknown: nil,
+ push: 1,
+ web: 2,
+ trigger: 3,
+ schedule: 4,
+ api: 5,
+ external: 6
+ }
+
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
@@ -269,10 +280,6 @@ module Ci
commit.sha == sha
end
- def triggered?
- trigger_requests.any?
- end
-
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 07213ca608a..45d8cd34359 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -24,6 +24,10 @@ module Ci
owner == current_user
end
+ def own!(user)
+ update(owner: user)
+ end
+
def inactive?
!active?
end
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 2b807731d0d..564334ad1ad 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -6,7 +6,7 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id
has_many :builds
- serialize :variables
+ serialize :variables # rubocop:disable Cop/ActiverecordSerialize
def user_variables
return [] unless variables
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index dd1e6630642..c7bdc997eca 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -43,7 +43,12 @@ module Noteable
end
def resolvable_discussions
- @resolvable_discussions ||= discussion_notes.resolvable.discussions(self)
+ @resolvable_discussions ||=
+ if defined?(@discussions)
+ @discussions.select(&:resolvable?)
+ else
+ discussion_notes.resolvable.discussions(self)
+ end
end
def discussions_resolvable?
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 6fbc6a9f58b..20ef1378500 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -6,9 +6,9 @@ class DiffNote < Note
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
- serialize :original_position, Gitlab::Diff::Position
- serialize :position, Gitlab::Diff::Position
- serialize :change_position, Gitlab::Diff::Position
+ serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
validates :original_position, presence: true
validates :position, presence: true
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 0b6b920ed66..9b32d573387 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -21,7 +21,8 @@ class Discussion
end
def self.build_collection(notes, context_noteable = nil)
- notes.group_by { |n| n.discussion_id(context_noteable) }.values.map { |notes| build(notes, context_noteable) }
+ grouped_notes = notes.group_by { |n| n.discussion_id(context_noteable) }
+ grouped_notes.values.map { |notes| build(notes, context_noteable) }
end
# Returns an alphanumeric discussion ID based on `build_discussion_id`
diff --git a/app/models/event.rb b/app/models/event.rb
index e6fad46077a..46e89388bc1 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -26,7 +26,7 @@ class Event < ActiveRecord::Base
belongs_to :target, polymorphic: true
# For Hash only
- serialize :data
+ serialize :data # rubocop:disable Cop/ActiverecordSerialize
# Callbacks
after_create :reset_project_activity
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index 2738b229d84..d73cfcf630d 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,9 +1,9 @@
class WebHookLog < ActiveRecord::Base
belongs_to :web_hook
- serialize :request_headers, Hash
- serialize :request_data, Hash
- serialize :response_headers, Hash
+ serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
validates :web_hook, presence: true
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index ebf8fb92ab5..7126de2d488 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -7,7 +7,7 @@
class LegacyDiffNote < Note
include NoteOnDiff
- serialize :st_diff
+ serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize
validates :line_code, presence: true, line_code: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1c4bb119bbe..dd155252ad5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -21,7 +21,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to :assignee, class_name: "User"
- serialize :merge_params, Hash
+ serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
@@ -220,10 +220,10 @@ class MergeRequest < ActiveRecord::Base
def diffs(diff_options = {})
if compare
- # When saving MR diffs, `no_collapse` is implicitly added (because we need
+ # When saving MR diffs, `expanded` is implicitly added (because we need
# to save the entire contents to the DB), so add that here for
# consistency.
- compare.diffs(diff_options.merge(no_collapse: true))
+ compare.diffs(diff_options.merge(expanded: true))
else
merge_request_diff.diffs(diff_options)
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 1bd61c1d465..1c2f335f95e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -11,8 +11,8 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
- serialize :st_commits
- serialize :st_diffs
+ serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
+ serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
state_machine :state, initial: :empty do
state :collected
diff --git a/app/models/note.rb b/app/models/note.rb
index 60257aac93b..832c68243fb 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -110,7 +110,7 @@ class Note < ActiveRecord::Base
end
def discussions(context_noteable = nil)
- Discussion.build_collection(fresh, context_noteable)
+ Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
end
def find_discussion(discussion_id)
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index e8b000ddad6..ae9f71e7747 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
- serialize :scopes, Array
+ serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize
belongs_to :user
diff --git a/app/models/project.rb b/app/models/project.rb
index a59095cb25c..7cb79e3249d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -242,6 +242,7 @@ class Project < ActiveRecord::Base
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
+ scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
@@ -350,10 +351,6 @@ class Project < ActiveRecord::Base
where("projects.id IN (#{union.to_sql})")
end
- def search_by_visibility(level)
- where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
- end
-
def search_by_title(query)
pattern = "%#{query}%"
table = Project.arel_table
@@ -1064,11 +1061,6 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_pipeline(ref, sha, current_user = nil)
- pipeline_for(ref, sha) ||
- pipelines.create(sha: sha, ref: ref, user: current_user)
- end
-
def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 331123a5a5b..e3cafd4d1c6 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base
insecure_mode: true,
algorithm: 'aes-256-cbc'
- serialize :data, JSON
+ serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize
validates :project, presence: true
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 543b9b293e0..e1cc56551ba 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -167,7 +167,7 @@ class ProjectTeam
access = RequestStore.store[key]
end
- # Lookup only the IDs we need
+ # Look up only the IDs we need
user_ids = user_ids - access.keys
return access if user_ids.empty?
@@ -178,6 +178,13 @@ class ProjectTeam
maximum(:access_level)
access.merge!(users_access)
+
+ missing_user_ids = user_ids - users_access.keys
+
+ missing_user_ids.each do |user_id|
+ access[user_id] = Gitlab::Access::NO_ACCESS
+ end
+
access
end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 0ae5864615a..eed3ca7e179 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,5 +1,5 @@
class SentNotification < ActiveRecord::Base
- serialize :position, Gitlab::Diff::Position
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
belongs_to :project
belongs_to :noteable, polymorphic: true
diff --git a/app/models/service.rb b/app/models/service.rb
index 8916f88076e..6a0b0a5c522 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,7 +2,7 @@
# and implement a set of methods
class Service < ActiveRecord::Base
include Sortable
- serialize :properties, JSON
+ serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize
default_value_for :active, false
default_value_for :push_events, true
diff --git a/app/models/user.rb b/app/models/user.rb
index 3f816a250c2..8114d0ff88e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -40,7 +40,7 @@ class User < ActiveRecord::Base
otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
- serialize :otp_backup_codes, JSON
+ serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
@@ -369,6 +369,7 @@ class User < ActiveRecord::Base
# Pattern used to extract `@user` user references from text
def reference_pattern
%r{
+ (?<!\w)
#{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
}x
@@ -557,12 +558,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end
- def viewable_starred_projects
- starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (?)",
- [Project::PUBLIC, Project::INTERNAL],
- authorized_projects.select(:project_id))
- end
-
def owned_projects
@owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?',
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index d4af4490608..2d7405dc240 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -23,7 +23,7 @@ module Ci
!::Gitlab::UserAccess
.new(user, project: build.project)
- .can_push_to_branch?(build.ref)
+ .can_merge_to_branch?(build.ref)
end
end
end
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb
index a0db5b8f0f4..ad7ad020b03 100644
--- a/app/serializers/analytics_build_entity.rb
+++ b/app/serializers/analytics_build_entity.rb
@@ -25,7 +25,7 @@ class AnalyticsBuildEntity < Grape::Entity
end
expose :url do |build|
- url_to(:namespace_project_build, build)
+ url_to(:namespace_project_job, build)
end
expose :commit_url do |build|
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
index 5e99204c658..301b718d060 100644
--- a/app/serializers/build_action_entity.rb
+++ b/app/serializers/build_action_entity.rb
@@ -6,7 +6,7 @@ class BuildActionEntity < Grape::Entity
end
expose :path do |build|
- play_namespace_project_build_path(
+ play_namespace_project_job_path(
build.project.namespace,
build.project,
build)
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index 8b643d8e783..dde17aa68b8 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -6,7 +6,7 @@ class BuildArtifactEntity < Grape::Entity
end
expose :path do |build|
- download_namespace_project_build_artifacts_path(
+ download_namespace_project_job_artifacts_path(
build.project.namespace,
build.project,
build)
diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb
index e2276808b90..05dd8270e92 100644
--- a/app/serializers/build_entity.rb
+++ b/app/serializers/build_entity.rb
@@ -5,15 +5,15 @@ class BuildEntity < Grape::Entity
expose :name
expose :build_path do |build|
- path_to(:namespace_project_build, build)
+ path_to(:namespace_project_job, build)
end
expose :retry_path do |build|
- path_to(:retry_namespace_project_build, build)
+ path_to(:retry_namespace_project_job, build)
end
expose :play_path, if: -> (*) { playable? } do |build|
- path_to(:play_namespace_project_build, build)
+ path_to(:play_namespace_project_job, build)
end
expose :playable?, as: :playable
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index bc4f68710b2..35df95549b7 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -1,4 +1,6 @@
class IssueEntity < IssuableEntity
+ include RequestAwareEntity
+
expose :branch_name
expose :confidential
expose :assignees, using: API::Entities::UserBasic
@@ -7,4 +9,8 @@ class IssueEntity < IssuableEntity
expose :project_id
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
+
+ expose :web_url do |issue|
+ namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index ea57cc97a7e..486f8c36fbd 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -5,6 +5,7 @@ class PipelineEntity < Grape::Entity
expose :user, using: UserEntity
expose :active?, as: :active
expose :coverage
+ expose :source
expose :path do |pipeline|
namespace_project_pipeline_path(
@@ -24,7 +25,6 @@ class PipelineEntity < Grape::Entity
expose :flags do
expose :latest?, as: :latest
- expose :triggered?, as: :triggered
expose :stuck?, as: :stuck
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index a98b7167765..8227a78a650 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -2,8 +2,9 @@ module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
- def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil)
+ def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil)
@pipeline = Ci::Pipeline.new(
+ source: source,
project: project,
ref: ref,
sha: sha,
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 8362f01ddb8..beb27a5a597 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -4,7 +4,7 @@ module Ci
trigger_request = trigger.trigger_requests.create(variables: variables)
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref).
- execute(ignore_skip_ci: true, trigger_request: trigger_request)
+ execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
trigger_request if pipeline.persisted?
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index d22236b961b..f080e6326a1 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -106,7 +106,7 @@ class GitPushService < BaseService
EventCreateService.new.push(@project, current_user, build_push_data)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
- Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute
+ Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
if push_remove_branch?
AfterBranchDeleteService
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 96432837481..7c424fba428 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -11,7 +11,7 @@ class GitTagPushService < BaseService
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
- Ci::CreatePipelineService.new(project, current_user, @push_data).execute
+ Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
true
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index 163bd5662b0..dff549f502c 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -20,7 +20,7 @@
%span
Groups
= nav_link path: 'builds#index' do
- = link_to admin_builds_path, title: 'Jobs' do
+ = link_to admin_jobs_path, title: 'Jobs' do
%span
Jobs
= nav_link path: ['runners#index', 'runners#show'] do
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 53f0a1e7fde..3c9f932a225 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -79,6 +79,12 @@
= gitlab_pages
%span.light.pull-right
= boolean_to_icon gitlab_pages_enabled
+ - gitlab_shared_runners = 'Shared Runners'
+ - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
+ %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
+ = gitlab_shared_runners
+ %span.light.pull-right
+ = boolean_to_icon gitlab_shared_runners_enabled
.col-md-4
%h4
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/jobs/index.html.haml
index 66d633119c2..09be17f07be 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -4,15 +4,15 @@
%div{ class: container_class }
.top-area
- - build_path_proc = ->(scope) { admin_builds_path(scope: scope) }
+ - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
.nav-controls
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+ = link_to 'Cancel all', cancel_all_admin_jobs_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.row-content-block.second-block
#{(@scope || 'all').capitalize} jobs
%ul.content-list.builds-content-list.admin-builds-table
- = render "projects/builds/table", builds: @builds, admin: true
+ = render "projects/jobs/table", builds: @builds, admin: true
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index dc4116e1ce0..801430e525e 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -85,7 +85,7 @@
%tr.build
%td.id
- if project
- = link_to namespace_project_build_path(project.namespace, project, build) do
+ = link_to namespace_project_job_path(project.namespace, project, build) do
%strong ##{build.id}
- else
%strong ##{build.id}
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 3b2a555a143..2890ae7173b 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -9,7 +9,7 @@
= render "projects/last_push"
%div{ class: container_class }
- - unless show_user_callout?
+ - if show_user_callout?
= render 'shared/user_callout'
- if @projects.any? || params[:name]
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index e4dfe0c8c08..29658da7792 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -92,7 +92,7 @@
-# Shortcut to Pipelines > Jobs
- if project_nav_tab? :builds
%li.hidden
- = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
Jobs
-# Shortcut to commits page
diff --git a/app/views/notify/links/ci/builds/_build.html.haml b/app/views/notify/links/ci/builds/_build.html.haml
index d35b3839171..644cf506eff 100644
--- a/app/views/notify/links/ci/builds/_build.html.haml
+++ b/app/views/notify/links/ci/builds/_build.html.haml
@@ -1,2 +1,2 @@
-%a{ href: pipeline_build_url(pipeline, build), style: "color:#3777b0;text-decoration:none;" }
+%a{ href: pipeline_job_url(pipeline, build), style: "color:#3777b0;text-decoration:none;" }
= build.name
diff --git a/app/views/notify/links/ci/builds/_build.text.erb b/app/views/notify/links/ci/builds/_build.text.erb
index 741c7f344c8..773ae8174e9 100644
--- a/app/views/notify/links/ci/builds/_build.text.erb
+++ b/app/views/notify/links/ci/builds/_build.text.erb
@@ -1 +1 @@
-Job #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
+Job #<%= build.id %> ( <%= pipeline_job_url(pipeline, build) %> )
diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml
index 34d5c3b7285..e2966ec33c2 100644
--- a/app/views/projects/artifacts/_tree_directory.html.haml
+++ b/app/views/projects/artifacts/_tree_directory.html.haml
@@ -1,4 +1,4 @@
-- path_to_directory = browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: directory.path)
+- path_to_directory = browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: directory.path)
%tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index ce7e25d774b..ea0b43b85cf 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -1,4 +1,4 @@
-- path_to_file = file_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: file.path)
+- path_to_file = file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: file.path)
%tr.tree-item{ 'data-link' => path_to_file }
- blob = file.blob
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 9fbb30f7c7c..961c805dc7c 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,22 +1,22 @@
- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
= render "projects/pipelines/head"
-= render "projects/builds/header", show_controls: false
+= render "projects/jobs/header", show_controls: false
.tree-holder
.nav-block
.tree-controls
- = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
+ = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build),
rel: 'nofollow', download: '', class: 'btn btn-default download' do
= icon('download')
Download artifacts archive
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
- path_breadcrumbs do |title, path|
%li
- = link_to truncate(title, length: 40), browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to truncate(title, length: 40), browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
.tree-content-holder
%table.table.tree-table
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index d8da83b9a80..b25c7c95196 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -1,21 +1,21 @@
- page_title @path, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
= render "projects/pipelines/head"
-= render "projects/builds/header", show_controls: false
+= render "projects/jobs/header", show_controls: false
#tree-holder.tree-holder
.nav-block
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
- path_breadcrumbs do |title, path|
- title = truncate(title, length: 40)
%li
- if path == @path
- = link_to file_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path) do
+ = link_to file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) do
%strong= title
- else
- = link_to title, browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to title, browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
%article.file-holder
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index a190a8760ef..d9f28d66b66 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to namespace_project_build_url(job.project.namespace, job.project, job) do
+ = link_to namespace_project_job_url(job.project.namespace, job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
@@ -95,16 +95,16 @@
%td
.pull-right
- if can?(current_user, :read_build, job) && job.artifacts?
- = link_to download_namespace_project_build_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
+ = link_to download_namespace_project_job_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
- = link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
+ = link_to cancel_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- if job.playable? && !admin && can?(current_user, :update_build, job)
- = link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = link_to play_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play')
- elsif job.retryable?
- = link_to retry_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = link_to retry_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 506246f2ee6..e2baaa625ae 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -8,6 +8,7 @@
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
+ - next unless can?(current_user, :update_build, action)
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index c7e22a0b4ec..59844bc00cd 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -3,7 +3,7 @@
.diff-content
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- - elsif blob.too_large?
+ - elsif blob.truncated?
.nothing-here-block The file could not be displayed because it is too large.
- elsif blob.readable_text?
- if !diff_file.repository.diffable?(blob)
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 4768438c29e..d538c4c86c8 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -5,8 +5,8 @@
.content-block.oneline-block.files-changed
.inline-parallel-buttons
- - if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
- = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
+ - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
+ = link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 7439b8a66f7..43708d22a0c 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -3,7 +3,7 @@
- discussions = local_assigns.fetch(:discussions, nil)
- type = line.type
- line_code = diff_file.line_code(line)
-- if discussions && !line.meta?
+- if discussions && line.discussable?
- line_discussions = discussions[line_code]
%tr.line_holder{ class: type, id: (line_code unless plain) }
- case type
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7315e671056..9e221240cf2 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -13,7 +13,7 @@
= render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment)
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
- - if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
+ - if can?(current_user, :stop_environment, @environment)
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.environments-container
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 67403c36d7f..7bf271c2fc5 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -31,7 +31,7 @@
%ul
- if can_update_issue
%li
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'issuable-edit'
%li
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
@@ -55,10 +55,8 @@
.issue-details.issuable-details
.detail-page-description.content-block
- #js-issuable-app{ "data" => { "endpoint" => realtime_changes_namespace_project_issue_path(@project.namespace, @project, @issue),
- "can-update" => can?(current_user, :update_issue, @issue).to_s,
- "issuable-ref" => @issue.to_reference,
- } }
+ %script#js-issuable-app-initial-data{ type: "application/json" }= issuable_initial_data(@issue)
+ #js-issuable-app
%h2.title= markdown_field(@issue, :title)
- if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index d4cdb709b97..ad72ab5b199 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -6,7 +6,7 @@
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
%strong
Job
- = link_to "##{@build.id}", namespace_project_build_path(@project.namespace, @project, @build), class: 'js-build-id'
+ = link_to "##{@build.id}", namespace_project_job_path(@project.namespace, @project, @build), class: 'js-build-id'
in pipeline
%strong
= link_to "##{pipeline.id}", pipeline_path(pipeline)
@@ -17,7 +17,7 @@
%strong
= link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name'
- = render "projects/builds/user" if @build.user
+ = render "projects/jobs/user" if @build.user
= time_ago_with_tooltip(@build.created_at)
@@ -26,6 +26,6 @@
- if can?(current_user, :create_issue, @project) && @build.failed?
= link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
+ = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index b3abc0e3da1..3e83142377b 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -30,21 +30,21 @@
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
- = link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
+ = link_to keep_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
- = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
+ = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
- if @build.artifacts_metadata?
- = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+ = link_to browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
Browse
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
.title
Job details
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
+ = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
- if @build.merge_request
%p.build-detail-row
%span.build-light-text Merge Request:
@@ -69,7 +69,7 @@
\##{@build.runner.id}
.btn-group.btn-group-justified{ role: :group }
- if @build.active?
- = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
+ = link_to "Cancel", cancel_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
- if @build.trigger_request
.build-widget
@@ -119,7 +119,7 @@
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- = link_to namespace_project_build_path(@project.namespace, @project, build) do
+ = link_to namespace_project_job_path(@project.namespace, @project, build) do
= icon('arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/jobs/_table.html.haml
index 82806f022ee..82806f022ee 100644
--- a/app/views/projects/builds/_table.html.haml
+++ b/app/views/projects/jobs/_table.html.haml
diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/jobs/_user.html.haml
index 83f299da651..83f299da651 100644
--- a/app/views/projects/builds/_user.html.haml
+++ b/app/views/projects/jobs/_user.html.haml
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/jobs/index.html.haml
index a8c8afe2695..a33e3978ee1 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -4,13 +4,13 @@
%div{ class: container_class }
.top-area
- - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) }
+ - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
.nav-controls
- if can?(current_user, :update_build, @project)
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
+ = link_to 'Cancel running', cancel_all_namespace_project_jobs_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/jobs/show.html.haml
index a5a9a6435e3..0d10dfcef70 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -62,17 +62,17 @@
Showing last
%span.js-truncated-info-size.truncated-info-size><
KiB of log -
- %a.js-raw-link.raw-link{ href: raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw
+ %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
.controllers
- if @build.has_trace?
- = link_to raw_namespace_project_build_path(@project.namespace, @project, @build),
+ = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
title: 'Open raw trace',
data: { placement: 'top', container: 'body' },
class: 'js-raw-link-controller has-tooltip' do
= icon('download')
- if can?(current_user, :update_build, @project) && @build.erasable?
- = link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
+ = link_to erase_namespace_project_job_path(@project.namespace, @project, @build),
method: :post,
data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
title: 'Erase Build',
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index db9d77dba16..a33da149c62 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -11,7 +11,7 @@
- if project_nav_tab? :builds
= nav_link(controller: [:builds, :artifacts]) do
- = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
%span
Jobs
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index aea8d13b7c5..01cf2cc80e5 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -51,5 +51,5 @@
%span.stage
= build.stage.titleize
%span.build-name
- = link_to build.name, pipeline_build_url(pipeline, build)
+ = link_to build.name, pipeline_job_url(pipeline, build)
%pre.build-log= build_summary(build, skip: index >= 10)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c587155bc4f..c239253c8d5 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -100,7 +100,7 @@
Snippets
%div{ class: container_class }
- - if @user == current_user && !show_user_callout?
+ - if @user == current_user && show_user_callout?
= render 'shared/user_callout'
.tab-content
#activity.tab-pane
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 7eb0e84acb2..7b485b3363c 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -14,7 +14,7 @@ class PipelineScheduleWorker
Ci::CreatePipelineService.new(schedule.project,
schedule.owner,
ref: schedule.ref)
- .execute(save_on_errors: false, schedule: schedule)
+ .execute(:schedule, save_on_errors: false, schedule: schedule)
rescue => e
Rails.logger.error "#{schedule.id}: Failed to create a scheduled pipeline: #{e.message}"
ensure