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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-01 12:10:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-01 12:10:28 +0300
commita928c5170fa58e4aef91ebca6c4fc9ec7cea812e (patch)
treedc700a0e00f32ea0aa8f642b75b7da7c24ade7e8
parent0a6b0190477aec55a1cff8e2812b177ea6df39b2 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue53
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_row.vue24
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue3
-rw-r--r--app/assets/javascripts/diffs/constants.js1
-rw-r--r--app/assets/javascripts/diffs/store/actions.js16
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js4
-rw-r--r--app/assets/javascripts/diffs/store/utils.js3
-rw-r--r--app/assets/javascripts/milestones/project_milestone_combobox.vue22
-rw-r--r--app/assets/javascripts/pages/admin/dev_ops_score/index.js26
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue18
-rw-r--r--app/assets/javascripts/releases/components/app_edit_new.vue2
-rw-r--r--app/assets/javascripts/static_site_editor/services/parse_source_file.js11
-rw-r--r--app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js17
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue6
-rw-r--r--app/assets/stylesheets/framework/tables.scss30
-rw-r--r--app/assets/stylesheets/pages/diff.scss2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/views/admin/dev_ops_score/_disabled.html.haml14
-rw-r--r--app/views/admin/dev_ops_score/show.html.haml2
-rw-r--r--app/workers/issue_rebalancing_worker.rb9
-rw-r--r--changelogs/unreleased/230835-add-index-of-merge_request_id-on-approval-merge-request-rules-tabl.yml5
-rw-r--r--changelogs/unreleased/233002-diff-file-input-cutoff.yml5
-rw-r--r--changelogs/unreleased/241000-front-matter-parsing.yml5
-rw-r--r--changelogs/unreleased/241700-devops-score-migrate-empty-state-into-vue-component.yml5
-rw-r--r--changelogs/unreleased/243444-user-cannot-sign-out-of-gitlab-once-admin-resets-their-password.yml5
-rw-r--r--changelogs/unreleased/24629.yml5
-rw-r--r--changelogs/unreleased/bump-ado-image-to-v1-0-2.yml5
-rw-r--r--changelogs/unreleased/jdb-fix-diffs-viewer-max-lines.yml5
-rw-r--r--changelogs/unreleased/nfriend-prevent-enter-submission.yml5
-rw-r--r--db/migrate/20200826212800_add_index_on_merge_request_id_and_rule_type_to_approval_merge_request_rule.rb23
-rw-r--r--db/schema_migrations/202008262128001
-rw-r--r--db/structure.sql2
-rw-r--r--doc/development/gotchas.md15
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml2
-rw-r--r--locale/gitlab.pot37
-rw-r--r--spec/controllers/sessions_controller_spec.rb60
-rw-r--r--spec/features/admin/admin_dev_ops_score_spec.rb4
-rw-r--r--spec/frontend/__mocks__/lodash/debounce.js13
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js1
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js30
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js115
-rw-r--r--spec/frontend/diffs/store/actions_spec.js22
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js4
-rw-r--r--spec/frontend/ide/components/pipelines/list_spec.js63
-rw-r--r--spec/frontend/milestones/project_milestone_combobox_spec.js44
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js22
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js2
-rw-r--r--spec/frontend/static_site_editor/graphql/resolvers/file_spec.js2
-rw-r--r--spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js2
-rw-r--r--spec/frontend/static_site_editor/mock_data.js16
-rw-r--r--spec/frontend/static_site_editor/pages/home_spec.js2
-rw-r--r--spec/frontend/static_site_editor/services/load_source_content_spec.js7
-rw-r--r--spec/frontend/static_site_editor/services/parse_source_file_language_support_spec.js20
-rw-r--r--spec/frontend/static_site_editor/services/parse_source_file_spec.js46
-rw-r--r--spec/frontend/static_site_editor/services/submit_content_changes_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/file_row_spec.js12
-rw-r--r--spec/models/issue_spec.rb4
-rw-r--r--spec/services/issues/create_service_spec.rb6
-rw-r--r--spec/services/issues/update_service_spec.rb8
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/helpers/next_found_instance_of.rb33
-rw-r--r--spec/workers/issue_rebalancing_worker_spec.rb17
-rw-r--r--spec/workers/new_note_worker_spec.rb10
72 files changed, 742 insertions, 236 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 690a8c454e7..a0b7d19d05c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-e4ff30e44b6ac21f33290bbe7a9cbbd42f98d4d1
+e9860f7988a2c87638abf695d8613e3096312857
diff --git a/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue b/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue
new file mode 100644
index 00000000000..5429ec403d3
--- /dev/null
+++ b/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue
@@ -0,0 +1,53 @@
+<script>
+import { GlEmptyState, GlSprintf, GlLink, GlButton } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlEmptyState,
+ GlSprintf,
+ GlLink,
+ GlButton,
+ },
+ inject: {
+ isAdmin: {
+ type: Boolean,
+ },
+ svgPath: {
+ type: String,
+ },
+ docsLink: {
+ type: String,
+ },
+ primaryButtonPath: {
+ type: String,
+ },
+ },
+};
+</script>
+<template>
+ <gl-empty-state class="js-empty-state" :title="__('Usage ping is off')" :svg-path="svgPath">
+ <template #description>
+ <gl-sprintf
+ v-if="!isAdmin"
+ :message="
+ __(
+ 'To view instance-level analytics, ask an admin to turn on %{docLinkStart}usage ping%{docLinkEnd}.',
+ )
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="docsLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ <template v-else
+ ><p>
+ {{ __('Turn on usage ping to review instance-level analytics.') }}
+ </p>
+
+ <gl-button category="primary" variant="success" :href="primaryButtonPath">
+ {{ __('Turn on usage ping') }}</gl-button
+ >
+ </template>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 66d7c2e3530..4ba9c29f9a9 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -468,7 +468,7 @@ export default {
<div
:data-can-create-note="getNoteableData.current_user.can_create_note"
- class="files d-flex"
+ class="files d-flex gl-mt-2"
>
<div
v-if="showTreeList"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index e767c8cbefb..e3e140ea35e 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -133,6 +133,7 @@ export default {
'toggleFileDiscussions',
'toggleFileDiscussionWrappers',
'toggleFullDiff',
+ 'toggleActiveFileByHash',
]),
handleToggleFile() {
this.$emit('toggleFile');
@@ -149,6 +150,9 @@ export default {
const selector = this.diffContentIDSelector;
scrollToElement(document.querySelector(selector));
window.location.hash = selector;
+ if (!this.viewDiffsFileByFile) {
+ this.toggleActiveFileByHash(this.diffFile.file_hash);
+ }
}
},
},
diff --git a/app/assets/javascripts/diffs/components/diff_file_row.vue b/app/assets/javascripts/diffs/components/diff_file_row.vue
index 43b669625f4..2856e6ae8eb 100644
--- a/app/assets/javascripts/diffs/components/diff_file_row.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_row.vue
@@ -3,9 +3,10 @@
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import FileRow from '~/vue_shared/components/file_row.vue';
-import FileRowStats from './file_row_stats.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import FileRowStats from './file_row_stats.vue';
export default {
name: 'DiffFileRow',
@@ -14,6 +15,7 @@ export default {
FileRowStats,
ChangedFileIcon,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
file: {
type: Object,
@@ -28,11 +30,28 @@ export default {
required: false,
default: null,
},
+ viewedFiles: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
computed: {
showFileRowStats() {
return !this.hideFileStats && this.file.type === 'blob';
},
+ fileClasses() {
+ if (!this.glFeatures.highlightCurrentDiffRow) {
+ return '';
+ }
+
+ return this.file.type === 'blob' && !this.viewedFiles[this.file.fileHash]
+ ? 'gl-font-weight-bold'
+ : '';
+ },
+ isActive() {
+ return this.currentDiffFileId === this.file.fileHash;
+ },
},
};
</script>
@@ -41,8 +60,9 @@ export default {
<file-row
:file="file"
v-bind="$attrs"
- :class="{ 'is-active': currentDiffFileId === file.fileHash }"
+ :class="{ 'is-active': isActive }"
class="diff-file-row"
+ :file-classes="fileClasses"
v-on="$listeners"
>
<file-row-stats v-if="showFileRowStats" :file="file" class="mr-1" />
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 64c4715267f..d03d450b12d 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -25,7 +25,7 @@ export default {
};
},
computed: {
- ...mapState('diffs', ['tree', 'renderTreeList', 'currentDiffFileId']),
+ ...mapState('diffs', ['tree', 'renderTreeList', 'currentDiffFileId', 'viewedDiffFileIds']),
...mapGetters('diffs', ['allBlobs']),
filteredTreeList() {
const search = this.search.toLowerCase().trim();
@@ -93,6 +93,7 @@ export default {
:key="file.key"
:file="file"
:level="0"
+ :viewed-files="viewedDiffFileIds"
:hide-file-stats="hideFileStats"
:file-row-component="$options.DiffFileRow"
:current-diff-file-id="currentDiffFileId"
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 447136036ee..a79a385b9bc 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -34,7 +34,6 @@ export const COUNT_OF_AVATARS_IN_GUTTER = 3;
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
-export const MAX_LINES_TO_BE_RENDERED = 2000;
export const DIFF_FILE_SYMLINK_MODE = '120000';
export const DIFF_FILE_DELETED_MODE = '0';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index e0470a7d93f..0f275f1cb3e 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -84,7 +84,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
commit(types.SET_BATCH_LOADING, false);
if (!isNoteLink && !state.currentDiffFileId) {
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, diff_files[0].file_hash);
+ commit(types.VIEW_DIFF_FILE, diff_files[0].file_hash);
}
if (isNoteLink) {
@@ -100,7 +100,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
!state.diffFiles.some(f => f.file_hash === state.currentDiffFileId) &&
!isNoteLink
) {
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, state.diffFiles[0].file_hash);
+ commit(types.VIEW_DIFF_FILE, state.diffFiles[0].file_hash);
}
if (gon.features?.codeNavigation) {
@@ -183,7 +183,7 @@ export const fetchCoverageFiles = ({ commit, state }) => {
export const setHighlightedRow = ({ commit }, lineCode) => {
const fileHash = lineCode.split('_')[0];
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+ commit(types.VIEW_DIFF_FILE, fileHash);
};
// This is adding line discussions to the actual lines in the diff tree
@@ -428,13 +428,17 @@ export const toggleTreeOpen = ({ commit }, path) => {
commit(types.TOGGLE_FOLDER_OPEN, path);
};
+export const toggleActiveFileByHash = ({ commit }, hash) => {
+ commit(types.VIEW_DIFF_FILE, hash);
+};
+
export const scrollToFile = ({ state, commit }, path) => {
if (!state.treeEntries[path]) return;
const { fileHash } = state.treeEntries[path];
document.location.hash = fileHash;
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+ commit(types.VIEW_DIFF_FILE, fileHash);
};
export const toggleShowTreeList = ({ commit, state }, saving = true) => {
@@ -702,7 +706,7 @@ export const setCurrentDiffFileIdFromNote = ({ commit, state, rootGetters }, not
const fileHash = rootGetters.getDiscussion(note.discussion_id).diff_file?.file_hash;
if (fileHash && state.diffFiles.some(f => f.file_hash === fileHash)) {
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+ commit(types.VIEW_DIFF_FILE, fileHash);
}
};
@@ -710,5 +714,5 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => {
const fileHash = state.diffFiles[index].file_hash;
document.location.hash = fileHash;
- commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+ commit(types.VIEW_DIFF_FILE, fileHash);
};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index c8f6f6bb0e5..001d9d9f594 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -34,6 +34,7 @@ export default () => ({
showTreeList: true,
currentDiffFileId: '',
projectPath: '',
+ viewedDiffFileIds: {},
commentForms: [],
highlightedRow: null,
renderTreeList: true,
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 4b1dbc34902..5dba2e9d10d 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -19,7 +19,7 @@ export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE';
export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE';
export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN';
export const TOGGLE_SHOW_TREE_LIST = 'TOGGLE_SHOW_TREE_LIST';
-export const UPDATE_CURRENT_DIFF_FILE_ID = 'UPDATE_CURRENT_DIFF_FILE_ID';
+export const VIEW_DIFF_FILE = 'VIEW_DIFF_FILE';
export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM';
export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index c39532599cb..268326f9246 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,3 +1,4 @@
+import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { PARALLEL_DIFF_VIEW_TYPE } from '../constants';
import {
@@ -291,8 +292,9 @@ export default {
[types.TOGGLE_SHOW_TREE_LIST](state) {
state.showTreeList = !state.showTreeList;
},
- [types.UPDATE_CURRENT_DIFF_FILE_ID](state, fileId) {
+ [types.VIEW_DIFF_FILE](state, fileId) {
state.currentDiffFileId = fileId;
+ Vue.set(state.viewedDiffFileIds, fileId, true);
},
[types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) {
state.commentForms.push({
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 320b44bd537..3bdb79d3899 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -11,7 +11,6 @@ import {
OLD_LINE_TYPE,
MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
- MAX_LINES_TO_BE_RENDERED,
TREE_TYPE,
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
@@ -457,12 +456,10 @@ function getVisibleDiffLines(file) {
}
function finalizeDiffFile(file) {
- const name = (file.viewer && file.viewer.name) || diffViewerModes.text;
const lines = getVisibleDiffLines(file);
Object.assign(file, {
renderIt: lines < LINES_TO_BE_RENDERED_DIRECTLY,
- collapsed: name === diffViewerModes.text && lines > MAX_LINES_TO_BE_RENDERED,
isShowingFullFile: false,
isLoadingFullFile: false,
discussions: [],
diff --git a/app/assets/javascripts/milestones/project_milestone_combobox.vue b/app/assets/javascripts/milestones/project_milestone_combobox.vue
index 4e61e8b4262..b2196bd9ceb 100644
--- a/app/assets/javascripts/milestones/project_milestone_combobox.vue
+++ b/app/assets/javascripts/milestones/project_milestone_combobox.vue
@@ -89,6 +89,14 @@ export default {
return this.requestCount !== 0;
},
},
+ created() {
+ // This method is defined here instead of in `methods`
+ // because we need to access the .cancel() method
+ // lodash attaches to the function, which is
+ // made inaccessible by Vue. More info:
+ // https://stackoverflow.com/a/52988020/1063392
+ this.debouncedSearchMilestones = debounce(this.searchMilestones, 100);
+ },
mounted() {
this.fetchMilestones();
},
@@ -108,7 +116,7 @@ export default {
this.requestCount -= 1;
});
},
- searchMilestones: debounce(function searchMilestones() {
+ searchMilestones() {
this.requestCount += 1;
const options = {
search: this.searchQuery,
@@ -133,7 +141,14 @@ export default {
.finally(() => {
this.requestCount -= 1;
});
- }, 100),
+ },
+ onSearchBoxInput() {
+ this.debouncedSearchMilestones();
+ },
+ onSearchBoxEnter() {
+ this.debouncedSearchMilestones.cancel();
+ this.searchMilestones();
+ },
toggleMilestoneSelection(clickedMilestone) {
if (!clickedMilestone) return [];
@@ -186,7 +201,8 @@ export default {
v-model.trim="searchQuery"
class="gl-m-3"
:placeholder="this.$options.translations.searchMilestones"
- @input="searchMilestones"
+ @input="onSearchBoxInput"
+ @keydown.enter.prevent="onSearchBoxEnter"
/>
<gl-new-dropdown-item @click="onMilestoneClicked(null)">
diff --git a/app/assets/javascripts/pages/admin/dev_ops_score/index.js b/app/assets/javascripts/pages/admin/dev_ops_score/index.js
index c1056537f90..9d018408b88 100644
--- a/app/assets/javascripts/pages/admin/dev_ops_score/index.js
+++ b/app/assets/javascripts/pages/admin/dev_ops_score/index.js
@@ -1,3 +1,27 @@
+import Vue from 'vue';
import UserCallout from '~/user_callout';
+import UsagePingDisabled from '~/admin/dev_ops_score/components/usage_ping_disabled.vue';
-document.addEventListener('DOMContentLoaded', () => new UserCallout());
+document.addEventListener('DOMContentLoaded', () => {
+ // eslint-disable-next-line no-new
+ new UserCallout();
+
+ const emptyStateContainer = document.getElementById('js-devops-empty-state');
+
+ if (!emptyStateContainer) return false;
+
+ const { emptyStateSvgPath, enableUsagePingLink, docsLink, isAdmin } = emptyStateContainer.dataset;
+
+ return new Vue({
+ el: emptyStateContainer,
+ provide: {
+ isAdmin: Boolean(isAdmin),
+ svgPath: emptyStateSvgPath,
+ primaryButtonPath: enableUsagePingLink,
+ docsLink,
+ },
+ render(h) {
+ return h(UsagePingDisabled);
+ },
+ });
+});
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index e388604ed92..dd84246159b 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -87,6 +87,15 @@ export default {
},
},
created() {
+ // This method is defined here instead of in `methods`
+ // because we need to access the .cancel() method
+ // lodash attaches to the function, which is
+ // made inaccessible by Vue. More info:
+ // https://stackoverflow.com/a/52988020/1063392
+ this.debouncedSearch = debounce(function search() {
+ this.search(this.query);
+ }, SEARCH_DEBOUNCE_MS);
+
this.setProjectId(this.projectId);
this.search(this.query);
},
@@ -95,9 +104,13 @@ export default {
focusSearchBox() {
this.$refs.searchBox.$el.querySelector('input').focus();
},
- onSearchBoxInput: debounce(function search() {
+ onSearchBoxEnter() {
+ this.debouncedSearch.cancel();
this.search(this.query);
- }, SEARCH_DEBOUNCE_MS),
+ },
+ onSearchBoxInput() {
+ this.debouncedSearch();
+ },
selectRef(ref) {
this.setSelectedRef(ref);
this.$emit('input', this.selectedRef);
@@ -129,6 +142,7 @@ export default {
class="gl-m-3"
:placeholder="i18n.searchPlaceholder"
@input="onSearchBoxInput"
+ @keydown.enter.prevent="onSearchBoxEnter"
/>
<div class="gl-flex-grow-1 gl-overflow-y-auto">
diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue
index e34be1bd871..e1edf3d689d 100644
--- a/app/assets/javascripts/releases/components/app_edit_new.vue
+++ b/app/assets/javascripts/releases/components/app_edit_new.vue
@@ -140,7 +140,7 @@ export default {
class="form-control"
/>
</gl-form-group>
- <gl-form-group class="w-50" data-testid="milestones-field" @keydown.enter.prevent.capture>
+ <gl-form-group class="w-50" data-testid="milestones-field">
<label>{{ __('Milestones') }}</label>
<div class="d-flex flex-column col-md-6 col-sm-10 pl-0">
<milestone-combobox
diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file.js b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
index dca34e16e8d..57505d50d6e 100644
--- a/app/assets/javascripts/static_site_editor/services/parse_source_file.js
+++ b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
@@ -1,6 +1,11 @@
-const parseSourceFile = raw => {
- const frontMatterRegex = /(^---$[\s\S]*?^---$)/m;
- const preGroupedRegex = /([\s\S]*?)(^---$[\s\S]*?^---$)(\s*)([\s\S]*)/m; // preFrontMatter, frontMatter, spacing, and content
+import getFrontMatterLanguageDefinition from './parse_source_file_language_support';
+
+const parseSourceFile = (raw, options = { frontMatterLanguage: 'yaml' }) => {
+ const { open, close } = getFrontMatterLanguageDefinition(options.frontMatterLanguage);
+ const anyChar = '[\\s\\S]';
+ const frontMatterBlock = `^${open}$${anyChar}*?^${close}$`;
+ const frontMatterRegex = new RegExp(`${frontMatterBlock}`, 'm');
+ const preGroupedRegex = new RegExp(`(${anyChar}*?)(${frontMatterBlock})(\\s*)(${anyChar}*)`, 'm'); // preFrontMatter, frontMatter, spacing, and content
let initial;
let editable;
diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js b/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js
new file mode 100644
index 00000000000..ec0eaca81b8
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js
@@ -0,0 +1,17 @@
+const frontMatterLanguageDefinitions = [
+ { name: 'yaml', open: '---', close: '---' },
+ { name: 'toml', open: '\\+\\+\\+', close: '\\+\\+\\+' },
+ { name: 'json', open: '{', close: '}' },
+];
+
+const getFrontMatterLanguageDefinition = name => {
+ const languageDefinition = frontMatterLanguageDefinitions.find(def => def.name === name);
+
+ if (!languageDefinition) {
+ throw new Error(`Unsupported front matter language: ${name}`);
+ }
+
+ return languageDefinition;
+};
+
+export default getFrontMatterLanguageDefinition;
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 0952e37e46e..004bf882dd6 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -18,6 +18,11 @@ export default {
type: Number,
required: true,
},
+ fileClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
isTree() {
@@ -123,6 +128,7 @@ export default {
:style="levelIndentation"
class="file-row-name str-truncated"
data-qa-selector="file_name_content"
+ :class="fileClasses"
>
<file-icon
class="file-row-icon"
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 1f60485aa36..2924dd3bf61 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -180,3 +180,33 @@ table {
border-top: 0;
}
}
+
+.vulnerability-list {
+ @media (min-width: $breakpoint-sm) {
+ .checkbox {
+ padding-left: $gl-spacing-scale-4;
+ padding-right: 0;
+
+ + td,
+ + th {
+ padding-left: $gl-spacing-scale-4;
+ }
+ }
+
+ .status {
+ width: 8%;
+ }
+
+ .severity {
+ width: 9%;
+ }
+
+ .identifier {
+ width: 12%;
+ }
+
+ .scanner {
+ width: 15%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 3c36a5a207b..62af7103b39 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1062,7 +1062,7 @@ table.code {
.diff-tree-list {
position: -webkit-sticky;
position: sticky;
- $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px;
+ $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 15px;
top: $top-pos;
max-height: calc(100vh - #{$top-pos});
z-index: 202;
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index db7f17a78b4..bf5b1d5d92d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -39,6 +39,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
push_frontend_feature_flag(:unified_diff_lines, @project)
+ push_frontend_feature_flag(:highlight_current_diff_row, @project)
end
before_action do
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 62fd8ed376f..baf7a05f8ba 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -10,6 +10,8 @@ class SessionsController < Devise::SessionsController
include KnownSignIn
skip_before_action :check_two_factor_requirement, only: [:destroy]
+ skip_before_action :check_password_expiration, only: [:destroy]
+
# replaced with :require_no_authentication_without_flash
skip_before_action :require_no_authentication, only: [:new, :create]
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 4044ad9cf21..fc668ad75fd 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -445,7 +445,7 @@ class Issue < ApplicationRecord
super
rescue ActiveRecord::QueryCanceled => e
# Symptom of running out of space - schedule rebalancing
- IssueRebalancingWorker.perform_async(id)
+ IssueRebalancingWorker.perform_async(nil, project_id)
raise e
end
@@ -453,7 +453,7 @@ class Issue < ApplicationRecord
super
rescue ActiveRecord::QueryCanceled => e
# Symptom of running out of space - schedule rebalancing
- IssueRebalancingWorker.perform_async(id)
+ IssueRebalancingWorker.perform_async(nil, project_id)
raise e
end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 8e42db746dd..cf1ef6a9710 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -29,7 +29,7 @@ module Issues
gates = [issue.project, issue.project.group].compact
return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
- IssueRebalancingWorker.perform_async(issue.id)
+ IssueRebalancingWorker.perform_async(nil, issue.project_id)
end
def create_assignee_note(issue, old_assignees)
diff --git a/app/views/admin/dev_ops_score/_disabled.html.haml b/app/views/admin/dev_ops_score/_disabled.html.haml
deleted file mode 100644
index bd808218f75..00000000000
--- a/app/views/admin/dev_ops_score/_disabled.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-.container.devops-empty
- .col-sm-12.justify-content-center.text-center
- = custom_icon('dev_ops_score_no_index')
- %h4= _('Usage ping is not enabled')
- - if !current_user.admin?
- %p
- - usage_ping_path = help_page_path('development/telemetry/usage_ping')
- - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
- = s_('In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
- - if current_user.admin?
- %p
- = _('Enable usage ping to get an overview of how you are using GitLab from a feature perspective.')
- - if current_user.admin?
- = link_to _('Enable usage ping'), metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'btn btn-primary'
diff --git a/app/views/admin/dev_ops_score/show.html.haml b/app/views/admin/dev_ops_score/show.html.haml
index 520194acc88..3a7fb9d7c8c 100644
--- a/app/views/admin/dev_ops_score/show.html.haml
+++ b/app/views/admin/dev_ops_score/show.html.haml
@@ -7,7 +7,7 @@
.gl-mt-3
- if !usage_ping_enabled
- = render 'disabled'
+ #js-devops-empty-state{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_usage_ping_link: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/telemetry/usage_ping') } }
- elsif @metric.blank?
= render 'no_data'
- else
diff --git a/app/workers/issue_rebalancing_worker.rb b/app/workers/issue_rebalancing_worker.rb
index c31b6cfbd86..032ba5534e6 100644
--- a/app/workers/issue_rebalancing_worker.rb
+++ b/app/workers/issue_rebalancing_worker.rb
@@ -7,11 +7,14 @@ class IssueRebalancingWorker
urgency :low
feature_category :issue_tracking
- def perform(issue_id)
- issue = Issue.find(issue_id)
+ def perform(ignore = nil, project_id = nil)
+ return if project_id.nil?
+
+ project = Project.find(project_id)
+ issue = project.issues.first # All issues are equivalent as far as we are concerned
IssueRebalancingService.new(issue).execute
rescue ActiveRecord::RecordNotFound, IssueRebalancingService::TooManyIssues => e
- Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id)
+ Gitlab::ErrorTracking.log_exception(e, project_id: project_id)
end
end
diff --git a/changelogs/unreleased/230835-add-index-of-merge_request_id-on-approval-merge-request-rules-tabl.yml b/changelogs/unreleased/230835-add-index-of-merge_request_id-on-approval-merge-request-rules-tabl.yml
new file mode 100644
index 00000000000..a387a47164a
--- /dev/null
+++ b/changelogs/unreleased/230835-add-index-of-merge_request_id-on-approval-merge-request-rules-tabl.yml
@@ -0,0 +1,5 @@
+---
+title: Add index on merge_request_id to approval_merge_request_rules
+merge_request: 40556
+author:
+type: other
diff --git a/changelogs/unreleased/233002-diff-file-input-cutoff.yml b/changelogs/unreleased/233002-diff-file-input-cutoff.yml
new file mode 100644
index 00000000000..018c3d0ef0a
--- /dev/null
+++ b/changelogs/unreleased/233002-diff-file-input-cutoff.yml
@@ -0,0 +1,5 @@
+---
+title: Fix file file input top position cutoff
+merge_request: 40634
+author:
+type: fixed
diff --git a/changelogs/unreleased/241000-front-matter-parsing.yml b/changelogs/unreleased/241000-front-matter-parsing.yml
new file mode 100644
index 00000000000..a5a1b466ea4
--- /dev/null
+++ b/changelogs/unreleased/241000-front-matter-parsing.yml
@@ -0,0 +1,5 @@
+---
+title: Add toml and json front matter language support to Static Site Editor's WYSIWYG mode
+merge_request: 40718
+author:
+type: added
diff --git a/changelogs/unreleased/241700-devops-score-migrate-empty-state-into-vue-component.yml b/changelogs/unreleased/241700-devops-score-migrate-empty-state-into-vue-component.yml
new file mode 100644
index 00000000000..15128a69829
--- /dev/null
+++ b/changelogs/unreleased/241700-devops-score-migrate-empty-state-into-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate DevOps Score empty state into Vue component
+merge_request: 40595
+author:
+type: changed
diff --git a/changelogs/unreleased/243444-user-cannot-sign-out-of-gitlab-once-admin-resets-their-password.yml b/changelogs/unreleased/243444-user-cannot-sign-out-of-gitlab-once-admin-resets-their-password.yml
new file mode 100644
index 00000000000..fa57890b031
--- /dev/null
+++ b/changelogs/unreleased/243444-user-cannot-sign-out-of-gitlab-once-admin-resets-their-password.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users with expired passwords to sign out
+merge_request: 40830
+author:
+type: fixed
diff --git a/changelogs/unreleased/24629.yml b/changelogs/unreleased/24629.yml
new file mode 100644
index 00000000000..16cc6d0db43
--- /dev/null
+++ b/changelogs/unreleased/24629.yml
@@ -0,0 +1,5 @@
+---
+title: Highlight un-focused/un-viewed file's in file tree
+merge_request: 27937
+author:
+type: changed
diff --git a/changelogs/unreleased/bump-ado-image-to-v1-0-2.yml b/changelogs/unreleased/bump-ado-image-to-v1-0-2.yml
new file mode 100644
index 00000000000..d24e9a1e6c3
--- /dev/null
+++ b/changelogs/unreleased/bump-ado-image-to-v1-0-2.yml
@@ -0,0 +1,5 @@
+---
+title: Fix auto-deploy-image external chart dependencies
+merge_request: 40730
+author:
+type: fixed
diff --git a/changelogs/unreleased/jdb-fix-diffs-viewer-max-lines.yml b/changelogs/unreleased/jdb-fix-diffs-viewer-max-lines.yml
new file mode 100644
index 00000000000..e482ce337e2
--- /dev/null
+++ b/changelogs/unreleased/jdb-fix-diffs-viewer-max-lines.yml
@@ -0,0 +1,5 @@
+---
+title: Fix client usage of max line rendering
+merge_request: 40741
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-prevent-enter-submission.yml b/changelogs/unreleased/nfriend-prevent-enter-submission.yml
new file mode 100644
index 00000000000..05db098c95a
--- /dev/null
+++ b/changelogs/unreleased/nfriend-prevent-enter-submission.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent form submission in search boxes on New Release and Edit Release pages
+merge_request: 40011
+author:
+type: changed
diff --git a/db/migrate/20200826212800_add_index_on_merge_request_id_and_rule_type_to_approval_merge_request_rule.rb b/db/migrate/20200826212800_add_index_on_merge_request_id_and_rule_type_to_approval_merge_request_rule.rb
new file mode 100644
index 00000000000..a40e09648ef
--- /dev/null
+++ b/db/migrate/20200826212800_add_index_on_merge_request_id_and_rule_type_to_approval_merge_request_rule.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddIndexOnMergeRequestIdAndRuleTypeToApprovalMergeRequestRule < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = "approval_mr_rule_index_merge_request_id"
+
+ def up
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ :merge_request_id,
+ name: INDEX_NAME
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name :approval_merge_request_rules, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20200826212800 b/db/schema_migrations/20200826212800
new file mode 100644
index 00000000000..3eaae5c48c8
--- /dev/null
+++ b/db/schema_migrations/20200826212800
@@ -0,0 +1 @@
+360c42f4d34c3b03e7a0375a0ff2776f066888f0a40131180bf301b876ea58db \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 00eae5f4f43..dbf4f29184e 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18945,6 +18945,8 @@ CREATE UNIQUE INDEX any_approver_merge_request_rule_type_unique_index ON public.
CREATE UNIQUE INDEX any_approver_project_rule_type_unique_index ON public.approval_project_rules USING btree (project_id) WHERE (rule_type = 3);
+CREATE INDEX approval_mr_rule_index_merge_request_id ON public.approval_merge_request_rules USING btree (merge_request_id);
+
CREATE UNIQUE INDEX approval_rule_name_index_for_code_owners ON public.approval_merge_request_rules USING btree (merge_request_id, code_owner, name) WHERE ((code_owner = true) AND (section IS NULL));
CREATE UNIQUE INDEX backup_labels_group_id_project_id_title_idx ON public.backup_labels USING btree (group_id, project_id, title);
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 6dff9deb59d..f7b44e74c17 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -106,7 +106,7 @@ end
Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported.
```
-### Alternative: `expect_next_instance_of` or `allow_next_instance_of`
+### Alternative: `expect_next_instance_of`, `allow_next_instance_of`, `expect_next_found_instance_of` or `allow_next_found_instance_of`
Instead of writing:
@@ -130,8 +130,21 @@ end
allow_next_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
+
+# Do this:
+expect_next_found_instance_of(Project) do |project|
+ expect(project).to receive(:add_import_job)
+end
+
+# Do this:
+allow_next_found_instance_of(Project) do |project|
+ allow(project).to receive(:add_import_job)
+end
```
+_**Note:** Since Active Record is not calling the `.new` method on model classes to instantiate the objects,
+you should use `expect_next_found_instance_of` or `allow_next_found_instance_of` mock helpers to setup mock on objects returned by Active Record query & finder methods._
+
If we also want to initialize the instance with some particular arguments, we
could also pass it like:
diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
index f234008dad4..e9d77766db3 100644
--- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
.dast-auto-deploy:
- image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.0"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.2"
dast_environment_deploy:
extends: .dast-auto-deploy
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 76fb2948144..41120750ff4 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
.auto-deploy:
- image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.0"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.2"
dependencies: []
include:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f79b82446e8..04f38ee91fe 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7021,6 +7021,9 @@ msgstr ""
msgid "Could not create group"
msgstr ""
+msgid "Could not create issue"
+msgstr ""
+
msgid "Could not create project"
msgstr ""
@@ -9281,9 +9284,6 @@ msgstr ""
msgid "Enable usage ping"
msgstr ""
-msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
-msgstr ""
-
msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
msgstr ""
@@ -13034,9 +13034,6 @@ msgstr ""
msgid "In %{time_to_now}"
msgstr ""
-msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
-msgstr ""
-
msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
msgstr ""
@@ -21873,6 +21870,9 @@ msgstr ""
msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
+msgid "SecurityReports|Ensure that %{trackingStart}issue tracking%{trackingEnd} is enabled for this project and you have %{permissionsStart}permission to create new issues%{permissionsEnd}."
+msgstr ""
+
msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
msgstr ""
@@ -21924,6 +21924,9 @@ msgstr ""
msgid "SecurityReports|Project"
msgstr ""
+msgid "SecurityReports|Project was not found or you do not have permission to add this project to Security Dashboards."
+msgstr ""
+
msgid "SecurityReports|Projects added"
msgstr ""
@@ -21999,7 +22002,7 @@ msgstr ""
msgid "SecurityReports|To widen your search, change or remove filters above"
msgstr ""
-msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}: %{errorMessage}"
msgstr ""
msgid "SecurityReports|Unable to add %{invalidProjects}"
@@ -25974,6 +25977,9 @@ msgstr ""
msgid "To view all %{scannedResourcesCount} scanned URLs, please download the CSV file"
msgstr ""
+msgid "To view instance-level analytics, ask an admin to turn on %{docLinkStart}usage ping%{docLinkEnd}."
+msgstr ""
+
msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
msgstr ""
@@ -26283,6 +26289,9 @@ msgstr ""
msgid "Turn on usage ping"
msgstr ""
+msgid "Turn on usage ping to review instance-level analytics."
+msgstr ""
+
msgid "Twitter"
msgstr ""
@@ -26760,7 +26769,7 @@ msgstr ""
msgid "Usage"
msgstr ""
-msgid "Usage ping is not enabled"
+msgid "Usage ping is off"
msgstr ""
msgid "Usage statistics"
@@ -27547,9 +27556,6 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to unlink the issue. Please try again later."
msgstr ""
-msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
-msgstr ""
-
msgid "VulnerabilityManagement|Something went wrong, could not get user."
msgstr ""
@@ -29534,9 +29540,15 @@ msgstr ""
msgid "mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} to create one."
msgstr ""
+msgid "mrWidget|A new merge train has started and this merge request is the first of the queue."
+msgstr ""
+
msgid "mrWidget|Added to the merge train by"
msgstr ""
+msgid "mrWidget|Added to the merge train. There are %{mergeTrainPosition} merge requests waiting to be merged"
+msgstr ""
+
msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr ""
@@ -29624,9 +29636,6 @@ msgstr ""
msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
msgstr ""
-msgid "mrWidget|In the merge train at position %{mergeTrainPosition}"
-msgstr ""
-
msgid "mrWidget|Jump to first unresolved thread"
msgstr ""
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 2eefe6771f1..7754fac2fb2 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -6,11 +6,11 @@ RSpec.describe SessionsController do
include DeviseHelpers
include LdapHelpers
- describe '#new' do
- before do
- set_devise_mapping(context: @request)
- end
+ before do
+ set_devise_mapping(context: @request)
+ end
+ describe '#new' do
context 'when auto sign-in is enabled' do
before do
stub_omniauth_setting(auto_sign_in_with_provider: :saml)
@@ -59,13 +59,19 @@ RSpec.describe SessionsController do
end
end
end
- end
- describe '#create' do
- before do
- set_devise_mapping(context: @request)
+ it "redirects correctly for referer on same host with params" do
+ host = "test.host"
+ search_path = "/search?search=seed_project"
+ request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}"
+
+ get(:new, params: { redirect_to_referer: :yes })
+
+ expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
+ end
+ describe '#create' do
it_behaves_like 'known sign in' do
let(:user) { create(:user) }
let(:post_action) { post(:create, params: { user: { login: user.username, password: user.password } }) }
@@ -439,25 +445,8 @@ RSpec.describe SessionsController do
end
end
- describe "#new" do
- before do
- set_devise_mapping(context: @request)
- end
-
- it "redirects correctly for referer on same host with params" do
- host = "test.host"
- search_path = "/search?search=seed_project"
- request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}"
-
- get(:new, params: { redirect_to_referer: :yes })
-
- expect(controller.stored_location_for(:redirect)).to eq(search_path)
- end
- end
-
context 'when login fails' do
before do
- set_devise_mapping(context: @request)
@request.env["warden.options"] = { action: 'unauthenticated' }
end
@@ -471,10 +460,6 @@ RSpec.describe SessionsController do
describe '#set_current_context' do
let_it_be(:user) { create(:user) }
- before do
- set_devise_mapping(context: @request)
- end
-
context 'when signed in' do
before do
sign_in(user)
@@ -528,4 +513,21 @@ RSpec.describe SessionsController do
end
end
end
+
+ describe '#destroy' do
+ before do
+ sign_in(user)
+ end
+
+ context 'for a user whose password has expired' do
+ let(:user) { create(:user, password_expires_at: 2.days.ago) }
+
+ it 'allows to sign out successfully' do
+ delete :destroy
+
+ expect(response).to redirect_to(new_user_session_path)
+ expect(controller.current_user).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/features/admin/admin_dev_ops_score_spec.rb b/spec/features/admin/admin_dev_ops_score_spec.rb
index 31a2b4bbe72..1d3e7deef65 100644
--- a/spec/features/admin/admin_dev_ops_score_spec.rb
+++ b/spec/features/admin/admin_dev_ops_score_spec.rb
@@ -22,10 +22,10 @@ RSpec.describe 'DevOps Report page' do
stub_application_setting(usage_ping_enabled: false)
end
- it 'shows empty state' do
+ it 'shows empty state', :js do
visit admin_dev_ops_score_path
- expect(page).to have_content('Usage ping is not enabled')
+ expect(page).to have_selector(".js-empty-state")
end
it 'hides the intro callout' do
diff --git a/spec/frontend/__mocks__/lodash/debounce.js b/spec/frontend/__mocks__/lodash/debounce.js
index 97fdb39097a..e8b61c80147 100644
--- a/spec/frontend/__mocks__/lodash/debounce.js
+++ b/spec/frontend/__mocks__/lodash/debounce.js
@@ -8,4 +8,15 @@
// [2]: https://gitlab.com/gitlab-org/gitlab/-/issues/213378
// Further reference: https://github.com/facebook/jest/issues/3465
-export default fn => fn;
+export default fn => {
+ const debouncedFn = jest.fn().mockImplementation(fn);
+ debouncedFn.cancel = jest.fn();
+ debouncedFn.flush = jest.fn().mockImplementation(() => {
+ const errorMessage =
+ "The .flush() method returned by lodash.debounce is not yet implemented/mocked by the mock in 'spec/frontend/__mocks__/lodash/debounce.js'.";
+
+ throw new Error(errorMessage);
+ });
+
+ return debouncedFn;
+};
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index 5b5edcb2a34..1d8e1740a9e 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -44,6 +44,7 @@ describe('DiffFileHeader component', () => {
toggleFileDiscussions: jest.fn(),
toggleFileDiscussionWrappers: jest.fn(),
toggleFullDiff: jest.fn(),
+ toggleActiveFileByHash: jest.fn(),
},
},
},
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index afdd4bfb335..23adc8f9da4 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -7,9 +7,12 @@ import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
describe('Diff File Row component', () => {
let wrapper;
- const createComponent = (props = {}) => {
+ const createComponent = (props = {}, highlightCurrentDiffRow = false) => {
wrapper = shallowMount(DiffFileRow, {
propsData: { ...props },
+ provide: {
+ glFeatures: { highlightCurrentDiffRow },
+ },
});
};
@@ -56,6 +59,31 @@ describe('Diff File Row component', () => {
);
});
+ it.each`
+ features | fileType | isViewed | expected
+ ${{ highlightCurrentDiffRow: true }} | ${'blob'} | ${false} | ${'gl-font-weight-bold'}
+ ${{}} | ${'blob'} | ${true} | ${''}
+ ${{}} | ${'tree'} | ${false} | ${''}
+ ${{}} | ${'tree'} | ${true} | ${''}
+ `(
+ 'with (features="$features", fileType="$fileType", isViewed=$isViewed), sets fileClasses="$expected"',
+ ({ features, fileType, isViewed, expected }) => {
+ createComponent(
+ {
+ file: {
+ type: fileType,
+ fileHash: '#123456789',
+ },
+ level: 0,
+ hideFileStats: false,
+ viewedFiles: isViewed ? { '#123456789': true } : {},
+ },
+ features.highlightCurrentDiffRow,
+ );
+ expect(wrapper.find(FileRow).props('fileClasses')).toBe(expected);
+ },
+ );
+
describe('FileRowStats components', () => {
it.each`
type | hideFileStats | value | desc
diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index 14cb2a17aec..cc177a81d88 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -1,16 +1,26 @@
import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import TreeList from '~/diffs/components/tree_list.vue';
import createStore from '~/diffs/store/modules';
+import FileTree from '~/vue_shared/components/file_tree.vue';
describe('Diffs tree list component', () => {
let wrapper;
+ let store;
const getFileRows = () => wrapper.findAll('.file-row');
const localVue = createLocalVue();
localVue.use(Vuex);
- const createComponent = state => {
- const store = new Vuex.Store({
+ const createComponent = (mountFn = mount) => {
+ wrapper = mountFn(TreeList, {
+ store,
+ localVue,
+ propsData: { hideFileStats: false },
+ });
+ };
+
+ beforeEach(() => {
+ store = new Vuex.Store({
modules: {
diffs: createStore(),
},
@@ -23,61 +33,57 @@ describe('Diffs tree list component', () => {
addedLines: 10,
removedLines: 20,
...store.state.diffs,
- ...state,
};
+ });
- wrapper = mount(TreeList, {
- store,
- localVue,
- propsData: { hideFileStats: false },
+ const setupFilesInState = () => {
+ const treeEntries = {
+ 'index.js': {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'index.js',
+ name: 'index.js',
+ path: 'app/index.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ parentPath: 'app',
+ },
+ app: {
+ key: 'app',
+ path: 'app',
+ name: 'app',
+ type: 'tree',
+ tree: [],
+ },
+ };
+
+ Object.assign(store.state.diffs, {
+ treeEntries,
+ tree: [treeEntries['index.js'], treeEntries.app],
});
};
- beforeEach(() => {
- localStorage.removeItem('mr_diff_tree_list');
-
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
- it('renders empty text', () => {
- expect(wrapper.text()).toContain('No files found');
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders empty text', () => {
+ expect(wrapper.text()).toContain('No files found');
+ });
});
describe('with files', () => {
beforeEach(() => {
- const treeEntries = {
- 'index.js': {
- addedLines: 0,
- changed: true,
- deleted: false,
- fileHash: 'test',
- key: 'index.js',
- name: 'index.js',
- path: 'app/index.js',
- removedLines: 0,
- tempFile: true,
- type: 'blob',
- parentPath: 'app',
- },
- app: {
- key: 'app',
- path: 'app',
- name: 'app',
- type: 'tree',
- tree: [],
- },
- };
-
- createComponent({
- treeEntries,
- tree: [treeEntries['index.js'], treeEntries.app],
- });
-
- return wrapper.vm.$nextTick();
+ setupFilesInState();
+ createComponent();
});
it('renders tree', () => {
@@ -136,4 +142,23 @@ describe('Diffs tree list component', () => {
});
});
});
+
+ describe('with viewedDiffFileIds', () => {
+ const viewedDiffFileIds = { fileId: '#12345' };
+
+ beforeEach(() => {
+ setupFilesInState();
+ store.state.diffs.viewedDiffFileIds = viewedDiffFileIds;
+ });
+
+ it('passes the viewedDiffFileIds to the FileTree', () => {
+ createComponent(shallowMount);
+
+ return wrapper.vm.$nextTick().then(() => {
+ // Have to use $attrs['viewed-files'] because we are passing down an object
+ // and attributes('') stringifies values (e.g. [object])...
+ expect(wrapper.find(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
+ });
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 858f362d3df..4f647b0cd41 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -191,10 +191,10 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' },
+ { type: types.VIEW_DIFF_FILE, payload: 'test' },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' },
+ { type: types.VIEW_DIFF_FILE, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
],
[],
@@ -300,7 +300,7 @@ describe('DiffsStoreActions', () => {
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
testAction(setHighlightedRow, 'ABC_123', {}, [
{ type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
- { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' },
+ { type: types.VIEW_DIFF_FILE, payload: 'ABC' },
]);
});
});
@@ -904,7 +904,7 @@ describe('DiffsStoreActions', () => {
expect(document.location.hash).toBe('#test');
});
- it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ it('commits VIEW_DIFF_FILE', () => {
const state = {
treeEntries: {
path: {
@@ -915,7 +915,7 @@ describe('DiffsStoreActions', () => {
scrollToFile({ state, commit }, 'path');
- expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test');
+ expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, 'test');
});
});
@@ -1413,7 +1413,7 @@ describe('DiffsStoreActions', () => {
});
describe('setCurrentDiffFileIdFromNote', () => {
- it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ it('commits VIEW_DIFF_FILE', () => {
const commit = jest.fn();
const state = { diffFiles: [{ file_hash: '123' }] };
const rootGetters = {
@@ -1423,10 +1423,10 @@ describe('DiffsStoreActions', () => {
setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
- expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, '123');
+ expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, '123');
});
- it('does not commit UPDATE_CURRENT_DIFF_FILE_ID when discussion has no diff_file', () => {
+ it('does not commit VIEW_DIFF_FILE when discussion has no diff_file', () => {
const commit = jest.fn();
const state = { diffFiles: [{ file_hash: '123' }] };
const rootGetters = {
@@ -1439,7 +1439,7 @@ describe('DiffsStoreActions', () => {
expect(commit).not.toHaveBeenCalled();
});
- it('does not commit UPDATE_CURRENT_DIFF_FILE_ID when diff file does not exist', () => {
+ it('does not commit VIEW_DIFF_FILE when diff file does not exist', () => {
const commit = jest.fn();
const state = { diffFiles: [{ file_hash: '123' }] };
const rootGetters = {
@@ -1454,12 +1454,12 @@ describe('DiffsStoreActions', () => {
});
describe('navigateToDiffFileIndex', () => {
- it('commits UPDATE_CURRENT_DIFF_FILE_ID', done => {
+ it('commits VIEW_DIFF_FILE', done => {
testAction(
navigateToDiffFileIndex,
0,
{ diffFiles: [{ file_hash: '123' }] },
- [{ type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: '123' }],
+ [{ type: types.VIEW_DIFF_FILE, payload: '123' }],
[],
done,
);
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index ce98bc58af7..393f042a9f9 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -737,11 +737,11 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ describe('VIEW_DIFF_FILE', () => {
it('updates currentDiffFileId', () => {
const state = createState();
- mutations[types.UPDATE_CURRENT_DIFF_FILE_ID](state, 'somefileid');
+ mutations[types.VIEW_DIFF_FILE](state, 'somefileid');
expect(state.currentDiffFileId).toBe('somefileid');
});
diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js
index 86cdbafaff9..7f083fa7c25 100644
--- a/spec/frontend/ide/components/pipelines/list_spec.js
+++ b/spec/frontend/ide/components/pipelines/list_spec.js
@@ -22,11 +22,11 @@ describe('IDE pipelines list', () => {
const defaultState = {
links: { ciHelpPagePath: TEST_HOST },
pipelinesEmptyStateSvgPath: TEST_HOST,
- pipelines: {
- stages: [],
- failedStages: [],
- isLoadingJobs: false,
- },
+ };
+ const defaultPipelinesState = {
+ stages: [],
+ failedStages: [],
+ isLoadingJobs: false,
};
const fetchLatestPipelineMock = jest.fn();
@@ -34,23 +34,20 @@ describe('IDE pipelines list', () => {
const failedStagesGetterMock = jest.fn().mockReturnValue([]);
const fakeProjectPath = 'alpha/beta';
- const createComponent = (state = {}) => {
- const { pipelines: pipelinesState, ...restOfState } = state;
- const { defaultPipelines, ...defaultRestOfState } = defaultState;
-
- const fakeStore = new Vuex.Store({
+ const createStore = (rootState, pipelinesState) => {
+ return new Vuex.Store({
getters: {
currentProject: () => ({ web_url: 'some/url ', path_with_namespace: fakeProjectPath }),
},
state: {
- ...defaultRestOfState,
- ...restOfState,
+ ...defaultState,
+ ...rootState,
},
modules: {
pipelines: {
namespaced: true,
state: {
- ...defaultPipelines,
+ ...defaultPipelinesState,
...pipelinesState,
},
actions: {
@@ -69,10 +66,12 @@ describe('IDE pipelines list', () => {
},
},
});
+ };
+ const createComponent = (state = {}, pipelinesState = {}) => {
wrapper = shallowMount(List, {
localVue,
- store: fakeStore,
+ store: createStore(state, pipelinesState),
});
};
@@ -94,31 +93,33 @@ describe('IDE pipelines list', () => {
describe('when loading', () => {
let defaultPipelinesLoadingState;
+
beforeAll(() => {
defaultPipelinesLoadingState = {
- ...defaultState.pipelines,
isLoadingPipeline: true,
};
});
it('does not render when pipeline has loaded before', () => {
- createComponent({
- pipelines: {
+ createComponent(
+ {},
+ {
...defaultPipelinesLoadingState,
hasLoadedPipeline: true,
},
- });
+ );
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
it('renders loading state', () => {
- createComponent({
- pipelines: {
+ createComponent(
+ {},
+ {
...defaultPipelinesLoadingState,
hasLoadedPipeline: false,
},
- });
+ );
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
@@ -126,21 +127,22 @@ describe('IDE pipelines list', () => {
describe('when loaded', () => {
let defaultPipelinesLoadedState;
+
beforeAll(() => {
defaultPipelinesLoadedState = {
- ...defaultState.pipelines,
isLoadingPipeline: false,
hasLoadedPipeline: true,
};
});
it('renders empty state when no latestPipeline', () => {
- createComponent({ pipelines: { ...defaultPipelinesLoadedState, latestPipeline: null } });
+ createComponent({}, { ...defaultPipelinesLoadedState, latestPipeline: null });
expect(wrapper.element).toMatchSnapshot();
});
describe('with latest pipeline loaded', () => {
let withLatestPipelineState;
+
beforeAll(() => {
withLatestPipelineState = {
...defaultPipelinesLoadedState,
@@ -149,12 +151,12 @@ describe('IDE pipelines list', () => {
});
it('renders ci icon', () => {
- createComponent({ pipelines: withLatestPipelineState });
+ createComponent({}, withLatestPipelineState);
expect(wrapper.find(CiIcon).exists()).toBe(true);
});
it('renders pipeline data', () => {
- createComponent({ pipelines: withLatestPipelineState });
+ createComponent({}, withLatestPipelineState);
expect(wrapper.text()).toContain('#1');
});
@@ -162,7 +164,7 @@ describe('IDE pipelines list', () => {
it('renders list of jobs', () => {
const stages = [];
const isLoadingJobs = true;
- createComponent({ pipelines: { ...withLatestPipelineState, stages, isLoadingJobs } });
+ createComponent({}, { ...withLatestPipelineState, stages, isLoadingJobs });
const jobProps = wrapper
.findAll(Tab)
@@ -177,7 +179,7 @@ describe('IDE pipelines list', () => {
const failedStages = [];
failedStagesGetterMock.mockReset().mockReturnValue(failedStages);
const isLoadingJobs = true;
- createComponent({ pipelines: { ...withLatestPipelineState, isLoadingJobs } });
+ createComponent({}, { ...withLatestPipelineState, isLoadingJobs });
const jobProps = wrapper
.findAll(Tab)
@@ -191,12 +193,13 @@ describe('IDE pipelines list', () => {
describe('with YAML error', () => {
it('renders YAML error', () => {
const yamlError = 'test yaml error';
- createComponent({
- pipelines: {
+ createComponent(
+ {},
+ {
...defaultPipelinesLoadedState,
latestPipeline: { ...pipelines[0], yamlError },
},
- });
+ );
expect(wrapper.text()).toContain('Found errors in your .gitlab-ci.yml:');
expect(wrapper.text()).toContain(yamlError);
diff --git a/spec/frontend/milestones/project_milestone_combobox_spec.js b/spec/frontend/milestones/project_milestone_combobox_spec.js
index 2265c9bdc2e..4859561c4e3 100644
--- a/spec/frontend/milestones/project_milestone_combobox_spec.js
+++ b/spec/frontend/milestones/project_milestone_combobox_spec.js
@@ -2,10 +2,12 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import { GlNewDropdown, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { ENTER_KEY } from '~/lib/utils/keys';
import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue';
import { milestones as projectMilestones } from './mock_data';
const TEST_SEARCH_ENDPOINT = '/api/v4/projects/8/search';
+const TEST_SEARCH = 'TEST_SEARCH';
const extraLinks = [
{ text: 'Create new', url: 'http://127.0.0.1:3000/h5bp/html5-boilerplate/-/milestones/new' },
@@ -21,6 +23,8 @@ describe('Milestone selector', () => {
const findNoResultsMessage = () => wrapper.find({ ref: 'noResults' });
+ const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+
const factory = (options = {}) => {
wrapper = shallowMount(MilestoneCombobox, {
...options,
@@ -63,7 +67,7 @@ describe('Milestone selector', () => {
describe('before results', () => {
it('should show a loading icon', () => {
const request = mock.onGet(TEST_SEARCH_ENDPOINT, {
- params: { search: 'TEST_SEARCH', scope: 'milestones' },
+ params: { search: TEST_SEARCH, scope: 'milestones' },
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
@@ -85,9 +89,9 @@ describe('Milestone selector', () => {
describe('with empty results', () => {
beforeEach(() => {
mock
- .onGet(TEST_SEARCH_ENDPOINT, { params: { search: 'TEST_SEARCH', scope: 'milestones' } })
+ .onGet(TEST_SEARCH_ENDPOINT, { params: { search: TEST_SEARCH, scope: 'milestones' } })
.reply(200, []);
- wrapper.find(GlSearchBoxByType).vm.$emit('input', 'TEST_SEARCH');
+ findSearchBox().vm.$emit('input', TEST_SEARCH);
return axios.waitForAll();
});
@@ -116,7 +120,7 @@ describe('Milestone selector', () => {
web_url: 'http://127.0.0.1:3000/h5bp/html5-boilerplate/-/milestones/6',
},
]);
- wrapper.find(GlSearchBoxByType).vm.$emit('input', 'v0.1');
+ findSearchBox().vm.$emit('input', 'v0.1');
return axios.waitForAll().then(() => {
items = wrapper.findAll('[role="milestone option"]');
});
@@ -147,4 +151,36 @@ describe('Milestone selector', () => {
expect(findNoResultsMessage().exists()).toBe(false);
});
});
+
+ describe('when Enter is pressed', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ projectId,
+ preselectedMilestones,
+ extraLinks,
+ },
+ data() {
+ return {
+ searchQuery: 'TEST_SEARCH',
+ };
+ },
+ });
+
+ mock
+ .onGet(TEST_SEARCH_ENDPOINT, { params: { search: 'TEST_SEARCH', scope: 'milestones' } })
+ .reply(200, []);
+ });
+
+ it('should trigger a search', async () => {
+ mock.resetHistory();
+
+ findSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+
+ await axios.waitForAll();
+
+ expect(mock.history.get.length).toBe(1);
+ expect(mock.history.get[0].url).toBe(TEST_SEARCH_ENDPOINT);
+ });
+ });
});
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 1556f5b19dc..6490bdcf038 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import { GlLoadingIcon, GlSearchBoxByType, GlNewDropdownItem, GlIcon } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
import { sprintf } from '~/locale';
+import { ENTER_KEY } from '~/lib/utils/keys';
import RefSelector from '~/ref/components/ref_selector.vue';
import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
import createStore from '~/ref/stores/';
@@ -83,6 +84,8 @@ describe('Ref selector component', () => {
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+
const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
const findBranchDropdownItems = () => findBranchesSection().findAll(GlNewDropdownItem);
const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
@@ -120,7 +123,7 @@ describe('Ref selector component', () => {
// Convenience methods
//
const updateQuery = newQuery => {
- wrapper.find(GlSearchBoxByType).vm.$emit('input', newQuery);
+ findSearchBox().vm.$emit('input', newQuery);
};
const selectFirstBranch = () => {
@@ -244,6 +247,23 @@ describe('Ref selector component', () => {
});
});
+ describe('when the Enter is pressed', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests({ andClearMocks: true });
+ });
+
+ it('requeries the endpoints when Enter is pressed', () => {
+ findSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
describe('when no results are found', () => {
beforeEach(() => {
branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index f4be911171e..33c2f641ada 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -10,7 +10,7 @@ import UnsavedChangesConfirmDialog from '~/static_site_editor/components/unsaved
import {
sourceContentTitle as title,
- sourceContent as content,
+ sourceContentYAML as content,
sourceContentBody as body,
returnUrl,
} from '../mock_data';
diff --git a/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js b/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js
index 8504d09e0f1..24651543650 100644
--- a/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js
+++ b/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js
@@ -5,7 +5,7 @@ import {
projectId,
sourcePath,
sourceContentTitle as title,
- sourceContent as content,
+ sourceContentYAML as content,
} from '../../mock_data';
jest.mock('~/static_site_editor/services/load_source_content', () => jest.fn());
diff --git a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
index 515b5394594..750b777cf5d 100644
--- a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js
@@ -6,7 +6,7 @@ import {
projectId as project,
sourcePath,
username,
- sourceContent as content,
+ sourceContentYAML as content,
savedContentMeta,
} from '../../mock_data';
diff --git a/spec/frontend/static_site_editor/mock_data.js b/spec/frontend/static_site_editor/mock_data.js
index 96de9b73af0..29a6fa23d2f 100644
--- a/spec/frontend/static_site_editor/mock_data.js
+++ b/spec/frontend/static_site_editor/mock_data.js
@@ -1,8 +1,18 @@
-export const sourceContentHeader = `---
+export const sourceContentHeaderYAML = `---
layout: handbook-page-toc
title: Handbook
twitter_image: '/images/tweets/handbook-gitlab.png'
---`;
+export const sourceContentHeaderTOML = `+++
+layout: "handbook-page-toc"
+title: "Handbook"
+twitter_image: "/images/tweets/handbook-gitlab.png"
++++`;
+export const sourceContentHeaderJSON = `{
+"layout": "handbook-page-toc",
+"title": "Handbook",
+"twitter_image": "/images/tweets/handbook-gitlab.png",
+}`;
export const sourceContentSpacing = `
`;
export const sourceContentBody = `## On this page
@@ -13,7 +23,9 @@ export const sourceContentBody = `## On this page
![image](path/to/image1.png)
`;
-export const sourceContent = `${sourceContentHeader}${sourceContentSpacing}${sourceContentBody}`;
+export const sourceContentYAML = `${sourceContentHeaderYAML}${sourceContentSpacing}${sourceContentBody}`;
+export const sourceContentTOML = `${sourceContentHeaderTOML}${sourceContentSpacing}${sourceContentBody}`;
+export const sourceContentJSON = `${sourceContentHeaderJSON}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser';
diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js
index c5473596df8..41f8a1075c0 100644
--- a/spec/frontend/static_site_editor/pages/home_spec.js
+++ b/spec/frontend/static_site_editor/pages/home_spec.js
@@ -13,7 +13,7 @@ import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constant
import {
projectId as project,
returnUrl,
- sourceContent as content,
+ sourceContentYAML as content,
sourceContentTitle as title,
sourcePath,
username,
diff --git a/spec/frontend/static_site_editor/services/load_source_content_spec.js b/spec/frontend/static_site_editor/services/load_source_content_spec.js
index 87893bb7a6e..54061b7a503 100644
--- a/spec/frontend/static_site_editor/services/load_source_content_spec.js
+++ b/spec/frontend/static_site_editor/services/load_source_content_spec.js
@@ -2,7 +2,12 @@ import Api from '~/api';
import loadSourceContent from '~/static_site_editor/services/load_source_content';
-import { sourceContent, sourceContentTitle, projectId, sourcePath } from '../mock_data';
+import {
+ sourceContentYAML as sourceContent,
+ sourceContentTitle,
+ projectId,
+ sourcePath,
+} from '../mock_data';
describe('loadSourceContent', () => {
describe('requesting source content succeeds', () => {
diff --git a/spec/frontend/static_site_editor/services/parse_source_file_language_support_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_language_support_spec.js
new file mode 100644
index 00000000000..9bc706c31d6
--- /dev/null
+++ b/spec/frontend/static_site_editor/services/parse_source_file_language_support_spec.js
@@ -0,0 +1,20 @@
+import getFrontMatterLanguageDefinition from '~/static_site_editor/services/parse_source_file_language_support';
+
+describe('static_site_editor/services/parse_source_file_language_support', () => {
+ describe('getFrontMatterLanguageDefinition', () => {
+ it.each`
+ languageName
+ ${'yaml'}
+ ${'toml'}
+ ${'json'}
+ ${'abcd'}
+ `('returns $hasMatch when provided $languageName', ({ languageName }) => {
+ try {
+ const definition = getFrontMatterLanguageDefinition(languageName);
+ expect(definition.name).toBe(languageName);
+ } catch (error) {
+ expect(error.message).toBe(`Unsupported front matter language: ${languageName}`);
+ }
+ });
+ });
+});
diff --git a/spec/frontend/static_site_editor/services/parse_source_file_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
index 19d1a21991f..6d55bed6721 100644
--- a/spec/frontend/static_site_editor/services/parse_source_file_spec.js
+++ b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
@@ -1,12 +1,16 @@
import {
- sourceContent as content,
- sourceContentHeader as frontMatter,
+ sourceContentYAML as content,
+ sourceContentTOML as tomlContent,
+ sourceContentJSON as jsonContent,
+ sourceContentHeaderYAML as yamlFrontMatter,
+ sourceContentHeaderTOML as tomlFrontMatter,
+ sourceContentHeaderJSON as jsonFrontMatter,
sourceContentBody as body,
} from '../mock_data';
import parseSourceFile from '~/static_site_editor/services/parse_source_file';
-describe('parseSourceFile', () => {
+describe('static_site_editor/services/parse_source_file', () => {
const contentComplex = [content, content, content].join('');
const complexBody = [body, content, content].join('');
const edit = 'and more';
@@ -14,13 +18,22 @@ describe('parseSourceFile', () => {
const newContentComplex = `${contentComplex} ${edit}`;
describe('unmodified front matter', () => {
+ const yamlOptions = { frontMatterLanguage: 'yaml' };
+
it.each`
- parsedSource
- ${parseSourceFile(content)}
- ${parseSourceFile(contentComplex)}
- `('returns the correct front matter when queried', ({ parsedSource }) => {
- expect(parsedSource.frontMatter()).toBe(frontMatter);
- });
+ parsedSource | targetFrontMatter
+ ${parseSourceFile(content)} | ${yamlFrontMatter}
+ ${parseSourceFile(contentComplex)} | ${yamlFrontMatter}
+ ${parseSourceFile(content, yamlOptions)} | ${yamlFrontMatter}
+ ${parseSourceFile(contentComplex, yamlOptions)} | ${yamlFrontMatter}
+ ${parseSourceFile(tomlContent, { frontMatterLanguage: 'toml' })} | ${tomlFrontMatter}
+ ${parseSourceFile(jsonContent, { frontMatterLanguage: 'json' })} | ${jsonFrontMatter}
+ `(
+ 'returns $targetFrontMatter when frontMatter queried',
+ ({ parsedSource, targetFrontMatter }) => {
+ expect(parsedSource.frontMatter()).toBe(targetFrontMatter);
+ },
+ );
});
describe('unmodified content', () => {
@@ -49,9 +62,12 @@ describe('parseSourceFile', () => {
});
describe('modified front matter', () => {
- const newFrontMatter = '---\nnewKey: newVal\n---';
- const contentWithNewFrontMatter = content.replace(frontMatter, newFrontMatter);
- const contentComplexWithNewFrontMatter = contentComplex.replace(frontMatter, newFrontMatter);
+ const newYamlFrontMatter = '---\nnewKey: newVal\n---';
+ const contentWithNewFrontMatter = content.replace(yamlFrontMatter, newYamlFrontMatter);
+ const contentComplexWithNewFrontMatter = contentComplex.replace(
+ yamlFrontMatter,
+ newYamlFrontMatter,
+ );
it.each`
parsedSource | targetContent
@@ -60,11 +76,11 @@ describe('parseSourceFile', () => {
`(
'returns the correct front matter and modified content',
({ parsedSource, targetContent }) => {
- expect(parsedSource.frontMatter()).toBe(frontMatter);
+ expect(parsedSource.frontMatter()).toBe(yamlFrontMatter);
- parsedSource.setFrontMatter(newFrontMatter);
+ parsedSource.setFrontMatter(newYamlFrontMatter);
- expect(parsedSource.frontMatter()).toBe(newFrontMatter);
+ expect(parsedSource.frontMatter()).toBe(newYamlFrontMatter);
expect(parsedSource.content()).toBe(targetContent);
},
);
diff --git a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
index 645ccedf7e7..d464e6b1895 100644
--- a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
@@ -20,7 +20,7 @@ import {
commitMultipleResponse,
createMergeRequestResponse,
sourcePath,
- sourceContent as content,
+ sourceContentYAML as content,
trackingCategory,
images,
} from '../mock_data';
diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js
index c5917249f0a..d28c35d26bf 100644
--- a/spec/frontend/vue_shared/components/file_row_spec.js
+++ b/spec/frontend/vue_shared/components/file_row_spec.js
@@ -139,4 +139,16 @@ describe('File row component', () => {
expect(wrapper.vm.hasUrlAtCurrentRoute()).toBe(true);
});
+
+ it('render with the correct file classes prop', () => {
+ createComponent({
+ file: {
+ ...file(),
+ },
+ level: 0,
+ fileClasses: 'font-weight-bold',
+ });
+
+ expect(wrapper.find('.file-row-name').classes()).toContain('font-weight-bold');
+ });
});
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index ff6d448561d..0b9c06d9737 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1196,7 +1196,7 @@ RSpec.describe Issue do
it 'schedules rebalancing if we time-out when finding a gap' do
lhs = build_stubbed(:issue, relative_position: 99, project: project)
to_move = build(:issue, project: project)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
expect { to_move.move_between(lhs, issue) }.to raise_error(ActiveRecord::QueryCanceled)
end
@@ -1205,7 +1205,7 @@ RSpec.describe Issue do
describe '#find_next_gap_after' do
it 'schedules rebalancing if we time-out when finding a gap' do
allow(issue).to receive(:find_next_gap) { raise ActiveRecord::QueryCanceled }
- expect(IssueRebalancingWorker).to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
expect { issue.move_sequence_after }.to raise_error(ActiveRecord::QueryCanceled)
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 004c37a0a7a..4edbbf4d4d8 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -77,7 +77,7 @@ RSpec.describe Issues::CreateService do
it 'rebalances if needed' do
create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(Integer)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
end
@@ -86,7 +86,7 @@ RSpec.describe Issues::CreateService do
stub_feature_flags(rebalance_issues: false)
create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).not_to receive(:perform_async).with(Integer)
+ expect(IssueRebalancingWorker).not_to receive(:perform_async)
expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
end
@@ -95,7 +95,7 @@ RSpec.describe Issues::CreateService do
stub_feature_flags(rebalance_issues: project)
create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(Integer)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index aa9ce4ce1a9..1fed6094314 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -126,7 +126,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).not_to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).not_to receive(:perform_async)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -142,7 +142,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -156,7 +156,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -170,7 +170,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(issue.id)
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index de4bd178b9d..395930c8cbc 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -115,6 +115,7 @@ RSpec.configure do |config|
config.include StubExperiments
config.include StubGitlabCalls
config.include StubGitlabData
+ config.include NextFoundInstanceOf
config.include NextInstanceOf
config.include TestEnv
config.include Devise::Test::ControllerHelpers, type: :controller
diff --git a/spec/support/helpers/next_found_instance_of.rb b/spec/support/helpers/next_found_instance_of.rb
new file mode 100644
index 00000000000..ff34fcdd1d3
--- /dev/null
+++ b/spec/support/helpers/next_found_instance_of.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module NextFoundInstanceOf
+ ERROR_MESSAGE = 'NextFoundInstanceOf mock helpers can only be used with ActiveRecord targets'
+
+ def expect_next_found_instance_of(klass)
+ check_if_active_record!(klass)
+
+ stub_allocate(expect(klass)) do |expectation|
+ yield(expectation)
+ end
+ end
+
+ def allow_next_found_instance_of(klass)
+ check_if_active_record!(klass)
+
+ stub_allocate(allow(klass)) do |allowance|
+ yield(allowance)
+ end
+ end
+
+ private
+
+ def check_if_active_record!(klass)
+ raise ArgumentError.new(ERROR_MESSAGE) unless klass < ActiveRecord::Base
+ end
+
+ def stub_allocate(target)
+ target.to receive(:allocate).and_wrap_original do |method|
+ method.call.tap { |allocation| yield(allocation) }
+ end
+ end
+end
diff --git a/spec/workers/issue_rebalancing_worker_spec.rb b/spec/workers/issue_rebalancing_worker_spec.rb
index a2e44e74e06..8b0fcd4bc5a 100644
--- a/spec/workers/issue_rebalancing_worker_spec.rb
+++ b/spec/workers/issue_rebalancing_worker_spec.rb
@@ -10,23 +10,30 @@ RSpec.describe IssueRebalancingWorker do
service = double(execute: nil)
expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service)
- described_class.new.perform(issue.id)
+ described_class.new.perform(nil, issue.project_id)
end
it 'anticipates the inability to find the issue' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ActiveRecord::RecordNotFound, include(issue_id: -1))
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ActiveRecord::RecordNotFound, include(project_id: -1))
expect(IssueRebalancingService).not_to receive(:new)
- described_class.new.perform(-1)
+ described_class.new.perform(nil, -1)
end
it 'anticipates there being too many issues' do
service = double
allow(service).to receive(:execute) { raise IssueRebalancingService::TooManyIssues }
expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service)
- expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(issue_id: issue.id))
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(project_id: issue.project_id))
- described_class.new.perform(issue.id)
+ described_class.new.perform(nil, issue.project_id)
+ end
+
+ it 'takes no action if the value is nil' do
+ expect(IssueRebalancingService).not_to receive(:new)
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ described_class.new.perform(nil, nil)
end
end
end
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 76702ee0ffc..86b6d041e5c 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -54,13 +54,9 @@ RSpec.describe NewNoteWorker do
let(:note) { create(:note) }
before do
- # TODO: `allow_next_instance_of` helper method is not working
- # because ActiveRecord is directly calling `.allocate` on model
- # classes and bypasses the `.new` method call.
- # Fix the `allow_next_instance_of` helper and change these to mock
- # the next instance of `Note` model class.
- allow(Note).to receive(:find_by).with(id: note.id).and_return(note)
- allow(note).to receive(:skip_notification?).and_return(true)
+ allow_next_found_instance_of(Note) do |note|
+ allow(note).to receive(:skip_notification?).and_return(true)
+ end
end
it 'does not create a new note notification' do