summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorClement Ho <ClemMakesApps@gmail.com>2018-05-16 12:46:52 -0500
committerClement Ho <ClemMakesApps@gmail.com>2018-05-16 12:46:52 -0500
commitfcae43c604cb903c11a2cf6c27824e47f315831a (patch)
treeaeefc767a1bdccef5ef77e950c16f5deabac4691 /app
parente32aed55b8cf01965558056d86087a31f65ca874 (diff)
parent9e41ed61ea70532578416e01c228ff7a81abc192 (diff)
downloadgitlab-ce-fcae43c604cb903c11a2cf6c27824e47f315831a.tar.gz
Merge branch 'master' into bootstrap4
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_edited_text.vue10
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue30
-rw-r--r--app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue77
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js28
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue95
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue53
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue96
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js2
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue7
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue20
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue (renamed from app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js)6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue (renamed from app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js)166
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue14
-rw-r--r--app/assets/stylesheets/framework/mixins.scss12
-rw-r--r--app/assets/stylesheets/framework/snippets.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss8
-rw-r--r--app/assets/stylesheets/pages/notes.scss9
-rw-r--r--app/controllers/application_controller.rb9
-rw-r--r--app/controllers/concerns/send_file_upload.rb4
-rw-r--r--app/controllers/projects/notes_controller.rb13
-rw-r--r--app/controllers/projects/pipelines_controller.rb4
-rw-r--r--app/controllers/users/terms_controller.rb4
-rw-r--r--app/helpers/auto_devops_helper.rb9
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/helpers/users_helper.rb19
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/list.rb3
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/user.rb8
-rw-r--r--app/policies/ci/build_policy.rb9
-rw-r--r--app/policies/ci/pipeline_policy.rb8
-rw-r--r--app/policies/global_policy.rb19
-rw-r--r--app/policies/project_policy.rb4
-rw-r--r--app/views/discussions/_discussion.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml116
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml4
-rw-r--r--app/views/projects/wikis/edit.html.haml11
-rw-r--r--app/views/shared/boards/components/_board.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml2
43 files changed, 500 insertions, 415 deletions
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index c2a35341eb2..fae580c091b 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -179,7 +179,7 @@
role="row"
>
<div
- class="alert alert-danger alert-block append-bottom-0 table-section section-100"
+ class="alert alert-danger alert-block append-bottom-0"
role="gridcell"
>
<div>
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index 4ddca918495..2dc39d1a186 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -32,17 +32,17 @@ export default {
<template>
<div :class="className">
{{ actionText }}
- <time-ago-tooltip
- :time="editedAt"
- tooltip-placement="bottom"
- />
<template v-if="editedBy">
- by
+ {{ s__('ByAuthor|by') }}
<a
:href="editedBy.path"
class="js-vue-author author_link">
{{ editedBy.name }}
</a>
</template>
+ <time-ago-tooltip
+ :time="editedAt"
+ tooltip-placement="bottom"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index c3d1ef1fcc6..7183d0b50b2 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -62,6 +62,21 @@ export default {
<template>
<div class="note-header-info">
+ <div
+ v-if="includeToggle"
+ class="discussion-actions">
+ <button
+ @click="handleToggle"
+ class="note-action-button discussion-toggle-button js-vue-toggle-button"
+ type="button">
+ <i
+ :class="toggleChevronClass"
+ class="fa"
+ aria-hidden="true">
+ </i>
+ {{ __('Toggle discussion') }}
+ </button>
+ </div>
<a :href="author.path">
<span class="note-header-author-name">{{ author.name }}</span>
<span class="note-headline-light">
@@ -95,20 +110,5 @@ export default {
</i>
</span>
</span>
- <div
- v-if="includeToggle"
- class="discussion-actions">
- <button
- @click="handleToggle"
- class="note-action-button discussion-toggle-button js-vue-toggle-button"
- type="button">
- <i
- :class="toggleChevronClass"
- class="fa"
- aria-hidden="true">
- </i>
- Toggle discussion
- </button>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue
new file mode 100644
index 00000000000..df21e2f8771
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue
@@ -0,0 +1,77 @@
+<script>
+import _ from 'underscore';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { s__, sprintf } from '~/locale';
+
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ deleteWikiUrl: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ pageTitle: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ csrfToken: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ },
+ computed: {
+ message() {
+ return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
+ },
+ title() {
+ return sprintf(
+ s__('WikiPageConfirmDelete|Delete page %{pageTitle}?'),
+ {
+ pageTitle: _.escape(this.pageTitle),
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ onSubmit() {
+ this.$refs.form.submit();
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ id="delete-wiki-modal"
+ :header-title-text="title"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete page')"
+ @submit="onSubmit"
+ >
+ {{ message }}
+ <form
+ ref="form"
+ :action="deleteWikiUrl"
+ method="post"
+ class="form-horizontal js-requires-input"
+ >
+ <input
+ ref="method"
+ type="hidden"
+ name="_method"
+ value="delete"
+ />
+ <input
+ type="hidden"
+ name="authenticity_token"
+ :value="csrfToken"
+ />
+ </form>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index ec01c66ffda..0295653cb29 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -1,12 +1,40 @@
import $ from 'jquery';
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import csrf from '~/lib/utils/csrf';
import Wikis from './wikis';
import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form';
+import deleteWikiModal from './components/delete_wiki_modal.vue';
document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
+
+ const deleteWikiButton = document.getElementById('delete-wiki-button');
+
+ if (deleteWikiButton) {
+ Vue.use(Translate);
+
+ const { deleteWikiUrl, pageTitle } = deleteWikiButton.dataset;
+ const deleteWikiModalEl = document.getElementById('delete-wiki-modal');
+ const deleteModal = new Vue({ // eslint-disable-line
+ el: deleteWikiModalEl,
+ data: {
+ deleteWikiUrl: '',
+ },
+ render(createElement) {
+ return createElement(deleteWikiModal, {
+ props: {
+ pageTitle,
+ deleteWikiUrl,
+ csrfToken: csrf.token,
+ },
+ });
+ },
+ });
+ }
});
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
deleted file mode 100644
index 0cdffbde05b..00000000000
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<script>
- /* eslint-disable no-alert */
-
- import eventHub from '../event_hub';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
- import icon from '../../vue_shared/components/icon.vue';
- import tooltip from '../../vue_shared/directives/tooltip';
-
- export default {
- directives: {
- tooltip,
- },
- components: {
- loadingIcon,
- icon,
- },
- props: {
- endpoint: {
- type: String,
- required: true,
- },
- title: {
- type: String,
- required: true,
- },
- icon: {
- type: String,
- required: true,
- },
- cssClass: {
- type: String,
- required: true,
- },
- pipelineId: {
- type: Number,
- required: true,
- },
- type: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- isLoading: false,
- };
- },
- computed: {
- buttonClass() {
- return `btn ${this.cssClass}`;
- },
- },
- created() {
- // We're using eventHub to listen to the modal here instead of
- // using props because it would would make the parent components
- // much more complex to keep track of the loading state of each button
- eventHub.$on('postAction', this.setLoading);
- },
- beforeDestroy() {
- eventHub.$off('postAction', this.setLoading);
- },
- methods: {
- onClick() {
- eventHub.$emit('openConfirmationModal', {
- pipelineId: this.pipelineId,
- endpoint: this.endpoint,
- type: this.type,
- });
- },
- setLoading(endpoint) {
- if (endpoint === this.endpoint) {
- this.isLoading = true;
- }
- },
- },
- };
-</script>
-
-<template>
- <button
- v-tooltip
- type="button"
- @click="onClick"
- :class="buttonClass"
- :title="title"
- :aria-label="title"
- data-container="body"
- data-placement="top"
- :disabled="isLoading">
- <icon
- :name="icon"
- />
- <loading-icon v-if="isLoading" />
- </button>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 714aed1333e..41986b827cd 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -1,7 +1,7 @@
<script>
- import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+ import Modal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
- import pipelinesTableRowComponent from './pipelines_table_row.vue';
+ import PipelinesTableRowComponent from './pipelines_table_row.vue';
import eventHub from '../event_hub';
/**
@@ -11,8 +11,8 @@
*/
export default {
components: {
- pipelinesTableRowComponent,
- DeprecatedModal,
+ PipelinesTableRowComponent,
+ Modal,
},
props: {
pipelines: {
@@ -37,30 +37,18 @@
return {
pipelineId: '',
endpoint: '',
- type: '',
};
},
computed: {
modalTitle() {
- return this.type === 'stop' ?
- sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
- pipelineId: `'${this.pipelineId}'`,
- }, false) :
- sprintf(s__('Pipeline|Retry pipeline #%{pipelineId}?'), {
- pipelineId: `'${this.pipelineId}'`,
- }, false);
+ return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
+ pipelineId: `${this.pipelineId}`,
+ }, false);
},
modalText() {
- return this.type === 'stop' ?
- sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
- pipelineId: `<strong>#${this.pipelineId}</strong>`,
- }, false) :
- sprintf(s__('Pipeline|You’re about to retry pipeline %{pipelineId}.'), {
- pipelineId: `<strong>#${this.pipelineId}</strong>`,
- }, false);
- },
- primaryButtonLabel() {
- return this.type === 'stop' ? s__('Pipeline|Stop pipeline') : s__('Pipeline|Retry pipeline');
+ return sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
+ pipelineId: `<strong>#${this.pipelineId}</strong>`,
+ }, false);
},
},
created() {
@@ -73,7 +61,6 @@
setModalData(data) {
this.pipelineId = data.pipelineId;
this.endpoint = data.endpoint;
- this.type = data.type;
},
onSubmit() {
eventHub.$emit('postAction', this.endpoint);
@@ -120,20 +107,16 @@
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
- <deprecated-modal
+
+ <modal
id="confirmation-modal"
- :title="modalTitle"
- :text="modalText"
- kind="danger"
- :primary-button-label="primaryButtonLabel"
+ :header-title-text="modalTitle"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('Pipeline|Stop pipeline')"
@submit="onSubmit"
>
- <template
- slot="body"
- slot-scope="props"
- >
- <p v-html="props.text"></p>
- </template>
- </deprecated-modal>
+ <span v-html="modalText"></span>
+ </modal>
+
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 0dcda8a2234..eaff0b25354 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -1,13 +1,14 @@
<script>
- /* eslint-disable no-param-reassign */
- import asyncButtonComponent from './async_button.vue';
- import pipelinesActionsComponent from './pipelines_actions.vue';
- import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
- import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
- import pipelineStage from './stage.vue';
- import pipelineUrl from './pipeline_url.vue';
- import pipelinesTimeago from './time_ago.vue';
- import commitComponent from '../../vue_shared/components/commit.vue';
+ import eventHub from '../event_hub';
+ import PipelinesActionsComponent from './pipelines_actions.vue';
+ import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
+ import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
+ import PipelineStage from './stage.vue';
+ import PipelineUrl from './pipeline_url.vue';
+ import PipelinesTimeago from './time_ago.vue';
+ import CommitComponent from '../../vue_shared/components/commit.vue';
+ import LoadingButton from '../../vue_shared/components/loading_button.vue';
+ import Icon from '../../vue_shared/components/icon.vue';
/**
* Pipeline table row.
@@ -16,14 +17,15 @@
*/
export default {
components: {
- asyncButtonComponent,
- pipelinesActionsComponent,
- pipelinesArtifactsComponent,
- commitComponent,
- pipelineStage,
- pipelineUrl,
- ciBadge,
- pipelinesTimeago,
+ PipelinesActionsComponent,
+ PipelinesArtifactsComponent,
+ CommitComponent,
+ PipelineStage,
+ PipelineUrl,
+ CiBadge,
+ PipelinesTimeago,
+ LoadingButton,
+ Icon,
},
props: {
pipeline: {
@@ -44,6 +46,12 @@
required: true,
},
},
+ data() {
+ return {
+ isRetrying: false,
+ isCancelling: false,
+ };
+ },
computed: {
/**
* If provided, returns the commit tag.
@@ -119,8 +127,10 @@
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
+ // eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
+ // eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
@@ -216,6 +226,21 @@
return this.viewType === 'child';
},
},
+
+ methods: {
+ handleCancelClick() {
+ this.isCancelling = true;
+
+ eventHub.$emit('openConfirmationModal', {
+ pipelineId: this.pipeline.id,
+ endpoint: this.pipeline.cancel_path,
+ });
+ },
+ handleRetryClick() {
+ this.isRetrying = true;
+ eventHub.$emit('retryPipeline', this.pipeline.retry_path);
+ },
+ },
};
</script>
<template>
@@ -287,7 +312,8 @@
<div
v-if="displayPipelineActions"
- class="table-section section-20 table-button-footer pipeline-actions">
+ class="table-section section-20 table-button-footer pipeline-actions"
+ >
<div class="btn-group table-action-buttons">
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
@@ -300,29 +326,27 @@
:artifacts="pipeline.details.artifacts"
/>
- <async-button-component
+ <loading-button
v-if="pipeline.flags.retryable"
- :endpoint="pipeline.retry_path"
- css-class="js-pipelines-retry-button btn-default btn-retry"
- title="Retry"
- icon="repeat"
- :pipeline-id="pipeline.id"
- data-toggle="modal"
- data-target="#confirmation-modal"
- type="retry"
- />
+ @click="handleRetryClick"
+ container-class="js-pipelines-retry-button btn btn-default btn-retry"
+ :loading="isRetrying"
+ :disabled="isRetrying"
+ >
+ <icon name="repeat" />
+ </loading-button>
- <async-button-component
+ <loading-button
v-if="pipeline.flags.cancelable"
- :endpoint="pipeline.cancel_path"
- css-class="js-pipelines-cancel-button btn-remove"
- title="Stop"
- icon="close"
- :pipeline-id="pipeline.id"
+ @click="handleCancelClick"
data-toggle="modal"
data-target="#confirmation-modal"
- type="stop"
- />
+ container-class="js-pipelines-cancel-button btn btn-remove"
+ :loading="isCancelling"
+ :disabled="isCancelling"
+ >
+ <icon name="close" />
+ </loading-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 6d87f75ae8e..de0faf181e5 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -53,10 +53,12 @@ export default {
});
eventHub.$on('postAction', this.postAction);
+ eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
+ eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index fc65f1a714e..2fc3778820b 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -28,11 +28,6 @@
isOpen: false,
};
},
- computed: {
- clipboardText() {
- return `docker pull ${this.repo.location}`;
- },
- },
methods: {
...mapActions([
'fetchRepos',
@@ -84,7 +79,7 @@
<clipboard-button
v-if="repo.location"
- :text="clipboardText"
+ :text="repo.location"
:title="repo.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 36215a6f845..e4a4b3bb129 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -56,10 +56,6 @@
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
},
- clipboardText(text) {
- return `docker pull ${text}`;
- },
-
showError(message) {
Flash(errorMessages[message]);
},
@@ -89,7 +85,7 @@
<clipboard-button
v-if="item.location"
:title="item.location"
- :text="clipboardText(item.location)"
+ :text="item.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
</td>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
deleted file mode 100644
index f0298f732ea..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
- export default {
- name: 'MRWidgetMaintainerEdit',
- props: {
- maintainerEditAllowed: {
- type: Boolean,
- default: false,
- required: false,
- },
- },
- };
-</script>
-
-<template>
- <section class="mr-info-list mr-links">
- <p v-if="maintainerEditAllowed">
- {{ s__("mrWidget|Allows edits from maintainers") }}
- </p>
- </section>
-</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
index bf8628d18a6..926a3172412 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
@@ -10,6 +10,6 @@ In EE, the configuration extends this object to add a functioning squash-before-
button.
*/
-export default {
- template: '',
-};
+<script>
+ export default {};
+</script>
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 7f5f28091da..15097fa2a3f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -15,7 +15,6 @@ export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as Deployment } from './components/deployment.vue';
-export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
@@ -41,8 +40,8 @@ export { default as MRWidgetService } from './services/mr_widget_service';
export { default as eventHub } from './event_hub';
export { default as getStateKey } from './stores/get_state_key';
export { default as stateMaps } from './stores/state_maps';
-export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge';
+export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge.vue';
export { default as notify } from '../lib/utils/notify';
export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue';
-export { default as mrWidgetOptions } from './mr_widget_options';
+export { default as mrWidgetOptions } from './mr_widget_options.vue';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 345f9ac1b4b..f69fe03fcb3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,12 +1,13 @@
+<script>
+
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
-import Flash from '../flash';
+import createFlash from '../flash';
import {
WidgetHeader,
WidgetMergeHelp,
WidgetPipeline,
Deployment,
- WidgetMaintainerEdit,
WidgetRelatedLinks,
MergedState,
ClosedState,
@@ -40,10 +41,39 @@ import { setFavicon } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
name: 'MRWidget',
+ components: {
+ 'mr-widget-header': WidgetHeader,
+ 'mr-widget-merge-help': WidgetMergeHelp,
+ 'mr-widget-pipeline': WidgetPipeline,
+ Deployment,
+ 'mr-widget-related-links': WidgetRelatedLinks,
+ 'mr-widget-merged': MergedState,
+ 'mr-widget-closed': ClosedState,
+ 'mr-widget-merging': MergingState,
+ 'mr-widget-failed-to-merge': FailedToMerge,
+ 'mr-widget-wip': WorkInProgressState,
+ 'mr-widget-archived': ArchivedState,
+ 'mr-widget-conflicts': ConflictsState,
+ 'mr-widget-nothing-to-merge': NothingToMergeState,
+ 'mr-widget-not-allowed': NotAllowedState,
+ 'mr-widget-missing-branch': MissingBranchState,
+ 'mr-widget-ready-to-merge': ReadyToMergeState,
+ 'sha-mismatch': ShaMismatchState,
+ 'mr-widget-squash-before-merge': SquashBeforeMerge,
+ 'mr-widget-checking': CheckingState,
+ 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
+ 'mr-widget-pipeline-blocked': PipelineBlockedState,
+ 'mr-widget-pipeline-failed': PipelineFailedState,
+ 'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
+ 'mr-widget-auto-merge-failed': AutoMergeFailed,
+ 'mr-widget-rebase': RebaseState,
+ SourceBranchRemovalStatus,
+ },
props: {
mrData: {
type: Object,
required: false,
+ default: null,
},
},
data() {
@@ -72,6 +102,13 @@ export default {
(!this.mr.isNothingToMergeState && !this.mr.isMergedState);
},
},
+ created() {
+ this.initPolling();
+ this.bindEventHubListeners();
+ },
+ mounted() {
+ this.handleMounted();
+ },
methods: {
createService(store) {
const endpoints = {
@@ -99,7 +136,7 @@ export default {
cb.call(null, data);
}
})
- .catch(() => new Flash('Something went wrong. Please try again.'));
+ .catch(() => createFlash('Something went wrong. Please try again.'));
},
initPolling() {
this.pollingInterval = new SmartInterval({
@@ -134,7 +171,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line
+ createFlash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line
});
},
fetchActionsContent() {
@@ -147,7 +184,7 @@ export default {
Project.initRefSwitcher();
}
})
- .catch(() => new Flash('Something went wrong. Please try again.'));
+ .catch(() => createFlash('Something went wrong. Please try again.'));
},
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
@@ -202,76 +239,53 @@ export default {
this.initDeploymentsPolling();
},
},
- created() {
- this.initPolling();
- this.bindEventHubListeners();
- },
- mounted() {
- this.handleMounted();
- },
- components: {
- 'mr-widget-header': WidgetHeader,
- 'mr-widget-merge-help': WidgetMergeHelp,
- 'mr-widget-pipeline': WidgetPipeline,
- Deployment,
- 'mr-widget-maintainer-edit': WidgetMaintainerEdit,
- 'mr-widget-related-links': WidgetRelatedLinks,
- 'mr-widget-merged': MergedState,
- 'mr-widget-closed': ClosedState,
- 'mr-widget-merging': MergingState,
- 'mr-widget-failed-to-merge': FailedToMerge,
- 'mr-widget-wip': WorkInProgressState,
- 'mr-widget-archived': ArchivedState,
- 'mr-widget-conflicts': ConflictsState,
- 'mr-widget-nothing-to-merge': NothingToMergeState,
- 'mr-widget-not-allowed': NotAllowedState,
- 'mr-widget-missing-branch': MissingBranchState,
- 'mr-widget-ready-to-merge': ReadyToMergeState,
- 'mr-widget-sha-mismatch': ShaMismatchState,
- 'mr-widget-squash-before-merge': SquashBeforeMerge,
- 'mr-widget-checking': CheckingState,
- 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
- 'mr-widget-pipeline-blocked': PipelineBlockedState,
- 'mr-widget-pipeline-failed': PipelineFailedState,
- 'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
- 'mr-widget-auto-merge-failed': AutoMergeFailed,
- 'mr-widget-rebase': RebaseState,
- SourceBranchRemovalStatus,
- },
- template: `
- <div class="mr-state-widget prepend-top-default">
- <mr-widget-header :mr="mr" />
- <mr-widget-pipeline
- v-if="shouldRenderPipelines"
- :pipeline="mr.pipeline"
- :ci-status="mr.ciStatus"
- :has-ci="mr.hasCI"
- />
- <deployment
- v-for="deployment in mr.deployments"
- :key="deployment.id"
- :deployment="deployment"
+};
+</script>
+<template>
+ <div class="mr-state-widget prepend-top-default">
+ <mr-widget-header
+ :mr="mr"
+ />
+ <mr-widget-pipeline
+ v-if="shouldRenderPipelines"
+ :pipeline="mr.pipeline"
+ :ci-status="mr.ciStatus"
+ :has-ci="mr.hasCI"
+ />
+ <deployment
+ v-for="deployment in mr.deployments"
+ :key="deployment.id"
+ :deployment="deployment"
+ />
+ <div class="mr-widget-section">
+ <component
+ :is="componentName"
+ :mr="mr"
+ :service="service"
+ />
+
+ <section
+ v-if="mr.maintainerEditAllowed"
+ class="mr-info-list mr-links"
+ >
+ {{ s__("mrWidget|Allows edits from maintainers") }}
+ </section>
+
+ <mr-widget-related-links
+ v-if="shouldRenderRelatedLinks"
+ :state="mr.state"
+ :related-links="mr.relatedLinks"
+ />
+
+ <source-branch-removal-status
+ v-if="shouldRenderSourceBranchRemovalStatus"
/>
- <div class="mr-widget-section">
- <component
- :is="componentName"
- :mr="mr"
- :service="service" />
- <mr-widget-maintainer-edit
- :maintainerEditAllowed="mr.maintainerEditAllowed" />
- <mr-widget-related-links
- v-if="shouldRenderRelatedLinks"
- :state="mr.state"
- :related-links="mr.relatedLinks" />
- <source-branch-removal-status
- v-if="shouldRenderSourceBranchRemovalStatus"
- />
- </div>
- <div
- class="mr-widget-footer"
- v-if="shouldRenderMergeHelp">
- <mr-widget-merge-help />
- </div>
</div>
- `,
-};
+ <div
+ class="mr-widget-footer"
+ v-if="shouldRenderMergeHelp"
+ >
+ <mr-widget-merge-help />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index e832d94d32f..88c13a1f340 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -70,12 +70,14 @@
/>
</transition>
<transition name="fade">
- <span
- v-if="label"
- class="js-loading-button-label"
- >
- {{ label }}
- </span>
+ <slot>
+ <span
+ v-if="label"
+ class="js-loading-button-label"
+ >
+ {{ label }}
+ </span>
+ </slot>
</transition>
</button>
</template>
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 9ff24ebc127..0ea0b65b95f 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -210,3 +210,15 @@
margin-left: -$size;
}
}
+
+/*
+ * Mixin that fixes wrapping issues with long strings (e.g. URLs)
+ *
+ * Note: the width needs to be set for it to work in Firefox
+ */
+@mixin overflow-break-word {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+ max-width: 100%;
+}
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index bb185d2bd81..f80e9b1014b 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -67,7 +67,8 @@
padding: 8px 40px;
}
- .embed-toggle {
+ .embed-toggle,
+ .snippet-clipboard-btn {
height: 35px;
}
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index cd47f34d565..135cc5de0a5 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -284,6 +284,9 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
+ // as a fallback, hide overflow content so that dragging and dropping still works
+ overflow: hidden;
+
&:not(:last-child) {
margin-bottom: 5px;
}
@@ -310,14 +313,13 @@
}
.board-card-title {
+ @include overflow-break-word();
margin: 0 30px 0 0;
font-size: 1em;
line-height: inherit;
a {
color: $gl-text-color;
- word-wrap: break-word;
- word-break: break-word;
margin-right: 2px;
}
}
@@ -462,8 +464,8 @@
}
.issuable-header-text {
+ @include overflow-break-word();
padding-right: 35px;
- word-break: break-word;
> strong {
font-weight: $gl-font-weight-bold;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index f568c5d7036..d865dbc828f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -407,10 +407,6 @@ ul.notes {
.note-header {
display: flex;
justify-content: space-between;
-
- @include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
- flex-flow: row wrap;
- }
}
.note-header-info {
@@ -473,11 +469,6 @@ ul.notes {
margin-left: 10px;
color: $gray-darkest;
- @include notes-media('max', map-get($grid-breakpoints, lg) - 1) {
- float: none;
- margin-left: 0;
- }
-
.btn-group > .discussion-next-btn {
margin-left: -1px;
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2caffec66ac..2843d70c645 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -13,8 +13,7 @@ class ApplicationController < ActionController::Base
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
- before_action :enforce_terms!, if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms },
- unless: :peek_request?
+ before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
before_action :check_password_expiration
before_action :ldap_security_check
@@ -373,4 +372,10 @@ class ApplicationController < ActionController::Base
def peek_request?
request.path.start_with?('/-/peek')
end
+
+ def should_enforce_terms?
+ return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
+
+ !(peek_request? || devise_controller?)
+ end
end
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index 55011c89886..237c93daee8 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -2,6 +2,10 @@ module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment')
if attachment
redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" }
+ # By default, Rails will send uploads with an extension of .js with a
+ # content-type of text/javascript, which will trigger Rails'
+ # cross-origin JavaScript protection.
+ send_params[:content_type] = 'text/plain' if File.extname(attachment) == '.js'
send_params.merge!(filename: attachment, disposition: disposition)
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index bc13b8ad7ba..4d4c2af2415 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -8,19 +8,6 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
- #
- # This is a fix to make spinach feature tests passing:
- # Controller actions are returned from AbstractController::Base and methods of parent classes are
- # excluded in order to return only specific controller related methods.
- # That is ok for the app (no :create method in ancestors)
- # but fails for tests because there is a :create method on FactoryBot (one of the ancestors)
- #
- # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
- #
- def create
- super
- end
-
def delete_attachment
note.remove_attachment!
note.update_attribute(:attachment, nil)
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 0b1b46944aa..f7417a6a5aa 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -181,4 +181,8 @@ class Projects::PipelinesController < Projects::ApplicationController
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
end
+
+ def authorize_update_pipeline!
+ return access_denied! unless can?(current_user, :update_pipeline, @pipeline)
+ end
end
diff --git a/app/controllers/users/terms_controller.rb b/app/controllers/users/terms_controller.rb
index 95c5c3432d5..ab685b9106e 100644
--- a/app/controllers/users/terms_controller.rb
+++ b/app/controllers/users/terms_controller.rb
@@ -3,6 +3,10 @@ module Users
include InternalRedirect
skip_before_action :enforce_terms!
+ skip_before_action :check_password_expiration
+ skip_before_action :check_two_factor_requirement
+ skip_before_action :require_email
+
before_action :terms
layout 'terms'
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 16451993e93..7b076728685 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -24,6 +24,15 @@ module AutoDevopsHelper
end
end
+ def cluster_ingress_ip(project)
+ project
+ .cluster_ingresses
+ .where("external_ip is not null")
+ .limit(1)
+ .pluck(:external_ip)
+ .first
+ end
+
private
def missing_auto_devops_domain?(project)
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 079b3cd3aa0..cb6f709c604 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -41,7 +41,7 @@ module EventsHelper
key = key.to_s
active = 'active' if @event_filter.active?(key)
link_opts = {
- class: "event-filter-link has-tooltip",
+ class: "event-filter-link",
id: "#{key}_event_filter",
title: tooltip
}
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index eb81dc2de43..fa54eafd3a3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -257,6 +257,7 @@ module ProjectsHelper
if project.builds_enabled? && can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
+ nav_tabs << :operations
end
if project.external_issue_tracker
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index e803cd3a8d8..ce9373f5883 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -42,22 +42,11 @@ module UsersHelper
items << :sign_out if current_user
- # TODO: Remove these conditions when the permissions are prevented in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/45849
- terms_not_enforced = !Gitlab::CurrentSettings
- .current_application_settings
- .enforce_terms?
- required_terms_accepted = terms_not_enforced || current_user.terms_accepted?
+ return items if current_user&.required_terms_not_accepted?
- items << :help if required_terms_accepted
-
- if can?(current_user, :read_user, current_user) && required_terms_accepted
- items << :profile
- end
-
- if can?(current_user, :update_user, current_user) && required_terms_accepted
- items << :settings
- end
+ items << :help
+ items << :profile if can?(current_user, :read_user, current_user)
+ items << :settings if can?(current_user, :update_user, current_user)
items
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0b90834d415..1f49764e7cc 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -37,12 +37,16 @@ module Ci
delegate :id, to: :project, prefix: true
delegate :full_path, 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? }
validate :valid_commit_sha, unless: :importing?
+ # Replace validator below with
+ # `validates :source, presence: { unless: :importing? }, on: :create`
+ # when removing Gitlab.rails5? code.
+ validate :valid_source, unless: :importing?, on: :create
+
after_create :keep_around_commits, unless: :importing?
enum source: {
@@ -601,5 +605,11 @@ module Ci
project.repository.keep_around(self.sha)
project.repository.keep_around(self.before_sha)
end
+
+ def valid_source
+ if source.nil? || source == "unknown"
+ errors.add(:source, "invalid source")
+ end
+ end
end
end
diff --git a/app/models/list.rb b/app/models/list.rb
index 918275be142..5daf35ef845 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -31,7 +31,8 @@ class List < ActiveRecord::Base
if options.key?(:label)
json[:label] = label.as_json(
project: board.project,
- only: [:id, :title, :description, :color]
+ only: [:id, :title, :description, :color],
+ methods: [:text_color]
)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 32d34f5e9b8..534a0e630af 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -217,6 +217,7 @@ class Project < ActiveRecord::Base
has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
+ has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress'
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
diff --git a/app/models/user.rb b/app/models/user.rb
index d74d5aade5a..173ab38e20c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1097,8 +1097,11 @@ class User < ActiveRecord::Base
# <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92>
#
def increment_failed_attempts!
+ return if ::Gitlab::Database.read_only?
+
self.failed_attempts ||= 0
self.failed_attempts += 1
+
if attempts_exceeded?
lock_access! unless access_locked?
else
@@ -1197,6 +1200,11 @@ class User < ActiveRecord::Base
accepted_term_id.present?
end
+ def required_terms_not_accepted?
+ Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
+ !terms_accepted?
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 808a81cbbf9..8b65758f3e8 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -14,11 +14,20 @@ module Ci
@subject.triggered_by?(@user)
end
+ condition(:branch_allows_maintainer_push) do
+ @subject.project.branch_allows_maintainer_push?(@user, @subject.ref)
+ end
+
rule { protected_ref }.policy do
prevent :update_build
prevent :erase_build
end
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
+
+ rule { can?(:public_access) & branch_allows_maintainer_push }.policy do
+ enable :update_build
+ enable :update_commit_status
+ end
end
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index 6363c382ff8..540e4235299 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -4,8 +4,16 @@ module Ci
condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) }
+ condition(:branch_allows_maintainer_push) do
+ @subject.project.branch_allows_maintainer_push?(@user, @subject.ref)
+ end
+
rule { protected_ref }.prevent :update_pipeline
+ rule { can?(:public_access) & branch_allows_maintainer_push }.policy do
+ enable :update_pipeline
+ end
+
def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project)
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 64e550d19d0..1cf5515d9d7 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -1,22 +1,24 @@
class GlobalPolicy < BasePolicy
desc "User is blocked"
with_options scope: :user, score: 0
- condition(:blocked) { @user.blocked? }
+ condition(:blocked) { @user&.blocked? }
desc "User is an internal user"
with_options scope: :user, score: 0
- condition(:internal) { @user.internal? }
+ condition(:internal) { @user&.internal? }
desc "User's access has been locked"
with_options scope: :user, score: 0
- condition(:access_locked) { @user.access_locked? }
+ condition(:access_locked) { @user&.access_locked? }
- condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
+ condition(:can_create_fork, scope: :user) { @user && @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
+
+ condition(:required_terms_not_accepted, scope: :user, score: 0) do
+ @user&.required_terms_not_accepted?
+ end
rule { anonymous }.policy do
prevent :log_in
- prevent :access_api
- prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
@@ -38,6 +40,11 @@ class GlobalPolicy < BasePolicy
prevent :use_quick_actions
end
+ rule { required_terms_not_accepted }.policy do
+ prevent :access_api
+ prevent :access_git
+ end
+
rule { can_create_group }.policy do
enable :create_group
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 5759b1a376f..99a0d7118f2 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -76,7 +76,7 @@ class ProjectPolicy < BasePolicy
condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
desc "Has merge requests allowing pushes to user"
- condition(:has_merge_requests_allowing_pushes, scope: :subject) do
+ condition(:has_merge_requests_allowing_pushes) do
project.merge_requests_allowing_push_to_user(user).any?
end
@@ -354,9 +354,7 @@ class ProjectPolicy < BasePolicy
# to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
enable :create_build
- enable :update_build
enable :create_pipeline
- enable :update_pipeline
end
rule do
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 3e65d211c50..1765251c93d 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -13,7 +13,7 @@
= icon("chevron-up")
- else
= icon("chevron-down")
- Toggle discussion
+ = _('Toggle discussion')
= link_to_member(@project, discussion.author, avatar: false)
.inline.discussion-headline-light
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 146c183da73..dae3f5d6518 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -13,13 +13,13 @@
.nav-icon-container
= sprite_icon('project')
%span.nav-item-name
- Project
+ = _('Project')
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
= link_to project_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Overview') }
+ = _('Overview')
%li.divider.fly-out-top-item
= nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
@@ -40,45 +40,45 @@
.nav-icon-container
= sprite_icon('doc_text')
%span.nav-item-name
- Repository
+ = _('Repository')
%ul.sidebar-sub-level-items
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network), html_options: { class: "fly-out-top-item" } ) do
= link_to project_tree_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Repository') }
+ = _('Repository')
%li.divider.fly-out-top-item
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_tree_path(@project) do
- #{ _('Files') }
+ = _('Files')
= nav_link(controller: [:commit, :commits]) do
= link_to project_commits_path(@project, current_ref) do
- #{ _('Commits') }
+ = _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
= link_to project_branches_path(@project) do
- #{ _('Branches') }
+ = _('Branches')
= nav_link(controller: [:tags, :releases]) do
= link_to project_tags_path(@project) do
- #{ _('Tags') }
+ = _('Tags')
= nav_link(path: 'graphs#show') do
= link_to project_graph_path(@project, current_ref) do
- #{ _('Contributors') }
+ = _('Contributors')
= nav_link(controller: %w(network)) do
= link_to project_network_path(@project, current_ref) do
- #{ s_('ProjectNetworkGraph|Graph') }
+ = _('Graph')
= nav_link(controller: :compare) do
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
- #{ _('Compare') }
+ = _('Compare')
= nav_link(path: 'graphs#charts') do
= link_to charts_project_graph_path(@project, current_ref) do
- #{ _('Charts') }
+ = _('Charts')
- if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
@@ -86,7 +86,7 @@
.nav-icon-container
= sprite_icon('issues')
%span.nav-item-name
- Issues
+ = _('Issues')
- if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter
= number_with_delimiter(@project.open_issues_count)
@@ -95,7 +95,7 @@
= nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do
= link_to project_issues_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Issues') }
+ = _('Issues')
- if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter.fly-out-badge
= number_with_delimiter(@project.open_issues_count)
@@ -103,7 +103,7 @@
= nav_link(controller: :issues, action: :index) do
= link_to project_issues_path(@project), title: 'Issues' do
%span
- List
+ = _('List')
= nav_link(controller: :boards) do
= link_to project_boards_path(@project), title: boards_link_text do
@@ -113,12 +113,12 @@
= nav_link(controller: :labels) do
= link_to project_labels_path(@project), title: 'Labels' do
%span
- Labels
+ = _('Labels')
= nav_link(controller: :milestones) do
= link_to project_milestones_path(@project), title: 'Milestones' do
%span
- Milestones
+ = _('Milestones')
- if project_nav_tab? :external_issue_tracker
= nav_link do
- issue_tracker = @project.external_issue_tracker
@@ -139,54 +139,75 @@
.nav-icon-container
= sprite_icon('git-merge')
%span.nav-item-name
- Merge Requests
+ = _('Merge Requests')
%span.badge.badge-pill.count.merge_counter.js-merge-counter
= number_with_delimiter(@project.open_merge_requests_count)
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :merge_requests, html_options: { class: "fly-out-top-item" } ) do
= link_to project_merge_requests_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Merge Requests') }
+ = _('Merge Requests')
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge
= number_with_delimiter(@project.open_merge_requests_count)
- if project_nav_tab? :pipelines
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp]) do
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
.nav-icon-container
= sprite_icon('pipeline')
%span.nav-item-name
- CI / CD
+ = _('CI / CD')
%ul.sidebar-sub-level-items
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], html_options: { class: "fly-out-top-item" } ) do
= link_to project_pipelines_path(@project) do
%strong.fly-out-top-item-name
- #{ _('CI / CD') }
+ = _('CI / CD')
%li.divider.fly-out-top-item
- if project_nav_tab? :pipelines
= nav_link(path: ['pipelines#index', 'pipelines#show']) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
%span
- Pipelines
+ = _('Pipelines')
- if project_nav_tab? :builds
= nav_link(controller: [:jobs, :artifacts]) do
= link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
%span
- Jobs
+ = _('Jobs')
- if project_nav_tab? :pipelines
= nav_link(controller: :pipeline_schedules) do
= link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
%span
- Schedules
+ = _('Schedules')
+
+ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
+ = nav_link(path: 'pipelines#charts') do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ %span
+ = _('Charts')
+
+ - if project_nav_tab? :operations
+ = nav_link(controller: [:environments, :clusters, :user, :gcp]) do
+ = link_to project_environments_path(@project), class: 'shortcuts-operations' do
+ .nav-icon-container
+ = sprite_icon('cloud-gear')
+ %span.nav-item-name
+ = _('Operations')
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: [:environments, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do
+ = link_to project_environments_path(@project) do
+ %strong.fly-out-top-item-name
+ = _('Operations')
+ %li.divider.fly-out-top-item
- if project_nav_tab? :environments
= nav_link(controller: :environments) do
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
%span
- Environments
+ = _('Environments')
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
@@ -217,19 +238,18 @@
%span= _("Got it!")
= sprite_icon('thumb-up')
- - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
- = nav_link(path: 'pipelines#charts') do
- = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
- %span
- Charts
-
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
.nav-icon-container
= sprite_icon('disk')
%span.nav-item-name
- Registry
+ = _('Registry')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(controller: %w[projects/registry/repositories], html_options: { class: "fly-out-top-item" } ) do
+ = link_to project_container_registry_index_path(@project) do
+ %strong.fly-out-top-item-name
+ = _('Registry')
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
@@ -237,12 +257,12 @@
.nav-icon-container
= sprite_icon('book')
%span.nav-item-name
- Wiki
+ = _('Wiki')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do
= link_to get_project_wiki_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Wiki') }
+ = _('Wiki')
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
@@ -250,12 +270,12 @@
.nav-icon-container
= sprite_icon('snippet')
%span.nav-item-name
- Snippets
+ = _('Snippets')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :snippets, html_options: { class: "fly-out-top-item" } ) do
= link_to project_snippets_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Snippets') }
+ = _('Snippets')
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show]) do
@@ -263,7 +283,7 @@
.nav-icon-container
= sprite_icon('settings')
%span.nav-item-name.qa-settings-item
- Settings
+ = _('Settings')
%ul.sidebar-sub-level-items
- can_edit = can?(current_user, :admin_project, @project)
@@ -271,16 +291,16 @@
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show], html_options: { class: "fly-out-top-item" } ) do
= link_to edit_project_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Settings') }
+ = _('Settings')
%li.divider.fly-out-top-item
= nav_link(path: %w[projects#edit]) do
= link_to edit_project_path(@project), title: 'General' do
%span
- General
+ = _('General')
= nav_link(controller: :project_members) do
= link_to project_project_members_path(@project), title: 'Members' do
%span
- Members
+ = _('Members')
- if can_edit
= nav_link(controller: :badges) do
= link_to project_settings_badges_path(@project), title: _('Badges') do
@@ -290,21 +310,21 @@
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
- Integrations
+ = _('Integrations')
= nav_link(controller: :repository) do
= link_to project_settings_repository_path(@project), title: 'Repository' do
%span
- Repository
+ = _('Repository')
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
= link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
%span
- CI / CD
+ = _('CI / CD')
- if @project.pages_available?
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do
%span
- Pages
+ = _('Pages')
- else
= nav_link(controller: :project_members) do
@@ -312,12 +332,12 @@
.nav-icon-container
= sprite_icon('users')
%span.nav-item-name
- Members
+ = _('Members')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: %w[members#show], html_options: { class: "fly-out-top-item" } ) do
= link_to project_project_members_path(@project) do
%strong.fly-out-top-item-name
- #{ _('Members') }
+ = _('Members')
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 71e77dae69e..8cb6c446e18 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -35,7 +35,9 @@
= _('Domain')
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
.help-block
- = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.')
+ = s_('CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages.')
+ - if cluster_ingress_ip = cluster_ingress_ip(@project)
+ = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-success prepend-top-15"
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 9d3d4072027..35c7dc2984a 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -28,9 +28,16 @@
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project)
- = link_to project_wiki_path(@project, @page), data: { confirm: s_("WikiPageConfirmDelete|Are you sure you want to delete this page?")}, method: :delete, class: "btn btn-danger" do
- = _("Delete")
+ %button.btn.btn-danger{ data: { toggle: 'modal',
+ target: '#delete-wiki-modal',
+ delete_wiki_url: project_wiki_path(@project, @page),
+ page_title: @page.title.capitalize },
+ id: 'delete-wiki-button',
+ type: 'button' }
+ = _('Delete')
= render 'form'
= render 'sidebar'
+
+#delete-wiki-modal.modal.fade
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index f5070c3867f..67476a3f573 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -15,7 +15,7 @@
":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" },
class: "badge color-label title board-title-text",
- ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.text_color ? list.label.text_color : \"#2e2e2e\") }" }
+ ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" }
{{ list.title }}
- if can?(current_user, :admin_list, current_board_parent)
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 192d9ffb6f1..ddf54866b99 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -45,6 +45,6 @@
%strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
.input-group-btn
- %button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' }
+ %button.js-clipboard-btn.snippet-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '.js-snippet-url-area' }
= sprite_icon('duplicate', size: 16)
.clearfix