Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js19
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js23
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js22
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue60
-rw-r--r--app/assets/javascripts/blob/pdf/index.js8
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js2
-rw-r--r--app/assets/javascripts/notes/constants.js1
-rw-r--r--app/assets/javascripts/notes/mixins/description_version_history.js2
-rw-r--r--app/assets/javascripts/notes/stores/actions.js53
-rw-r--r--app/assets/javascripts/notes/stores/collapse_utils.js8
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js2
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js8
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js21
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue24
-rw-r--r--app/assets/stylesheets/pages/notes.scss13
-rw-r--r--app/models/commit_status_enums.rb1
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/environment.rb1
-rw-r--r--app/models/member.rb1
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/project_ci_cd_setting.rb6
-rw-r--r--app/presenters/commit_status_presenter.rb1
-rw-r--r--app/services/deployments/older_deployments_drop_service.rb31
-rw-r--r--app/views/help/_shortcuts.html.haml1
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml4
-rw-r--r--app/views/projects/pipelines/show.html.haml3
-rw-r--r--app/workers/all_queues.yml6
-rw-r--r--app/workers/deployments/forward_deployment_worker.rb14
-rw-r--r--changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml5
-rw-r--r--changelogs/unreleased/25276-allow-forward-deploys-only.yml5
-rw-r--r--changelogs/unreleased/psi-mermaid-details.yml5
-rw-r--r--db/migrate/20200124143014_add_restrict_deployment_order_to_project_ci_cd_settings.rb9
-rw-r--r--db/post_migrate/20200210062432_schedule_link_lfs_objects.rb23
-rw-r--r--db/schema.rb1
-rw-r--r--doc/user/shortcuts.md5
-rw-r--r--lib/gitlab/background_migration/link_lfs_objects.rb19
-rw-r--r--lib/gitlab/ci/status/build/failed.rb1
-rw-r--r--lib/quality/kubernetes_client.rb2
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/factories/projects.rb1
-rw-r--r--spec/features/markdown/mermaid_spec.rb30
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb53
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb66
-rw-r--r--spec/lib/quality/kubernetes_client_spec.rb2
-rw-r--r--spec/migrations/schedule_link_lfs_objects_spec.rb82
-rw-r--r--spec/models/deployment_spec.rb39
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb6
-rw-r--r--spec/services/deployments/older_deployments_drop_service_spec.rb78
49 files changed, 583 insertions, 216 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index c86a4c9f178..3856832de90 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -1,4 +1,5 @@
import flash from '~/flash';
+import $ from 'jquery';
import { sprintf, __ } from '../../locale';
// Renders diagrams and flowcharts from text using Mermaid in any element with the
@@ -18,7 +19,7 @@ import { sprintf, __ } from '../../locale';
// This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000;
-export default function renderMermaid($els) {
+function renderMermaids($els) {
if (!$els.length) return;
// A diagram may have been truncated in search results which will cause errors, so abort the render.
@@ -95,3 +96,19 @@ export default function renderMermaid($els) {
flash(`Can't load mermaid module: ${err}`);
});
}
+
+export default function renderMermaid($els) {
+ if (!$els.length) return;
+
+ const visibleMermaids = $els.filter(function filter() {
+ return $(this).closest('details').length === 0;
+ });
+
+ renderMermaids(visibleMermaids);
+
+ $els.closest('details').one('toggle', function toggle() {
+ if (this.open) {
+ renderMermaids($(this).find('.js-render-mermaid'));
+ }
+ });
+}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 66cb9fd7672..85636f3e5d2 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -1,6 +1,9 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
+import Vue from 'vue';
+import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
+import ShortcutsToggle from './shortcuts_toggle.vue';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
@@ -15,6 +18,15 @@ Mousetrap.stopCallback = (e, element, combo) => {
return defaultStopCallback(e, element, combo);
};
+function initToggleButton() {
+ return new Vue({
+ el: document.querySelector('.js-toggle-shortcuts'),
+ render(createElement) {
+ return createElement(ShortcutsToggle);
+ },
+ });
+}
+
export default class Shortcuts {
constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
@@ -48,6 +60,14 @@ export default class Shortcuts {
$(this).remove();
e.preventDefault();
});
+
+ $('.js-shortcuts-modal-trigger')
+ .off('click')
+ .on('click', this.onToggleHelp);
+
+ if (shouldDisableShortcuts()) {
+ disableShortcuts();
+ }
}
onToggleHelp(e) {
@@ -104,7 +124,8 @@ export default class Shortcuts {
}
return $('.js-more-help-button').remove();
- });
+ })
+ .then(initToggleButton);
}
focusFilter(e) {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js
new file mode 100644
index 00000000000..66aa1b752ae
--- /dev/null
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js
@@ -0,0 +1,22 @@
+import Mousetrap from 'mousetrap';
+import 'mousetrap/plugins/pause/mousetrap-pause';
+
+const shorcutsDisabledKey = 'shortcutsDisabled';
+
+export const shouldDisableShortcuts = () => {
+ try {
+ return localStorage.getItem(shorcutsDisabledKey) === 'true';
+ } catch (e) {
+ return false;
+ }
+};
+
+export function enableShortcuts() {
+ localStorage.setItem(shorcutsDisabledKey, false);
+ Mousetrap.unpause();
+}
+
+export function disableShortcuts() {
+ localStorage.setItem(shorcutsDisabledKey, true);
+ Mousetrap.pause();
+}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
new file mode 100644
index 00000000000..a53b1b06be9
--- /dev/null
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
@@ -0,0 +1,60 @@
+<script>
+import { GlToggle, GlSprintf } from '@gitlab/ui';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
+
+export default {
+ components: {
+ GlSprintf,
+ GlToggle,
+ },
+ data() {
+ return {
+ localStorageUsable: AccessorUtilities.isLocalStorageAccessSafe(),
+ shortcutsEnabled: !shouldDisableShortcuts(),
+ };
+ },
+ methods: {
+ onChange(value) {
+ this.shortcutsEnabled = value;
+ if (value) {
+ enableShortcuts();
+ } else {
+ disableShortcuts();
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="localStorageUsable" class="d-inline-flex align-items-center js-toggle-shortcuts">
+ <gl-toggle
+ v-model="shortcutsEnabled"
+ aria-describedby="shortcutsToggle"
+ class="prepend-left-10 mb-0"
+ label-position="right"
+ @change="onChange"
+ >
+ <template #labelOn>
+ <gl-sprintf
+ :message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled')"
+ >
+ <template #screenreaderOnly="{ content }">
+ <span class="sr-only">{{ content }}</span>
+ </template>
+ </gl-sprintf>
+ </template>
+ <template #labelOff>
+ <gl-sprintf
+ :message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled')"
+ >
+ <template #screenreaderOnly="{ content }">
+ <span class="sr-only">{{ content }}</span>
+ </template>
+ </gl-sprintf>
+ </template>
+ </gl-toggle>
+ <div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js
index e9a0b3979e6..19778d07983 100644
--- a/app/assets/javascripts/blob/pdf/index.js
+++ b/app/assets/javascripts/blob/pdf/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import pdfLab from '../../pdf/index.vue';
+import { GlLoadingIcon } from '@gitlab/ui';
export default () => {
const el = document.getElementById('js-pdf-viewer');
@@ -8,6 +9,7 @@ export default () => {
el,
components: {
pdfLab,
+ GlLoadingIcon,
},
data() {
return {
@@ -32,11 +34,7 @@ export default () => {
<div
class="text-center loading"
v-if="loading && !error">
- <i
- class="fa fa-spinner fa-spin"
- aria-hidden="true"
- aria-label="PDF loading">
- </i>
+ <gl-loading-icon class="mt-5" size="lg"/>
</div>
<pdf-lab
v-if="!loadError"
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index 72565c2ca13..2b6e1f25dc6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -12,7 +12,7 @@ export default class FilteredSearchDropdown {
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
- <i class="fa fa-spinner fa-spin"></i>
+ <span class="spinner"></span>
</div>`;
this.bindEvents();
}
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 68c117183a1..e9a81bc9553 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -18,6 +18,7 @@ export const HISTORY_ONLY_FILTER_VALUE = 2;
export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const DISCUSSION_TAB_LABEL = 'show';
export const NOTE_UNDERSCORE = 'note_';
+export const TIME_DIFFERENCE_VALUE = 10;
export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE,
diff --git a/app/assets/javascripts/notes/mixins/description_version_history.js b/app/assets/javascripts/notes/mixins/description_version_history.js
index 12d80f3faa2..66e6685cfd8 100644
--- a/app/assets/javascripts/notes/mixins/description_version_history.js
+++ b/app/assets/javascripts/notes/mixins/description_version_history.js
@@ -3,10 +3,12 @@
export default {
computed: {
canSeeDescriptionVersion() {},
+ canDeleteDescriptionVersion() {},
shouldShowDescriptionVersion() {},
descriptionVersionToggleIcon() {},
},
methods: {
toggleDescriptionVersion() {},
+ deleteDescriptionVersion() {},
},
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index f3dc6187c3f..594e3a14d56 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -491,23 +491,66 @@ export const convertToDiscussion = ({ commit }, noteId) =>
export const removeConvertedDiscussion = ({ commit }, noteId) =>
commit(types.REMOVE_CONVERTED_DISCUSSION, noteId);
-export const fetchDescriptionVersion = (_, { endpoint, startingVersion }) => {
+export const setCurrentDiscussionId = ({ commit }, discussionId) =>
+ commit(types.SET_CURRENT_DISCUSSION_ID, discussionId);
+
+export const fetchDescriptionVersion = ({ dispatch }, { endpoint, startingVersion }) => {
let requestUrl = endpoint;
if (startingVersion) {
requestUrl = mergeUrlParams({ start_version_id: startingVersion }, requestUrl);
}
+ dispatch('requestDescriptionVersion');
return axios
.get(requestUrl)
- .then(res => res.data)
- .catch(() => {
+ .then(res => {
+ dispatch('receiveDescriptionVersion', res.data);
+ })
+ .catch(error => {
+ dispatch('receiveDescriptionVersionError', error);
Flash(__('Something went wrong while fetching description changes. Please try again.'));
});
};
-export const setCurrentDiscussionId = ({ commit }, discussionId) =>
- commit(types.SET_CURRENT_DISCUSSION_ID, discussionId);
+export const requestDescriptionVersion = ({ commit }) => {
+ commit(types.REQUEST_DESCRIPTION_VERSION);
+};
+export const receiveDescriptionVersion = ({ commit }, descriptionVersion) => {
+ commit(types.RECEIVE_DESCRIPTION_VERSION, descriptionVersion);
+};
+export const receiveDescriptionVersionError = ({ commit }, error) => {
+ commit(types.RECEIVE_DESCRIPTION_VERSION_ERROR, error);
+};
+
+export const softDeleteDescriptionVersion = ({ dispatch }, { endpoint, startingVersion }) => {
+ let requestUrl = endpoint;
+
+ if (startingVersion) {
+ requestUrl = mergeUrlParams({ start_version_id: startingVersion }, requestUrl);
+ }
+ dispatch('requestDeleteDescriptionVersion');
+
+ return axios
+ .delete(requestUrl)
+ .then(() => {
+ dispatch('receiveDeleteDescriptionVersion');
+ })
+ .catch(error => {
+ dispatch('receiveDeleteDescriptionVersionError', error);
+ Flash(__('Something went wrong while deleting description changes. Please try again.'));
+ });
+};
+
+export const requestDeleteDescriptionVersion = ({ commit }) => {
+ commit(types.REQUEST_DELETE_DESCRIPTION_VERSION);
+};
+export const receiveDeleteDescriptionVersion = ({ commit }) => {
+ commit(types.RECEIVE_DELETE_DESCRIPTION_VERSION, __('Deleted'));
+};
+export const receiveDeleteDescriptionVersionError = ({ commit }, error) => {
+ commit(types.RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR, error);
+};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/collapse_utils.js b/app/assets/javascripts/notes/stores/collapse_utils.js
index 3cdcc7a05b8..d94fc626a3f 100644
--- a/app/assets/javascripts/notes/stores/collapse_utils.js
+++ b/app/assets/javascripts/notes/stores/collapse_utils.js
@@ -1,4 +1,4 @@
-import { DESCRIPTION_TYPE } from '../constants';
+import { DESCRIPTION_TYPE, TIME_DIFFERENCE_VALUE } from '../constants';
/**
* Checks the time difference between two notes from their 'created_at' dates
@@ -45,7 +45,11 @@ export const collapseSystemNotes = notes => {
const timeDifferenceMinutes = getTimeDifferenceMinutes(lastDescriptionSystemNote, note);
// are they less than 10 minutes apart from the same user?
- if (timeDifferenceMinutes > 10 || note.author.id !== lastDescriptionSystemNote.author.id) {
+ if (
+ timeDifferenceMinutes > TIME_DIFFERENCE_VALUE ||
+ note.author.id !== lastDescriptionSystemNote.author.id ||
+ lastDescriptionSystemNote.description_version_deleted
+ ) {
// update the previous system note
lastDescriptionSystemNote = note;
lastDescriptionSystemNoteIndex = acc.length;
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 771b80108b8..0e991f2f4f0 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -14,6 +14,7 @@ export default () => ({
isToggleStateButtonLoading: false,
isNotesFetched: false,
isLoading: true,
+ isLoadingDescriptionVersion: false,
// holds endpoints and permissions provided through haml
notesData: {
@@ -27,6 +28,7 @@ export default () => ({
commentsDisabled: false,
resolvableDiscussionsCount: 0,
unresolvedDiscussionsCount: 0,
+ descriptionVersion: null,
},
actions,
getters,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 8eb426d3f9b..6554aee0d5b 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -31,3 +31,11 @@ export const SET_CURRENT_DISCUSSION_ID = 'SET_CURRENT_DISCUSSION_ID';
export const CLOSE_ISSUE = 'CLOSE_ISSUE';
export const REOPEN_ISSUE = 'REOPEN_ISSUE';
export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
+
+// Description version
+export const REQUEST_DESCRIPTION_VERSION = 'REQUEST_DESCRIPTION_VERSION';
+export const RECEIVE_DESCRIPTION_VERSION = 'RECEIVE_DESCRIPTION_VERSION';
+export const RECEIVE_DESCRIPTION_VERSION_ERROR = 'RECEIVE_DESCRIPTION_VERSION_ERROR';
+export const REQUEST_DELETE_DESCRIPTION_VERSION = 'REQUEST_DELETE_DESCRIPTION_VERSION';
+export const RECEIVE_DELETE_DESCRIPTION_VERSION = 'RECEIVE_DELETE_DESCRIPTION_VERSION';
+export const RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR = 'RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 71091d26b85..d32a88e4c71 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -284,4 +284,25 @@ export default {
[types.SET_CURRENT_DISCUSSION_ID](state, discussionId) {
state.currentDiscussionId = discussionId;
},
+
+ [types.REQUEST_DESCRIPTION_VERSION](state) {
+ state.isLoadingDescriptionVersion = true;
+ },
+ [types.RECEIVE_DESCRIPTION_VERSION](state, descriptionVersion) {
+ state.isLoadingDescriptionVersion = false;
+ state.descriptionVersion = descriptionVersion;
+ },
+ [types.RECEIVE_DESCRIPTION_VERSION_ERROR](state) {
+ state.isLoadingDescriptionVersion = false;
+ },
+ [types.REQUEST_DELETE_DESCRIPTION_VERSION](state) {
+ state.isLoadingDescriptionVersion = true;
+ },
+ [types.RECEIVE_DELETE_DESCRIPTION_VERSION](state, descriptionVersion) {
+ state.isLoadingDescriptionVersion = false;
+ state.descriptionVersion = descriptionVersion;
+ },
+ [types.RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR](state) {
+ state.isLoadingDescriptionVersion = false;
+ },
};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 7e14b810c13..d9192d3d76b 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -132,7 +132,7 @@ export default () => {
});
axios
- .get(dataset.testReportEndpoint)
+ .get(dataset.testReportsCountEndpoint)
.then(({ data }) => {
document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
})
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 15ca64ba297..0c4d75fb0ad 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -17,11 +17,12 @@
* />
*/
import $ from 'jquery';
-import { mapGetters, mapActions } from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { mapGetters, mapActions, mapState } from 'vuex';
+import { GlButton, GlSkeletonLoading, GlTooltipDirective } from '@gitlab/ui';
import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
import noteHeader from '~/notes/components/note_header.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TimelineEntryItem from './timeline_entry_item.vue';
import { spriteIcon } from '../../../lib/utils/common_utils';
import initMRPopovers from '~/mr_popover/';
@@ -34,9 +35,13 @@ export default {
Icon,
noteHeader,
TimelineEntryItem,
+ GlButton,
GlSkeletonLoading,
},
- mixins: [descriptionVersionHistoryMixin],
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [descriptionVersionHistoryMixin, glFeatureFlagsMixin()],
props: {
note: {
type: Object,
@@ -50,6 +55,7 @@ export default {
},
computed: {
...mapGetters(['targetNoteHash']),
+ ...mapState(['descriptionVersion', 'isLoadingDescriptionVersion']),
noteAnchorId() {
return `note_${this.note.id}`;
},
@@ -80,7 +86,7 @@ export default {
initMRPopovers(this.$el.querySelectorAll('.gfm-merge_request'));
},
methods: {
- ...mapActions(['fetchDescriptionVersion']),
+ ...mapActions(['fetchDescriptionVersion', 'softDeleteDescriptionVersion']),
},
};
</script>
@@ -122,6 +128,16 @@ export default {
<gl-skeleton-loading />
</pre>
<pre v-else class="wrapper mt-2" v-html="descriptionVersion"></pre>
+ <gl-button
+ v-if="canDeleteDescriptionVersion"
+ ref="deleteDescriptionVersionButton"
+ v-gl-tooltip
+ :title="__('Remove description history')"
+ class="btn-transparent delete-description-history"
+ @click="deleteDescriptionVersion"
+ >
+ <icon name="remove" />
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 1da9f691639..1a06ae1ed41 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -311,13 +311,18 @@ $note-form-margin-left: 72px;
overflow: hidden;
.description-version {
+ position: relative;
+
+ .btn.delete-description-history {
+ position: absolute;
+ top: 18px;
+ right: 0;
+ }
+
pre {
max-height: $dropdown-max-height-lg;
white-space: pre-wrap;
-
- &.loading-state {
- height: 94px;
- }
+ padding-right: 30px;
}
}
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index dcf23d112d6..caebff91022 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -18,6 +18,7 @@ module CommitStatusEnums
unmet_prerequisites: 10,
scheduler_failure: 11,
data_integrity_failure: 12,
+ forward_deployment_failure: 13,
insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003,
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 68f50b13a07..fe42fb93633 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -41,6 +41,9 @@ class Deployment < ApplicationRecord
scope :visible, -> { where(status: %i[running success failed canceled]) }
scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success }
+ scope :active, -> { where(status: %i[created running]) }
+ scope :older_than, -> (deployment) { where('id < ?', deployment.id) }
+ scope :with_deployable, -> { includes(:deployable).where('deployable_id IS NOT NULL') }
state_machine :status, initial: :created do
event :run do
@@ -74,6 +77,14 @@ class Deployment < ApplicationRecord
Deployments::FinishedWorker.perform_async(id)
end
end
+
+ after_transition any => :running do |deployment|
+ next unless deployment.project.forward_deployment_enabled?
+
+ deployment.run_after_commit do
+ Deployments::ForwardDeploymentWorker.perform_async(id)
+ end
+ end
end
enum status: {
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 4635b05fcc7..bb41c4a066e 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -12,6 +12,7 @@ class Environment < ApplicationRecord
has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
+ has_many :active_deployments, -> { active }, class_name: 'Deployment'
has_many :prometheus_alerts, inverse_of: :environment
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
diff --git a/app/models/member.rb b/app/models/member.rb
index 57924161b63..a26a0615a6e 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -75,6 +75,7 @@ class Member < ApplicationRecord
scope :reporters, -> { active.where(access_level: REPORTER) }
scope :developers, -> { active.where(access_level: DEVELOPER) }
scope :maintainers, -> { active.where(access_level: MAINTAINER) }
+ scope :non_guests, -> { where('members.access_level > ?', GUEST) }
scope :masters, -> { maintainers } # @deprecated
scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
diff --git a/app/models/project.rb b/app/models/project.rb
index 1e27ce9f344..e16bd568153 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -343,6 +343,7 @@ class Project < ApplicationRecord
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
+ delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings
# Validations
validates :creator, presence: true, on: :create
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index a495d34c07c..b26a3025b61 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -18,6 +18,8 @@ class ProjectCiCdSetting < ApplicationRecord
},
allow_nil: true
+ default_value_for :forward_deployment_enabled, true
+
def self.available?
@available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
@@ -28,6 +30,10 @@ class ProjectCiCdSetting < ApplicationRecord
super
end
+ def forward_deployment_enabled?
+ super && ::Feature.enabled?(:forward_deployment_enabled, project)
+ end
+
private
def set_default_git_depth
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index ed76f95ac62..258852c77c6 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -14,6 +14,7 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
unmet_prerequisites: 'The job failed to complete prerequisite tasks',
scheduler_failure: 'The scheduler failed to assign job to the runner, please try again or contact system administrator',
data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator',
+ forward_deployment_failure: 'The deployment job is older than the previously succeeded deployment job, and therefore cannot be run',
invalid_bridge_trigger: 'This job could not be executed because downstream pipeline trigger definition is invalid',
downstream_bridge_project_not_found: 'This job could not be executed because downstream bridge project could not be found',
insufficient_bridge_permissions: 'This job could not be executed because of insufficient permissions to create a downstream pipeline',
diff --git a/app/services/deployments/older_deployments_drop_service.rb b/app/services/deployments/older_deployments_drop_service.rb
new file mode 100644
index 00000000000..122f8ac89ed
--- /dev/null
+++ b/app/services/deployments/older_deployments_drop_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Deployments
+ class OlderDeploymentsDropService
+ attr_reader :deployment
+
+ def initialize(deployment_id)
+ @deployment = Deployment.find_by_id(deployment_id)
+ end
+
+ def execute
+ return unless @deployment&.running?
+
+ older_deployments.find_each do |older_deployment|
+ older_deployment.deployable&.drop!(:forward_deployment_failure)
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e, subject_id: @deployment.id, deployment_id: older_deployment.id)
+ end
+ end
+
+ private
+
+ def older_deployments
+ @deployment
+ .environment
+ .active_deployments
+ .older_than(@deployment)
+ .with_deployable
+ end
+ end
+end
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 5f8f2333e40..4b9304cfdb9 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -6,6 +6,7 @@
= _('Keyboard Shortcuts')
%small
= link_to _('(Show all)'), '#', class: 'js-more-help-button'
+ .js-toggle-shortcuts
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 93854c212df..a003d6f8903 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -4,6 +4,10 @@
= link_to _("Help"), help_path
%li
= link_to _("Support"), support_url
+ %li
+ %button.js-shortcuts-modal-trigger{ type: "button" }
+ = _("Keyboard shortcuts")
+ %span.text-secondary.float-right{ "aria-hidden": true }= '?'.html_safe
= render_if_exists "shared/learn_gitlab_menu_item"
%li.divider
%li
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index f0b3ab24ea0..f39968eecef 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -21,4 +21,5 @@
= render "projects/pipelines/with_tabs", pipeline: @pipeline
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json),
- test_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json) } }
+ test_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json),
+ test_reports_count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 5ff1a331b09..f6daab73689 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -225,6 +225,12 @@
:latency_sensitive:
:resource_boundary: :cpu
:weight: 3
+- :name: deployment:deployments_forward_deployment
+ :feature_category: :continuous_delivery
+ :has_external_dependencies:
+ :latency_sensitive:
+ :resource_boundary: :unknown
+ :weight: 3
- :name: deployment:deployments_success
:feature_category: :continuous_delivery
:has_external_dependencies:
diff --git a/app/workers/deployments/forward_deployment_worker.rb b/app/workers/deployments/forward_deployment_worker.rb
new file mode 100644
index 00000000000..a25b8ca0478
--- /dev/null
+++ b/app/workers/deployments/forward_deployment_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Deployments
+ class ForwardDeploymentWorker
+ include ApplicationWorker
+
+ queue_namespace :deployment
+ feature_category :continuous_delivery
+
+ def perform(deployment_id)
+ Deployments::OlderDeploymentsDropService.new(deployment_id).execute
+ end
+ end
+end
diff --git a/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml b/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml
new file mode 100644
index 00000000000..45957b4acb2
--- /dev/null
+++ b/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Allow keyboard shortcuts to be disabled
+merge_request: 18782
+author:
+type: added
diff --git a/changelogs/unreleased/25276-allow-forward-deploys-only.yml b/changelogs/unreleased/25276-allow-forward-deploys-only.yml
new file mode 100644
index 00000000000..a3d017880eb
--- /dev/null
+++ b/changelogs/unreleased/25276-allow-forward-deploys-only.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to deploy only forward deployments
+merge_request: 22959
+author:
+type: changed
diff --git a/changelogs/unreleased/psi-mermaid-details.yml b/changelogs/unreleased/psi-mermaid-details.yml
new file mode 100644
index 00000000000..b5c8f7481bd
--- /dev/null
+++ b/changelogs/unreleased/psi-mermaid-details.yml
@@ -0,0 +1,5 @@
+---
+title: Correctly render mermaid digrams inside details blocks
+merge_request: 23662
+author:
+type: fixed
diff --git a/db/migrate/20200124143014_add_restrict_deployment_order_to_project_ci_cd_settings.rb b/db/migrate/20200124143014_add_restrict_deployment_order_to_project_ci_cd_settings.rb
new file mode 100644
index 00000000000..6a222ce8d51
--- /dev/null
+++ b/db/migrate/20200124143014_add_restrict_deployment_order_to_project_ci_cd_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRestrictDeploymentOrderToProjectCiCdSettings < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :forward_deployment_enabled, :boolean
+ end
+end
diff --git a/db/post_migrate/20200210062432_schedule_link_lfs_objects.rb b/db/post_migrate/20200210062432_schedule_link_lfs_objects.rb
index c955639c259..b401065dd94 100644
--- a/db/post_migrate/20200210062432_schedule_link_lfs_objects.rb
+++ b/db/post_migrate/20200210062432_schedule_link_lfs_objects.rb
@@ -4,32 +4,11 @@ class ScheduleLinkLfsObjects < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
- MIGRATION = 'LinkLfsObjects'
- BATCH_SIZE = 1_000
disable_ddl_transaction!
- class Project < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'projects'
- end
-
def up
- fork_network_members =
- Gitlab::BackgroundMigration::LinkLfsObjects::ForkNetworkMember
- .select(1)
- .with_non_existing_lfs_objects
- .where('fork_network_members.project_id = projects.id')
-
- forks = Project.where('EXISTS (?)', fork_network_members)
-
- queue_background_migration_jobs_by_range_at_intervals(
- forks,
- MIGRATION,
- BackgroundMigrationWorker.minimum_interval,
- batch_size: BATCH_SIZE
- )
+ # no-op as background migration being schedule times out in some instances
end
def down
diff --git a/db/schema.rb b/db/schema.rb
index f95662582ec..7b27b2524ce 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3166,6 +3166,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_204737) do
t.boolean "group_runners_enabled", default: true, null: false
t.boolean "merge_pipelines_enabled"
t.integer "default_git_depth"
+ t.boolean "forward_deployment_enabled"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true
end
diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md
index e7cb5e59be8..1df1a8a8ba6 100644
--- a/doc/user/shortcuts.md
+++ b/doc/user/shortcuts.md
@@ -6,7 +6,10 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/shortcuts.html'
# GitLab keyboard shortcuts
GitLab has many useful keyboard shortcuts to make it easier to access different features.
-You can see the quick reference sheet within GitLab itself with <kbd>Shift</kbd> + <kbd>?</kbd>.
+You can see a modal listing keyboard shortcuts within GitLab itself by pressing <kbd>?</kbd>,
+or clicking **Keyboard shortcuts** in the Help menu at the top right.
+From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/issues/22113),
+keyboard shortcuts can be disabled using the **Enable**/**Disable** toggle in this modal window.
The [Global Shortcuts](#global-shortcuts) work from any area of GitLab, but you must
be in specific pages for the other shortcuts to be available, as explained in each
diff --git a/lib/gitlab/background_migration/link_lfs_objects.rb b/lib/gitlab/background_migration/link_lfs_objects.rb
index 014bebc4258..69c03f617bf 100644
--- a/lib/gitlab/background_migration/link_lfs_objects.rb
+++ b/lib/gitlab/background_migration/link_lfs_objects.rb
@@ -24,24 +24,7 @@ module Gitlab
end
def perform(start_id, end_id)
- select_query =
- ForkNetworkMember
- .select('lop.lfs_object_id, fork_network_members.project_id')
- .with_non_existing_lfs_objects
- .where(project_id: start_id..end_id)
-
- return if select_query.empty?
-
- execute <<-SQL
- INSERT INTO lfs_objects_projects (lfs_object_id, project_id)
- #{select_query.to_sql}
- SQL
- end
-
- private
-
- def execute(sql)
- ::ActiveRecord::Base.connection.execute(sql)
+ # no-op as some queries times out
end
end
end
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 0915a98a0fa..b0b01538a30 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -19,6 +19,7 @@ module Gitlab
unmet_prerequisites: 'unmet prerequisites',
scheduler_failure: 'scheduler failure',
data_integrity_failure: 'data integrity failure',
+ forward_deployment_failure: 'forward deployment failure',
invalid_bridge_trigger: 'downstream pipeline trigger definition is invalid',
downstream_bridge_project_not_found: 'downstream project could not be found',
insufficient_bridge_permissions: 'no permissions to trigger downstream pipeline',
diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb
index db21c0b013b..453b9d21adb 100644
--- a/lib/quality/kubernetes_client.rb
+++ b/lib/quality/kubernetes_client.rb
@@ -63,7 +63,7 @@ module Quality
'get',
RESOURCE_LIST,
%(--namespace "#{namespace}"),
- '-o custom-columns=NAME:.metadata.name'
+ '-o name'
]
run_command(command).lines.map(&:strip)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c7590ccb1fb..b8ed2c62e0c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -375,6 +375,12 @@ msgid_plural "%{releases} releases"
msgstr[0] ""
msgstr[1] ""
+msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgstr ""
+
+msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgstr ""
+
msgid "%{service_title} activated."
msgstr ""
@@ -7155,6 +7161,9 @@ msgstr ""
msgid "Enable mirror configuration"
msgstr ""
+msgid "Enable or disable keyboard shortcuts"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -10945,6 +10954,9 @@ msgstr ""
msgid "Keyboard Shortcuts"
msgstr ""
+msgid "Keyboard shortcuts"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -15857,6 +15869,9 @@ msgstr ""
msgid "Remove child epic from an epic"
msgstr ""
+msgid "Remove description history"
+msgstr ""
+
msgid "Remove due date"
msgstr ""
@@ -17780,6 +17795,9 @@ msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
+msgid "Something went wrong while deleting description changes. Please try again."
+msgstr ""
+
msgid "Something went wrong while deleting the image."
msgstr ""
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 490ae9e84e7..9be0b308680 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -37,6 +37,7 @@ FactoryBot.define do
group_runners_enabled { nil }
import_status { nil }
import_jid { nil }
+ forward_deployment_enabled { nil }
end
after(:create) do |project, evaluator|
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 4520d1bb2da..542caccb18d 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -68,4 +68,34 @@ describe 'Mermaid rendering', :js do
expect(page).to have_selector('pre.mermaid')
end
end
+
+ it 'correctly sizes mermaid diagram inside <details> block', :js do
+ description = <<~MERMAID
+ <details>
+ <summary>Click to show diagram</summary>
+
+ ```mermaid
+ graph TD;
+ A-->B;
+ A-->C;
+ B-->D;
+ C-->D;
+ ```
+
+ </details>
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ page.within('.description') do
+ page.find('summary').click
+ svg = page.find('svg.mermaid')
+
+ expect(svg[:width].to_i).to be_within(5).of(120)
+ expect(svg[:height].to_i).to be_within(5).of(220)
+ end
+ end
end
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index c6efe1f1896..beed1c07e51 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -17,6 +17,59 @@ describe 'User uses shortcuts', :js do
wait_for_requests
end
+ context 'disabling shortcuts' do
+ before do
+ page.evaluate_script("localStorage.removeItem('shortcutsDisabled')")
+ end
+
+ it 'can disable shortcuts from help menu' do
+ open_modal_shortcut_keys
+ click_toggle_button
+ close_modal
+
+ open_modal_shortcut_keys
+
+ # modal-shortcuts still in the DOM, but hidden
+ expect(find('#modal-shortcuts', visible: false)).not_to be_visible
+
+ page.refresh
+ open_modal_shortcut_keys
+
+ # after reload, shortcuts modal doesn't exist at all until we add it
+ expect(page).not_to have_selector('#modal-shortcuts')
+ end
+
+ it 're-enables shortcuts' do
+ open_modal_shortcut_keys
+ click_toggle_button
+ close_modal
+
+ open_modal_from_help_menu
+ click_toggle_button
+ close_modal
+
+ open_modal_shortcut_keys
+ expect(find('#modal-shortcuts')).to be_visible
+ end
+
+ def open_modal_shortcut_keys
+ find('body').native.send_key('?')
+ end
+
+ def open_modal_from_help_menu
+ find('.header-help-dropdown-toggle').click
+ find('button', text: 'Keyboard shortcuts').click
+ end
+
+ def click_toggle_button
+ find('.js-toggle-shortcuts .gl-toggle').click
+ end
+
+ def close_modal
+ find('.modal button[aria-label="Close"]').click
+ end
+ end
+
context 'when navigating to the Project pages' do
it 'redirects to the details page' do
visit project_issues_path(project)
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb
deleted file mode 100644
index 2a33af2c644..00000000000
--- a/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::LinkLfsObjects, :migration, schema: 2020_02_10_062432 do
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:fork_networks) { table(:fork_networks) }
- let(:fork_network_members) { table(:fork_network_members) }
- let(:lfs_objects) { table(:lfs_objects) }
- let(:lfs_objects_projects) { table(:lfs_objects_projects) }
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
- let(:source_project) { projects.create(namespace_id: namespace.id) }
- let(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let(:project) { projects.create(namespace_id: namespace.id) }
- let(:another_project) { projects.create(namespace_id: namespace.id) }
- let(:other_project) { projects.create(namespace_id: namespace.id) }
- let(:linked_project) { projects.create(namespace_id: namespace.id) }
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
-
- before do
- # Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
-
- [project, another_project, linked_project].each do |p|
- fork_network_members.create(
- fork_network_id: fork_network.id,
- project_id: p.id,
- forked_from_project_id: fork_network.root_project_id
- )
- end
-
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: other_project.id, forked_from_project_id: another_fork_network.root_project_id)
-
- # Links LFS objects to some projects
- [source_project, another_source_project, linked_project].each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'creates LfsObjectsProject records for forks within the specified range of project IDs' do
- expect { subject.perform(project.id, other_project.id) }.to change { lfs_objects_projects.count }.by(6)
-
- expect(lfs_object_ids_for(project)).to match_array(lfs_object_ids_for(source_project))
- expect(lfs_object_ids_for(another_project)).to match_array(lfs_object_ids_for(source_project))
- expect(lfs_object_ids_for(other_project)).to match_array(lfs_object_ids_for(another_source_project))
-
- expect { subject.perform(project.id, other_project.id) }.not_to change { lfs_objects_projects.count }
- end
-
- context 'when it is not necessary to create LfsObjectProject records' do
- it 'does not create LfsObjectProject records' do
- expect { subject.perform(linked_project.id, linked_project.id) }
- .not_to change { lfs_objects_projects.count }
- end
- end
-
- def lfs_object_ids_for(project)
- lfs_objects_projects.where(project_id: project.id).pluck(:lfs_object_id)
- end
-end
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb
index 6a62ef456c1..3a362dfccbf 100644
--- a/spec/lib/quality/kubernetes_client_spec.rb
+++ b/spec/lib/quality/kubernetes_client_spec.rb
@@ -102,7 +102,7 @@ RSpec.describe Quality::KubernetesClient do
it 'calls kubectl to retrieve the resource names' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with(["kubectl get #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" -o custom-columns=NAME:.metadata.name)])
+ %(--namespace "#{namespace}" -o name)])
.and_return(Gitlab::Popen::Result.new([], raw_resource_names_str, '', double(success?: true)))
expect(subject.__send__(:raw_resource_names)).to eq(raw_resource_names)
diff --git a/spec/migrations/schedule_link_lfs_objects_spec.rb b/spec/migrations/schedule_link_lfs_objects_spec.rb
deleted file mode 100644
index 87632bc7648..00000000000
--- a/spec/migrations/schedule_link_lfs_objects_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200210062432_schedule_link_lfs_objects.rb')
-
-describe ScheduleLinkLfsObjects, :migration, :sidekiq do
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:fork_networks) { table(:fork_networks) }
- let(:fork_network_members) { table(:fork_network_members) }
- let(:lfs_objects) { table(:lfs_objects) }
- let(:lfs_objects_projects) { table(:lfs_objects_projects) }
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
- let(:source_project) { projects.create(namespace_id: namespace.id) }
- let(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let(:project) { projects.create(namespace_id: namespace.id) }
- let(:another_project) { projects.create(namespace_id: namespace.id) }
- let(:other_project) { projects.create(namespace_id: namespace.id) }
- let(:linked_project) { projects.create(namespace_id: namespace.id) }
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
-
- before do
- # Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
-
- [project, another_project, linked_project].each do |p|
- fork_network_members.create(
- fork_network_id: fork_network.id,
- project_id: p.id,
- forked_from_project_id: fork_network.root_project_id
- )
- end
-
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: other_project.id, forked_from_project_id: another_fork_network.root_project_id)
- end
-
- context 'when there are forks to be backfilled' do
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
-
- # Links LFS objects to some projects
- [source_project, another_source_project, linked_project].each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'schedules background migration to link LFS objects' do
- Sidekiq::Testing.fake! do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq(2)
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(2.minutes, project.id, another_project.id)
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(4.minutes, other_project.id, other_project.id)
- end
- end
- end
-
- context 'when there are no forks to be backfilled' do
- before do
- # Links LFS objects to all projects
- projects.all.each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'does not schedule any job' do
- Sidekiq::Testing.fake! do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq(0)
- end
- end
- end
-end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 257f699a459..ab7e12cd43c 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -281,6 +281,45 @@ describe Deployment do
expect(last_deployments).to match_array(deployments.last(2))
end
end
+
+ describe 'active' do
+ subject { described_class.active }
+
+ it 'retrieves the active deployments' do
+ deployment1 = create(:deployment, status: :created )
+ deployment2 = create(:deployment, status: :running )
+ create(:deployment, status: :failed )
+ create(:deployment, status: :canceled )
+
+ is_expected.to contain_exactly(deployment1, deployment2)
+ end
+ end
+
+ describe 'older_than' do
+ let(:deployment) { create(:deployment) }
+
+ subject { described_class.older_than(deployment) }
+
+ it 'retrives the correct older deployments' do
+ older_deployment1 = create(:deployment)
+ older_deployment2 = create(:deployment)
+ deployment
+ create(:deployment)
+
+ is_expected.to contain_exactly(older_deployment1, older_deployment2)
+ end
+ end
+
+ describe 'with_deployable' do
+ subject { described_class.with_deployable }
+
+ it 'retrieves deployments with deployable builds' do
+ with_deployable = create(:deployment)
+ create(:deployment, deployable: nil)
+
+ is_expected.to contain_exactly(with_deployable)
+ end
+ end
end
describe '#includes_commit?' do
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index eb3a7e527c9..312cbbb0948 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -32,6 +32,12 @@ describe ProjectCiCdSetting do
end
end
+ describe '#forward_deployment_enabled' do
+ it 'is true by default' do
+ expect(described_class.new.forward_deployment_enabled).to be_truthy
+ end
+ end
+
describe '#default_git_depth' do
let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
diff --git a/spec/services/deployments/older_deployments_drop_service_spec.rb b/spec/services/deployments/older_deployments_drop_service_spec.rb
new file mode 100644
index 00000000000..44e9af07e46
--- /dev/null
+++ b/spec/services/deployments/older_deployments_drop_service_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Deployments::OlderDeploymentsDropService do
+ let(:environment) { create(:environment) }
+ let(:deployment) { create(:deployment, environment: environment) }
+ let(:service) { described_class.new(deployment) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ shared_examples 'it does not drop any build' do
+ it do
+ expect { subject }.to not_change(Ci::Build.failed, :count)
+ end
+ end
+
+ context 'when deployment is nil' do
+ let(:deployment) { nil }
+
+ it_behaves_like 'it does not drop any build'
+ end
+
+ context 'when a deployment is passed in' do
+ context 'and there is no active deployment for the related environment' do
+ let(:deployment) { create(:deployment, :canceled, environment: environment) }
+ let(:deployment2) { create(:deployment, :canceled, environment: environment) }
+
+ before do
+ deployment
+ deployment2
+ end
+
+ it_behaves_like 'it does not drop any build'
+ end
+
+ context 'and there are active deployment for the related environment' do
+ let(:deployment) { create(:deployment, :running, environment: environment) }
+ let(:deployment2) { create(:deployment, :running, environment: environment) }
+
+ context 'and there is no older deployment than "deployment"' do
+ before do
+ deployment
+ deployment2
+ end
+
+ it_behaves_like 'it does not drop any build'
+ end
+
+ context 'and there is an older deployment than "deployment"' do
+ let(:older_deployment) { create(:deployment, :running, environment: environment) }
+
+ before do
+ older_deployment
+ deployment
+ deployment2
+ end
+
+ it 'drops that older deployment' do
+ deployable = older_deployment.deployable
+ expect(deployable.failed?).to be_falsey
+
+ subject
+
+ expect(deployable.reload.failed?).to be_truthy
+ end
+
+ context 'and there is no deployable for that older deployment' do
+ let(:older_deployment) { create(:deployment, :running, environment: environment, deployable: nil) }
+
+ it_behaves_like 'it does not drop any build'
+ end
+ end
+ end
+ end
+ end
+end