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--.markdownlint.json1
-rw-r--r--app/assets/javascripts/design_management/utils/cache_update.js13
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue51
-rw-r--r--app/assets/javascripts/diffs/store/utils.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue17
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js68
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js10
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss1
-rw-r--r--app/assets/stylesheets/framework/filters.scss8
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/controllers/projects/variables_controller.rb7
-rw-r--r--app/graphql/mutations/design_management/move.rb2
-rw-r--r--app/models/design_management/design.rb2
-rw-r--r--app/models/design_management/design_collection.rb4
-rw-r--r--app/models/project_services/jira_service.rb6
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/presenters/packages/detail/package_presenter.rb1
-rw-r--r--app/services/ci/change_variable_service.rb7
-rw-r--r--app/services/design_management/move_designs_service.rb2
-rw-r--r--app/services/jira_import/cloud_users_mapper_service.rb19
-rw-r--r--app/services/jira_import/server_users_mapper_service.rb19
-rw-r--r--app/services/jira_import/users_importer.rb38
-rw-r--r--app/services/jira_import/users_mapper.rb30
-rw-r--r--app/services/jira_import/users_mapper_service.rb52
-rwxr-xr-xbin/feature-flag43
-rw-r--r--changelogs/unreleased/225946-replace-fa-close-with-gitlab-svg.yml5
-rw-r--r--changelogs/unreleased/232998-package-metadata.yml5
-rw-r--r--changelogs/unreleased/235759_backward_compatibility_on_jira_users_api_endpoint.yml5
-rw-r--r--changelogs/unreleased/235797-fix-issues-analytics-feature-name.yml5
-rw-r--r--changelogs/unreleased/reorder-designs-fixes.yml5
-rw-r--r--changelogs/unreleased/symlink-comments-disable-symlink-comments.yml6
-rw-r--r--changelogs/unreleased/todo-labels.yml5
-rw-r--r--config/feature_flags/development/ci_if_parenthesis_enabled.yml7
-rw-r--r--config/feature_flags/development/ci_plan_needs_size_limit.yml7
-rw-r--r--config/feature_flags/development/reorder_designs.yml2
-rw-r--r--config/initializers/rails_host_authorization.rb2
-rw-r--r--db/post_migrate/20200724130639_backfill_designs_relative_position.rb31
-rw-r--r--doc/api/repositories.md11
-rw-r--r--doc/ci/variables/README.md4
-rw-r--r--doc/ci/yaml/README.md18
-rw-r--r--doc/development/deleting_migrations.md2
-rw-r--r--doc/development/feature_flags/development.md20
-rw-r--r--doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.pngbin41721 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md34
-rw-r--r--lib/api/variables.rb29
-rw-r--r--lib/gitlab/background_migration/backfill_designs_relative_position.rb49
-rw-r--r--lib/gitlab/ci/features.rb4
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--lib/gitlab/usage_data_counters/track_unique_actions.rb2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rw-r--r--spec/bin/feature_flag_spec.rb48
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb4
-rw-r--r--spec/features/projects/issues/design_management/user_uploads_designs_spec.rb28
-rw-r--r--spec/frontend/__mocks__/@toast-ui/vue-editor/index.js11
-rw-r--r--spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap4
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap2
-rw-r--r--spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap6
-rw-r--r--spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/diffs/components/diff_table_cell_spec.js54
-rw-r--r--spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js4
-rw-r--r--spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap2
-rw-r--r--spec/frontend/helpers/filtered_search_spec_helper.js2
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap2
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap2
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap4
-rw-r--r--spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap2
-rw-r--r--spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap4
-rw-r--r--spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js51
-rw-r--r--spec/lib/gitlab/background_migration/backfill_designs_relative_position_spec.rb51
-rw-r--r--spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb13
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb62
-rw-r--r--spec/migrations/backfill_designs_relative_position_spec.rb41
-rw-r--r--spec/models/design_management/design_collection_spec.rb4
-rw-r--r--spec/presenters/packages/detail/package_presenter_spec.rb9
-rw-r--r--spec/services/event_create_service_spec.rb5
-rw-r--r--spec/services/jira_import/cloud_users_mapper_service_spec.rb19
-rw-r--r--spec/services/jira_import/server_users_mapper_service_spec.rb19
-rw-r--r--spec/services/jira_import/users_importer_spec.rb117
-rw-r--r--spec/services/jira_import/users_mapper_spec.rb43
-rw-r--r--spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb39
-rw-r--r--yarn.lock84
94 files changed, 837 insertions, 652 deletions
diff --git a/.markdownlint.json b/.markdownlint.json
index 253b7bd0c92..1c276dc42ba 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -7,6 +7,7 @@
"ul-style": {
"style": "dash"
},
+ "no-trailing-spaces": false,
"line-length": false,
"no-duplicate-header": {
"allow_different_nesting": true
diff --git a/app/assets/javascripts/design_management/utils/cache_update.js b/app/assets/javascripts/design_management/utils/cache_update.js
index 3e6ed39fbe8..5df6e064b25 100644
--- a/app/assets/javascripts/design_management/utils/cache_update.js
+++ b/app/assets/javascripts/design_management/utils/cache_update.js
@@ -1,5 +1,6 @@
/* eslint-disable @gitlab/require-i18n-strings */
+import { groupBy } from 'lodash';
import createFlash from '~/flash';
import { extractCurrentDiscussion, extractDesign } from './design_management_utils';
import {
@@ -159,13 +160,11 @@ const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables
const addNewDesignToStore = (store, designManagementUpload, query) => {
const data = store.readQuery(query);
- const newDesigns = data.project.issue.designCollection.designs.nodes.reduce((acc, design) => {
- if (!acc.find(d => d.filename === design.filename)) {
- acc.push(design);
- }
-
- return acc;
- }, designManagementUpload.designs);
+ const currentDesigns = data.project.issue.designCollection.designs.nodes;
+ const existingDesigns = groupBy(currentDesigns, 'filename');
+ const newDesigns = currentDesigns.concat(
+ designManagementUpload.designs.filter(d => !existingDesigns[d.filename]),
+ );
let newVersionNode;
const findNewVersions = designManagementUpload.designs.find(design => design.versions);
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 741462a849c..087a558efdc 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -147,7 +147,7 @@ export default {
slot="image-overlay"
:discussions="imageDiscussions"
:file-hash="diffFileHash"
- :can-comment="getNoteableData.current_user.can_create_note"
+ :can-comment="getNoteableData.current_user.can_create_note && !diffFile.brokenSymlink"
/>
<div v-if="showNotesContainer" class="note-container">
<user-avatar-link
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 58a4a3981bf..623a4ae1f8f 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -167,6 +167,7 @@ export default {
:id="file.file_hash"
:class="{
'is-active': currentDiffFileId === file.file_hash,
+ 'comments-disabled': Boolean(file.brokenSymlink),
}"
:data-path="file.new_path"
class="diff-file file-holder"
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index 198113e330a..49982a81372 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -1,8 +1,9 @@
<script>
import { mapGetters, mapActions } from 'vuex';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
+import { __ } from '~/locale';
import {
CONTEXT_LINE_TYPE,
LINE_POSITION_RIGHT,
@@ -18,6 +19,9 @@ export default {
DiffGutterAvatars,
GlIcon,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
line: {
type: Object,
@@ -123,6 +127,24 @@ export default {
lineNumber() {
return this.lineType === OLD_LINE_TYPE ? this.line.old_line : this.line.new_line;
},
+ addCommentTooltip() {
+ const brokenSymlinks = this.line.commentsDisabled;
+ let tooltip = __('Add a comment to this line');
+
+ if (brokenSymlinks) {
+ if (brokenSymlinks.wasSymbolic || brokenSymlinks.isSymbolic) {
+ tooltip = __(
+ 'Commenting on symbolic links that replace or are replaced by files is currently not supported.',
+ );
+ } else if (brokenSymlinks.wasReal || brokenSymlinks.isReal) {
+ tooltip = __(
+ 'Commenting on files that replace or are replaced by symbolic links is currently not supported.',
+ );
+ }
+ }
+
+ return tooltip;
+ },
},
mounted() {
this.unwatchShouldShowCommentButton = this.$watch('shouldShowCommentButton', newVal => {
@@ -146,17 +168,24 @@ export default {
<template>
<td ref="td" :class="classNameMap">
- <button
- v-if="shouldRenderCommentButton"
- v-show="shouldShowCommentButton"
- ref="addDiffNoteButton"
- type="button"
- class="add-diff-note js-add-diff-note-button qa-diff-comment"
- title="Add a comment to this line"
- @click="handleCommentButton"
+ <span
+ ref="addNoteTooltip"
+ v-gl-tooltip
+ class="add-diff-note tooltip-wrapper"
+ :title="addCommentTooltip"
>
- <gl-icon :size="12" name="comment" />
- </button>
+ <button
+ v-if="shouldRenderCommentButton"
+ v-show="shouldShowCommentButton"
+ ref="addDiffNoteButton"
+ type="button"
+ class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
+ :disabled="line.commentsDisabled"
+ @click="handleCommentButton"
+ >
+ <gl-icon :size="12" name="comment" />
+ </button>
+ </span>
<a
v-if="lineNumber"
ref="lineNumberRef"
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 2d87008d3b1..f014cddda32 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -480,6 +480,10 @@ export function getDiffPositionByLineCode(diffFiles, useSingleDiffStyle) {
// This method will check whether the discussion is still applicable
// to the diff line in question regarding different versions of the MR
export function isDiscussionApplicableToLine({ discussion, diffPosition, latestDiff }) {
+ if (!diffPosition) {
+ return false;
+ }
+
const { line_code, ...dp } = diffPosition;
// Removing `line_range` from diffPosition because the backend does not
// yet consistently return this property. This check can be removed,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 5298e20557d..f0951f6b177 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,5 +1,5 @@
import VisualTokenValue from './visual_token_value';
-import { objectToQueryString } from '~/lib/utils/common_utils';
+import { objectToQueryString, spriteIcon } from '~/lib/utils/common_utils';
import FilteredSearchContainer from './container';
export default class FilteredSearchVisualTokens {
@@ -84,7 +84,7 @@ export default class FilteredSearchVisualTokens {
<div class="value-container">
<div class="${capitalizeTokenValue ? 'text-capitalize' : ''} value"></div>
<div class="remove-token" role="button">
- <i class="fa fa-close"></i>
+ ${spriteIcon('close', 's16 close-icon')}
</div>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 5cf574e1387..67a8f11b760 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -1,4 +1,5 @@
<script>
+import { GlIcon } from '@gitlab/ui';
import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue';
import TimeTrackingSpentOnlyPane from './spent_only_pane.vue';
@@ -11,6 +12,7 @@ import eventHub from '../../event_hub';
export default {
name: 'IssuableTimeTracker',
components: {
+ GlIcon,
TimeTrackingCollapsedState,
TimeTrackingEstimateOnlyPane,
TimeTrackingSpentOnlyPane,
@@ -111,7 +113,7 @@ export default {
class="close-help-button float-right"
@click="toggleHelpState(false)"
>
- <i class="fa fa-close" aria-hidden="true"> </i>
+ <gl-icon name="close" />
</div>
</div>
<div class="time-tracking-content hide-collapsed">
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
index dd1da847001..c08659919fa 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
@@ -1,13 +1,11 @@
import { __ } from '~/locale';
-import { generateToolbarItem } from './services/editor_service';
-import buildCustomHTMLRenderer from './services/build_custom_renderer';
export const CUSTOM_EVENTS = {
openAddImageModal: 'gl_openAddImageModal',
};
/* eslint-disable @gitlab/require-i18n-strings */
-const TOOLBAR_ITEM_CONFIGS = [
+export const TOOLBAR_ITEM_CONFIGS = [
{ icon: 'heading', event: 'openHeadingSelect', classes: 'tui-heading', tooltip: __('Headings') },
{ icon: 'bold', command: 'Bold', tooltip: __('Add bold text') },
{ icon: 'italic', command: 'Italic', tooltip: __('Add italic text') },
@@ -30,11 +28,6 @@ const TOOLBAR_ITEM_CONFIGS = [
{ icon: 'doc-code', command: 'CodeBlock', tooltip: __('Insert a code block') },
];
-export const EDITOR_OPTIONS = {
- toolbarItems: TOOLBAR_ITEM_CONFIGS.map(config => generateToolbarItem(config)),
- customHTMLRenderer: buildCustomHTMLRenderer(),
-};
-
export const EDITOR_TYPES = {
markdown: 'markdown',
wysiwyg: 'wysiwyg',
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
index baeb98bec75..d96fe46522e 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
@@ -3,16 +3,11 @@ import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css';
import AddImageModal from './modals/add_image/add_image_modal.vue';
-import {
- EDITOR_OPTIONS,
- EDITOR_TYPES,
- EDITOR_HEIGHT,
- EDITOR_PREVIEW_STYLE,
- CUSTOM_EVENTS,
-} from './constants';
+import { EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, CUSTOM_EVENTS } from './constants';
import {
registerHTMLToMarkdownRenderer,
+ getEditorOptions,
addCustomEventListener,
removeCustomEventListener,
addImage,
@@ -35,7 +30,7 @@ export default {
options: {
type: Object,
required: false,
- default: () => EDITOR_OPTIONS,
+ default: () => null,
},
initialEditType: {
type: String,
@@ -65,13 +60,13 @@ export default {
};
},
computed: {
- editorOptions() {
- return { ...EDITOR_OPTIONS, ...this.options };
- },
editorInstance() {
return this.$refs.editor;
},
},
+ created() {
+ this.editorOptions = getEditorOptions(this.options);
+ },
beforeDestroy() {
this.removeListeners();
},
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
index b210ada412d..a9c5d442f62 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
@@ -1,3 +1,4 @@
+import { union, mapValues } from 'lodash';
import renderBlockHtml from './renderers/render_html_block';
import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text';
@@ -19,63 +20,20 @@ const executeRenderer = (renderers, node, context) => {
return availableRenderer ? availableRenderer.render(node, context) : context.origin();
};
-const buildCustomRendererFunctions = (customRenderers, defaults) => {
- const customTypes = Object.keys(customRenderers).filter(type => !defaults[type]);
- const customEntries = customTypes.map(type => {
- const fn = (node, context) => executeRenderer(customRenderers[type], node, context);
- return [type, fn];
- });
-
- return Object.fromEntries(customEntries);
-};
-
-const buildCustomHTMLRenderer = (
- customRenderers = {
- htmlBlock: [],
- htmlInline: [],
- list: [],
- paragraph: [],
- text: [],
- softbreak: [],
- },
-) => {
- const defaults = {
- htmlBlock(node, context) {
- const allHtmlBlockRenderers = [...customRenderers.htmlBlock, ...htmlBlockRenderers];
-
- return executeRenderer(allHtmlBlockRenderers, node, context);
- },
- htmlInline(node, context) {
- const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers];
-
- return executeRenderer(allHtmlInlineRenderers, node, context);
- },
- list(node, context) {
- const allListRenderers = [...customRenderers.list, ...listRenderers];
-
- return executeRenderer(allListRenderers, node, context);
- },
- paragraph(node, context) {
- const allParagraphRenderers = [...customRenderers.paragraph, ...paragraphRenderers];
-
- return executeRenderer(allParagraphRenderers, node, context);
- },
- text(node, context) {
- const allTextRenderers = [...customRenderers.text, ...textRenderers];
-
- return executeRenderer(allTextRenderers, node, context);
- },
- softbreak(node, context) {
- const allSoftbreakRenderers = [...customRenderers.softbreak, ...softbreakRenderers];
-
- return executeRenderer(allSoftbreakRenderers, node, context);
- },
+const buildCustomHTMLRenderer = customRenderers => {
+ const renderersByType = {
+ ...customRenderers,
+ htmlBlock: union(htmlBlockRenderers, customRenderers?.htmlBlock),
+ htmlInline: union(htmlInlineRenderers, customRenderers?.htmlInline),
+ list: union(listRenderers, customRenderers?.list),
+ paragraph: union(paragraphRenderers, customRenderers?.paragraph),
+ text: union(textRenderers, customRenderers?.text),
+ softbreak: union(softbreakRenderers, customRenderers?.softbreak),
};
- return {
- ...buildCustomRendererFunctions(customRenderers, defaults),
- ...defaults,
- };
+ return mapValues(renderersByType, renderers => {
+ return (node, context) => executeRenderer(renderers, node, context);
+ });
};
export default buildCustomHTMLRenderer;
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
index 6436dcaae64..51ba033dff0 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
+import { defaults } from 'lodash';
import ToolbarItem from '../toolbar_item.vue';
import buildHtmlToMarkdownRenderer from './build_html_to_markdown_renderer';
+import buildCustomHTMLRenderer from './build_custom_renderer';
+import { TOOLBAR_ITEM_CONFIGS } from '../constants';
const buildWrapper = propsData => {
const instance = new Vue({
@@ -54,3 +57,10 @@ export const registerHTMLToMarkdownRenderer = editorApi => {
renderer: renderer.constructor.factory(renderer, buildHtmlToMarkdownRenderer(renderer)),
});
};
+
+export const getEditorOptions = externalOptions => {
+ return defaults({
+ customHTMLRenderer: buildCustomHTMLRenderer(externalOptions?.customRenderers),
+ toolbarItems: TOOLBAR_ITEM_CONFIGS.map(toolbarItem => generateToolbarItem(toolbarItem)),
+ });
+};
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index a96a002b879..28bea9233cb 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -93,7 +93,6 @@
}
.fa-remove::before,
-.fa-close::before,
.fa-times::before {
content: '\f00d';
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index ee83ce67c03..2e87724a9e7 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -134,20 +134,20 @@
padding-left: 8px;
padding-right: 0;
- .fa-close {
+ .close-icon {
color: $gl-text-color-secondary;
}
- &:hover .fa-close {
+ &:hover .close-icon {
color: $gl-text-color;
}
&.inverted {
- .fa-close {
+ .close-icon {
color: $gl-text-color-secondary-inverted;
}
- &:hover .fa-close {
+ &:hover .close-icon {
color: $gl-text-color-inverted;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 46ea3c37f2d..e4e54501627 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -820,9 +820,7 @@ $note-form-margin-left: 72px;
}
}
-.add-diff-note {
- @include btn-comment-icon;
- opacity: 0;
+.tooltip-wrapper.add-diff-note {
margin-left: -52px;
position: absolute;
top: 50%;
@@ -830,6 +828,18 @@ $note-form-margin-left: 72px;
z-index: 10;
}
+.note-button.add-diff-note {
+ @include btn-comment-icon;
+ opacity: 0;
+
+ &[disabled] {
+ background: $white;
+ border-color: $gray-200;
+ color: $gl-gray-400;
+ cursor: not-allowed;
+ }
+}
+
.disabled-comment {
background-color: $gray-light;
border-radius: $border-radius-base;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index bbb37479fb0..c6f104a024b 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -143,6 +143,10 @@
margin-bottom: 0;
}
}
+
+ .gl-label-scoped {
+ --label-inset-border: inset 0 0 0 1px currentColor;
+ }
}
@include media-breakpoint-down(sm) {
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 2cc030d18fc..0fd047f90cf 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -12,7 +12,12 @@ class Projects::VariablesController < Projects::ApplicationController
end
def update
- if @project.update(variables_params)
+ update_result = Ci::ChangeVariablesService.new(
+ container: @project, current_user: current_user,
+ params: variables_params
+ ).execute
+
+ if update_result
respond_to do |format|
format.json { render_variables }
end
diff --git a/app/graphql/mutations/design_management/move.rb b/app/graphql/mutations/design_management/move.rb
index c103e596387..0b654447844 100644
--- a/app/graphql/mutations/design_management/move.rb
+++ b/app/graphql/mutations/design_management/move.rb
@@ -21,7 +21,7 @@ module Mutations
description: "The current state of the collection"
def ready(*)
- raise ::Gitlab::Graphql::Errors::ResourceNotAvailable unless ::Feature.enabled?(:reorder_designs)
+ raise ::Gitlab::Graphql::Errors::ResourceNotAvailable unless ::Feature.enabled?(:reorder_designs, default_enabled: true)
end
def resolve(**args)
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index 865cbb995b5..4e50c048611 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -82,7 +82,7 @@ module DesignManagement
scope :ordered, -> (project) do
# TODO: Always order by relative position after the feature flag is removed
# https://gitlab.com/gitlab-org/gitlab/-/issues/34382
- if Feature.enabled?(:reorder_designs, project)
+ if Feature.enabled?(:reorder_designs, project, default_enabled: true)
# We need to additionally sort by `id` to support keyset pagination.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17788/diffs#note_230875678
order(:relative_position, :id)
diff --git a/app/models/design_management/design_collection.rb b/app/models/design_management/design_collection.rb
index 5a4322a2994..96d5f4c2419 100644
--- a/app/models/design_management/design_collection.rb
+++ b/app/models/design_management/design_collection.rb
@@ -16,9 +16,7 @@ module DesignManagement
def find_or_create_design!(filename:)
designs.find { |design| design.filename == filename } ||
- designs.safe_find_or_create_by!(project: project, filename: filename) do |design|
- design.move_to_end
- end
+ designs.safe_find_or_create_by!(project: project, filename: filename)
end
def versions
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index c5d70f3f207..36d7026de30 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -8,6 +8,12 @@ class JiraService < IssueTrackerService
PROJECTS_PER_PAGE = 50
+ # TODO: use jira_service.deployment_type enum when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged
+ DEPLOYMENT_TYPES = {
+ server: 'SERVER',
+ cloud: 'CLOUD'
+ }.freeze
+
validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true
validates :username, presence: true, if: :activated?
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index bdfad994d55..21a73185df4 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -103,7 +103,7 @@ class ProjectPolicy < BasePolicy
with_scope :subject
condition(:moving_designs_disabled) do
- !::Feature.enabled?(:reorder_designs, @subject)
+ !::Feature.enabled?(:reorder_designs, @subject, default_enabled: true)
end
with_scope :subject
diff --git a/app/presenters/packages/detail/package_presenter.rb b/app/presenters/packages/detail/package_presenter.rb
index 631ee173188..bdb2e34854e 100644
--- a/app/presenters/packages/detail/package_presenter.rb
+++ b/app/presenters/packages/detail/package_presenter.rb
@@ -23,6 +23,7 @@ module Packages
package_detail[:maven_metadatum] = @package.maven_metadatum if @package.maven_metadatum
package_detail[:nuget_metadatum] = @package.nuget_metadatum if @package.nuget_metadatum
package_detail[:composer_metadatum] = @package.composer_metadatum if @package.composer_metadatum
+ package_detail[:conan_metadatum] = @package.conan_metadatum if @package.conan_metadatum
package_detail[:dependency_links] = @package.dependency_links.map(&method(:build_dependency_links))
package_detail[:pipeline] = build_pipeline_info(@package.build_info.pipeline) if @package.build_info
diff --git a/app/services/ci/change_variable_service.rb b/app/services/ci/change_variable_service.rb
index 6588ad598f9..f515a335d54 100644
--- a/app/services/ci/change_variable_service.rb
+++ b/app/services/ci/change_variable_service.rb
@@ -20,7 +20,12 @@ module Ci
private
def variable
- container.variables.find_by!(params[:variable_params].slice(:key)) # rubocop:disable CodeReuse/ActiveRecord
+ params[:variable] || find_variable
+ end
+
+ def find_variable
+ identifier = params[:variable_params].slice(:id).presence || params[:variable_params].slice(:key)
+ container.variables.find_by!(identifier) # rubocop:disable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/design_management/move_designs_service.rb b/app/services/design_management/move_designs_service.rb
index 4052c189969..ead41e8f4d4 100644
--- a/app/services/design_management/move_designs_service.rb
+++ b/app/services/design_management/move_designs_service.rb
@@ -13,7 +13,7 @@ module DesignManagement
def execute
return error(:no_focus) unless current_design.present?
- return error(:cannot_move) unless ::Feature.enabled?(:reorder_designs, project)
+ return error(:cannot_move) unless ::Feature.enabled?(:reorder_designs, project, default_enabled: true)
return error(:cannot_move) unless current_user.can?(:move_design, current_design)
return error(:no_neighbors) unless neighbors.present?
return error(:not_distinct) unless all_distinct?
diff --git a/app/services/jira_import/cloud_users_mapper_service.rb b/app/services/jira_import/cloud_users_mapper_service.rb
new file mode 100644
index 00000000000..b1c7aac584f
--- /dev/null
+++ b/app/services/jira_import/cloud_users_mapper_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class CloudUsersMapperService < UsersMapperService
+ private
+
+ def url
+ "/rest/api/2/users?maxResults=#{MAX_USERS}&startAt=#{start_at.to_i}"
+ end
+
+ def jira_user_id(jira_user)
+ jira_user['accountId']
+ end
+
+ def jira_user_name(jira_user)
+ jira_user['displayName']
+ end
+ end
+end
diff --git a/app/services/jira_import/server_users_mapper_service.rb b/app/services/jira_import/server_users_mapper_service.rb
new file mode 100644
index 00000000000..d38d134f55c
--- /dev/null
+++ b/app/services/jira_import/server_users_mapper_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class ServerUsersMapperService < UsersMapperService
+ private
+
+ def url
+ "/rest/api/2/user/search?username=''&maxResults=#{MAX_USERS}&startAt=#{start_at.to_i}"
+ end
+
+ def jira_user_id(jira_user)
+ jira_user['key']
+ end
+
+ def jira_user_name(jira_user)
+ jira_user['name']
+ end
+ end
+end
diff --git a/app/services/jira_import/users_importer.rb b/app/services/jira_import/users_importer.rb
index 579d3675073..9babd468d56 100644
--- a/app/services/jira_import/users_importer.rb
+++ b/app/services/jira_import/users_importer.rb
@@ -2,9 +2,7 @@
module JiraImport
class UsersImporter
- attr_reader :user, :project, :start_at, :result
-
- MAX_USERS = 50
+ attr_reader :user, :project, :start_at
def initialize(user, project, start_at)
@project = project
@@ -15,29 +13,43 @@ module JiraImport
def execute
Gitlab::JiraImport.validate_project_settings!(project, user: user)
- return ServiceResponse.success(payload: nil) if users.blank?
-
- result = UsersMapper.new(project, users).execute
- ServiceResponse.success(payload: result)
+ ServiceResponse.success(payload: mapped_users)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
- Gitlab::ErrorTracking.track_exception(error, project_id: project.id, request: url)
- ServiceResponse.error(message: "There was an error when communicating to Jira: #{error.message}")
+ Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
+ ServiceResponse.error(message: "There was an error when communicating to Jira")
rescue Projects::ImportService::Error => error
ServiceResponse.error(message: error.message)
end
private
- def users
- @users ||= client.get(url)
+ def mapped_users
+ users_mapper_service.execute
+ end
+
+ def users_mapper_service
+ @users_mapper_service ||= user_mapper_service_factory
end
- def url
- "/rest/api/2/users?maxResults=#{MAX_USERS}&startAt=#{start_at.to_i}"
+ def deployment_type
+ # TODO: use project.jira_service.deployment_type value when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged
+ @deployment_type ||= client.ServerInfo.all.deploymentType
end
def client
@client ||= project.jira_service.client
end
+
+ def user_mapper_service_factory
+ # TODO: use deployment_type enum from jira service when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged
+ case deployment_type.upcase
+ when JiraService::DEPLOYMENT_TYPES[:server]
+ ServerUsersMapperService.new(project.jira_service, start_at)
+ when JiraService::DEPLOYMENT_TYPES[:cloud]
+ CloudUsersMapperService.new(project.jira_service, start_at)
+ else
+ raise ArgumentError
+ end
+ end
end
end
diff --git a/app/services/jira_import/users_mapper.rb b/app/services/jira_import/users_mapper.rb
deleted file mode 100644
index c3cbeb157bd..00000000000
--- a/app/services/jira_import/users_mapper.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module JiraImport
- class UsersMapper
- attr_reader :project, :jira_users
-
- def initialize(project, jira_users)
- @project = project
- @jira_users = jira_users
- end
-
- def execute
- jira_users.to_a.map do |jira_user|
- {
- jira_account_id: jira_user['accountId'],
- jira_display_name: jira_user['displayName'],
- jira_email: jira_user['emailAddress']
- }.merge(match_user(jira_user))
- end
- end
-
- private
-
- # TODO: Matching user by email and displayName will be done as the part
- # of follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/219023
- def match_user(jira_user)
- { gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
- end
- end
-end
diff --git a/app/services/jira_import/users_mapper_service.rb b/app/services/jira_import/users_mapper_service.rb
new file mode 100644
index 00000000000..b5997d77215
--- /dev/null
+++ b/app/services/jira_import/users_mapper_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class UsersMapperService
+ MAX_USERS = 50
+
+ attr_reader :jira_service, :start_at
+
+ def initialize(jira_service, start_at)
+ @jira_service = jira_service
+ @start_at = start_at
+ end
+
+ def execute
+ users.to_a.map do |jira_user|
+ {
+ jira_account_id: jira_user_id(jira_user),
+ jira_display_name: jira_user_name(jira_user),
+ jira_email: jira_user['emailAddress']
+ }.merge(match_user(jira_user))
+ end
+ end
+
+ private
+
+ def users
+ @users ||= client.get(url)
+ end
+
+ def client
+ @client ||= jira_service.client
+ end
+
+ def url
+ raise NotImplementedError
+ end
+
+ def jira_user_id(jira_user)
+ raise NotImplementedError
+ end
+
+ def jira_user_name(jira_user)
+ raise NotImplementedError
+ end
+
+ # TODO: Matching user by email and displayName will be done as the part
+ # of follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/219023
+ def match_user(jira_user)
+ { gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
+ end
+ end
+end
diff --git a/bin/feature-flag b/bin/feature-flag
index d4ab295b30c..ba86df9c06c 100755
--- a/bin/feature-flag
+++ b/bin/feature-flag
@@ -8,7 +8,7 @@
require 'optparse'
require 'yaml'
require 'fileutils'
-require 'cgi'
+require 'uri'
require_relative '../lib/feature/shared' unless defined?(Feature::Shared)
@@ -105,10 +105,11 @@ class FeatureFlagOptionParser
end
def read_group
+ $stdout.puts
$stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:"
loop do
- $stdout.print "\n?> "
+ $stdout.print "?> "
group = $stdin.gets.strip
group = nil if group.empty?
return group if group.nil? || group.start_with?('group::')
@@ -121,6 +122,7 @@ class FeatureFlagOptionParser
# if there's only one type, do not ask, return
return TYPES.first.first if TYPES.one?
+ $stdout.puts
$stdout.puts ">> Please specify the type of your feature flag:"
$stdout.puts
TYPES.each do |type, data|
@@ -128,7 +130,7 @@ class FeatureFlagOptionParser
end
loop do
- $stdout.print "\n?> "
+ $stdout.print "?> "
type = $stdin.gets.strip.to_sym
return type if TYPES[type]
@@ -137,27 +139,41 @@ class FeatureFlagOptionParser
end
end
- def read_issue_url(options)
+ def read_introduced_by_url
+ $stdout.puts
+ $stdout.puts ">> If you have MR open, can you paste the URL here? (or enter to skip)"
+
+ loop do
+ $stdout.print "?> "
+ introduced_by_url = $stdin.gets.strip
+ introduced_by_url = nil if introduced_by_url.empty?
+ return introduced_by_url if introduced_by_url.nil? || introduced_by_url.start_with?('https://')
+
+ $stderr.puts "URL needs to start with https://"
+ end
+ end
+
+ def read_rollout_issue_url(options)
return unless TYPES.dig(options.type, :rollout_issue)
url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new"
title = "[Feature flag] Rollout of `#{options.name}`"
- description = File.read('.gitlab/issue_templates/Feature Flag Roll Out.md')
- description.sub!(':feature_name', options.name)
- issue_new_url = url + "?" +
- "issue[title]=" + CGI.escape(title) + "&"
- # TODO: We should be able to pick `issueable_template`
- # + "issue[description]=" + CGI.escape(description)
+ params = {
+ 'issue[title]' => "[Feature flag] Rollout of `#{options.name}`",
+ 'issuable_template' => 'Feature Flag Roll Out',
+ }
+ issue_new_url = url + "?" + URI.encode_www_form(params)
+ $stdout.puts
$stdout.puts ">> Open this URL and fill the rest of details:"
$stdout.puts issue_new_url
$stdout.puts
- $stdout.puts ">> Paste URL here, or enter to skip:"
+ $stdout.puts ">> Paste URL of `rollout issue` here, or enter to skip:"
loop do
- $stdout.print "\n?> "
+ $stdout.print "?> "
created_url = $stdin.gets.strip
created_url = nil if created_url.empty?
return created_url if created_url.nil? || created_url.start_with?('https://')
@@ -185,7 +201,8 @@ class FeatureFlagCreator
# Read type from $stdin unless is already set
options.type ||= FeatureFlagOptionParser.read_type
options.group ||= FeatureFlagOptionParser.read_group
- options.rollout_issue_url ||= FeatureFlagOptionParser.read_issue_url(options)
+ options.introduced_by_url ||= FeatureFlagOptionParser.read_introduced_by_url
+ options.rollout_issue_url ||= FeatureFlagOptionParser.read_rollout_issue_url(options)
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
diff --git a/changelogs/unreleased/225946-replace-fa-close-with-gitlab-svg.yml b/changelogs/unreleased/225946-replace-fa-close-with-gitlab-svg.yml
new file mode 100644
index 00000000000..ea78201234d
--- /dev/null
+++ b/changelogs/unreleased/225946-replace-fa-close-with-gitlab-svg.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-close icons with GitLab SVG close icon
+merge_request: 39267
+author:
+type: changed
diff --git a/changelogs/unreleased/232998-package-metadata.yml b/changelogs/unreleased/232998-package-metadata.yml
new file mode 100644
index 00000000000..038e84e9c8d
--- /dev/null
+++ b/changelogs/unreleased/232998-package-metadata.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Conan recipe display in the package details page
+merge_request: 39643
+author:
+type: fixed
diff --git a/changelogs/unreleased/235759_backward_compatibility_on_jira_users_api_endpoint.yml b/changelogs/unreleased/235759_backward_compatibility_on_jira_users_api_endpoint.yml
new file mode 100644
index 00000000000..aadad6fad88
--- /dev/null
+++ b/changelogs/unreleased/235759_backward_compatibility_on_jira_users_api_endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Handle user mapping for Jira server instances
+merge_request: 39362
+author:
+type: fixed
diff --git a/changelogs/unreleased/235797-fix-issues-analytics-feature-name.yml b/changelogs/unreleased/235797-fix-issues-analytics-feature-name.yml
new file mode 100644
index 00000000000..2221ab750b6
--- /dev/null
+++ b/changelogs/unreleased/235797-fix-issues-analytics-feature-name.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate additions and deletions attributes in Repositories API
+merge_request: 39653
+author:
+type: deprecated
diff --git a/changelogs/unreleased/reorder-designs-fixes.yml b/changelogs/unreleased/reorder-designs-fixes.yml
new file mode 100644
index 00000000000..05d2fc4ca7e
--- /dev/null
+++ b/changelogs/unreleased/reorder-designs-fixes.yml
@@ -0,0 +1,5 @@
+---
+title: Enable reorder_designs feature by default
+merge_request: 39555
+author:
+type: changed
diff --git a/changelogs/unreleased/symlink-comments-disable-symlink-comments.yml b/changelogs/unreleased/symlink-comments-disable-symlink-comments.yml
new file mode 100644
index 00000000000..2e1d19ae2a9
--- /dev/null
+++ b/changelogs/unreleased/symlink-comments-disable-symlink-comments.yml
@@ -0,0 +1,6 @@
+---
+title: Disable commenting on lines in files that were or are symlinks or replace or
+ are replaced by symlinks
+merge_request: 35371
+author:
+type: fixed
diff --git a/changelogs/unreleased/todo-labels.yml b/changelogs/unreleased/todo-labels.yml
new file mode 100644
index 00000000000..5a33cc6cfbf
--- /dev/null
+++ b/changelogs/unreleased/todo-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing scoped label borders for todos
+merge_request: 39459
+author:
+type: fixed
diff --git a/config/feature_flags/development/ci_if_parenthesis_enabled.yml b/config/feature_flags/development/ci_if_parenthesis_enabled.yml
new file mode 100644
index 00000000000..5de7f9cf09a
--- /dev/null
+++ b/config/feature_flags/development/ci_if_parenthesis_enabled.yml
@@ -0,0 +1,7 @@
+---
+name: ci_if_parenthesis_enabled
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37574
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/238174
+group: group::ci
+type: development
+default_enabled: true \ No newline at end of file
diff --git a/config/feature_flags/development/ci_plan_needs_size_limit.yml b/config/feature_flags/development/ci_plan_needs_size_limit.yml
new file mode 100644
index 00000000000..826aeb8f030
--- /dev/null
+++ b/config/feature_flags/development/ci_plan_needs_size_limit.yml
@@ -0,0 +1,7 @@
+---
+name: ci_plan_needs_size_limit
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37568
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/238173
+group: group::ci
+type: development
+default_enabled: true \ No newline at end of file
diff --git a/config/feature_flags/development/reorder_designs.yml b/config/feature_flags/development/reorder_designs.yml
index 3bb05ff743f..89c6bec7351 100644
--- a/config/feature_flags/development/reorder_designs.yml
+++ b/config/feature_flags/development/reorder_designs.yml
@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37835
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/232992
group: group::knowledge
type: development
-default_enabled: false
+default_enabled: true
diff --git a/config/initializers/rails_host_authorization.rb b/config/initializers/rails_host_authorization.rb
index 6cca39ea95b..7d719dd519f 100644
--- a/config/initializers/rails_host_authorization.rb
+++ b/config/initializers/rails_host_authorization.rb
@@ -3,7 +3,7 @@
# This file requires config/initializers/1_settings.rb
if Rails.env.development?
- Rails.application.config.hosts += [Gitlab.config.gitlab.host, 'unix']
+ Rails.application.config.hosts += [Gitlab.config.gitlab.host, 'unix', 'host.docker.internal']
if ENV['RAILS_HOSTS']
additional_hosts = ENV['RAILS_HOSTS'].split(',').select(&:presence)
diff --git a/db/post_migrate/20200724130639_backfill_designs_relative_position.rb b/db/post_migrate/20200724130639_backfill_designs_relative_position.rb
index a090678214f..ef8b38bf90c 100644
--- a/db/post_migrate/20200724130639_backfill_designs_relative_position.rb
+++ b/db/post_migrate/20200724130639_backfill_designs_relative_position.rb
@@ -1,38 +1,19 @@
# frozen_string_literal: true
+# This migration is not needed anymore and was disabled, because we're now
+# also backfilling design positions immediately before moving a design.
+#
+# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39555
class BackfillDesignsRelativePosition < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
- INTERVAL = 2.minutes
- BATCH_SIZE = 1000
- MIGRATION = 'BackfillDesignsRelativePosition'
-
- disable_ddl_transaction!
-
- class Issue < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'issues'
-
- has_many :designs
- end
-
- class Design < ActiveRecord::Base
- self.table_name = 'design_management_designs'
- end
def up
- issues_with_designs = Issue.where(id: Design.select(:issue_id))
-
- issues_with_designs.each_batch(of: BATCH_SIZE) do |relation, index|
- issue_ids = relation.pluck(:id)
- delay = INTERVAL * index
-
- migrate_in(delay, MIGRATION, [issue_ids])
- end
+ # no-op
end
def down
+ # no-op
end
end
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 79ccd5bc9e4..305216f853a 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -199,6 +199,9 @@ authentication if the repository is publicly accessible.
GET /projects/:id/repository/contributors
```
+CAUTION: **Deprecation:**
+The `additions` and `deletions` attributes are deprecated [as of GitLab 13.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39653) because they [always return `0`](https://gitlab.com/gitlab-org/gitlab/-/issues/233119).
+
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
@@ -212,14 +215,14 @@ Response:
"name": "Example User",
"email": "example@example.com",
"commits": 117,
- "additions": 2097,
- "deletions": 517
+ "additions": 0,
+ "deletions": 0
}, {
"name": "Sample User",
"email": "sample@example.com",
"commits": 33,
- "additions": 338,
- "deletions": 244
+ "additions": 0,
+ "deletions": 0
}]
```
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 8f137b6436a..61bc466692e 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -760,9 +760,9 @@ Examples:
- `($VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/) && $VARIABLE3`
- `$CI_COMMIT_BRANCH == "my-branch" || (($VARIABLE1 == "thing" || $VARIABLE2 == "thing") && $VARIABLE3)`
-The feature is currently deployed behind a feature flag that is **disabled by default**.
+The feature is currently deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can opt to enable it for your instance.
+can opt to disable it for your instance.
To enable it:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e6e84fbc3cb..82a410129a6 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1989,9 +1989,7 @@ This example creates four paths of execution:
- The maximum number of jobs that a single job can need in the `needs:` array is limited:
- For GitLab.com, the limit is ten. For more information, see our
[infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7541).
- - For self-managed instances, the limit is:
- - 10, if the `ci_plan_needs_size_limit` feature flag is disabled (default).
- - 50, if the `ci_plan_needs_size_limit` feature flag is enabled. This limit [can be changed](#changing-the-needs-job-limit-core-only).
+ - For self-managed instances, the limit is: 50. This limit [can be changed](#changing-the-needs-job-limit-core-only).
- If `needs:` refers to a job that is marked as `parallel:`.
the current job will depend on all parallel jobs created.
- `needs:` is similar to `dependencies:` in that it needs to use jobs from prior stages,
@@ -2002,18 +2000,10 @@ This example creates four paths of execution:
##### Changing the `needs:` job limit **(CORE ONLY)**
-The maximum number of jobs that can be defined within `needs:` defaults to 10.
+The maximum number of jobs that can be defined within `needs:` defaults to 50.
-To change this limit to 50 on a self-managed installation, a GitLab administrator
-with [access to the GitLab Rails console](../../administration/feature_flags.md)
-can enable the `:ci_plan_needs_size_limit` feature flag:
-
-```ruby
-Feature::enable(:ci_plan_needs_size_limit)
-```
-
-After the feature flag is enabled, you can choose a custom limit. For example, to
-set the limit to 100:
+A GitLab administrator with [access to the GitLab Rails console](../../administration/feature_flags.md)
+can choose a custom limit. For example, to set the limit to 100:
```ruby
Plan.default.actual_limits.update!(ci_needs_size_limit: 100)
diff --git a/doc/development/deleting_migrations.md b/doc/development/deleting_migrations.md
index 8aa16710d55..b8f23019ac2 100644
--- a/doc/development/deleting_migrations.md
+++ b/doc/development/deleting_migrations.md
@@ -23,7 +23,7 @@ Migrations can be disabled if:
In order to disable a migration, the following steps apply to all types of migrations:
1. Turn the migration into a no-op by removing the code inside `#up`, `#down`
- or `#perform` methods, and adding `#no-op` comment instead.
+ or `#perform` methods, and adding `# no-op` comment instead.
1. Add a comment explaining why the code is gone.
Disabling migrations requires explicit approval of Database Maintainer.
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
index cb97a775409..9b30187fcd1 100644
--- a/doc/development/feature_flags/development.md
+++ b/doc/development/feature_flags/development.md
@@ -78,19 +78,21 @@ Only feature flags that have a YAML definition file can be used when running the
```shell
$ bin/feature-flag my-feature-flag
>> Please specify the group introducing feature flag, like `group::apm`:
-
?> group::memory
->> Open this URL and fill the rest of details:
-https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=%5BFeature+flag%5D+Rollout+of+%60my-feature-flag%60&
->> Paste URL here, or enter to skip:
+>> If you have MR open, can you paste the URL here? (or enter to skip)
+?> https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38602
+
+>> Open this URL and fill the rest of details:
+https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue%5Btitle%5D=%5BFeature+flag%5D+Rollout+of+%60test-flag%60&issuable_template=Feature+Flag+Roll+Out
-?>
-create config/feature_flags/development/my_feature_flag.yml
+>> Paste URL of `rollout issue` here, or enter to skip:
+?> https://gitlab.com/gitlab-org/gitlab/-/issues/232533
+create config/feature_flags/development/test-flag.yml
---
-name: my_feature_flag
-introduced_by_url:
-rollout_issue_url:
+name: test-flag
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38602
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/232533
group: group::memory
type: development
default_enabled: false
diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png
deleted file mode 100644
index bf759f44dc5..00000000000
--- a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index b8a4d513318..407fc5db425 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -1,7 +1,7 @@
---
stage: Create
group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, concepts
---
@@ -13,16 +13,16 @@ process, as it clearly communicates the ability to merge the change.
## Optional Approvals **(CORE ONLY)**
-> Introduced in [GitLab Core 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/27426).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27426) in GitLab 13.2.
-Any user with Developer or greater [permissions](../../permissions.md) can approve a merge request in GitLab Core.
-This provides a consistent mechanism for reviewers to provide approval, and makes it easy for
+Any user with Developer or greater [permissions](../../permissions.md) can approve a merge request in GitLab Core and higher tiers.
+This provides a consistent mechanism for reviewers to approve merge requests, and makes it easy for
maintainers to know when a change is ready to merge. Approvals in Core are optional and do
not prevent a merge request from being merged when there is no approval.
## Required Approvals **(STARTER)**
-> Introduced in [GitLab Enterprise Edition 7.12](https://about.gitlab.com/releases/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only).
+> [Introduced](https://about.gitlab.com/releases/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only) in GitLab Enterprise Edition 7.12. Available in [GitLab Starter](https://about.gitlab.com/pricing/) and higher tiers.
Required approvals enable enforced code review by requiring specified people
to approve a merge request before it can be merged.
@@ -33,8 +33,8 @@ Required approvals enable multiple use cases:
- Specifying reviewers for a given proposed code change, as well as a minimum number
of reviewers, through [Approval rules](#approval-rules).
- Specifying categories of reviewers, such as backend, frontend, quality assurance,
- database, etc., for all proposed code changes.
-- Automatically designating [Code Owners as eligible approvers](#code-owners-as-eligible-approvers),
+ database, and so on, for all proposed code changes.
+- Designating [Code Owners as eligible approvers](#code-owners-as-eligible-approvers),
determined by the files changed in a merge request.
- [Requiring approval from a security team](#security-approvals-in-merge-requests-ultimate)
before merging code that could introduce a vulnerability.**(ULTIMATE)**
@@ -50,14 +50,10 @@ be merged, and optionally which users should do the approving. Approvals can be
If no approval rules are defined, any user can approve a merge request, though the default
minimum number of required approvers can still be set in the [project settings for merge request approvals](#merge-request-approvals-project-settings).
-Approval rules define how many approvals a merge request must receive before it can
-be merged, and optionally which users should do the approving. Approvals can be defined:
-
-- [As project defaults](#adding--editing-a-default-approval-rule).
-- [Per merge request](#editing--overriding-approval-rules-per-merge-request).
-
-If no approval rules are defined, any user can approve a merge request, though the default
-minimum number of required approvers can still be set in the [project settings for merge request approvals](#merge-request-approvals-project-settings).
+You can opt to define one single rule to approve a merge request among the available rules
+or choose more than one. Single approval rules are available in GitLab Starter and higher tiers,
+while [multiple approval rules](#multiple-approval-rules-premium) are available in
+[GitLab Premium](https://about.gitlab.com/pricing/) and above.
NOTE: **Note:**
On GitLab.com, you can add a group as an approver if you're a member of that group or the
@@ -88,6 +84,11 @@ if [**Prevent author approval**](#allowing-merge-request-authors-to-approve-thei
and [**Prevent committers approval**](#prevent-approval-of-merge-requests-by-their-committers) (disabled by default)
are enabled on the project settings.
+[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10294) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3,
+when an eligible approver comments on a merge request, it appears in the **Commented by** column of the Approvals widget,
+indicating who has engaged in the merge request review. Authors and reviewers can also easily identify who they should reach out
+to if they have any questions or inputs about the content of the merge request.
+
##### Implicit Approvers
If the number of required approvals is greater than the number of assigned approvers,
@@ -187,9 +188,6 @@ a rule is already defined.
When an [eligible approver](#eligible-approvers) approves a merge request, it will
reduce the number of approvals left for all rules that the approver belongs to.
-When an [eligible approver](#eligible-approvers) comments on a merge request, it
-appears in the **Commented by** column. This feature was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10294) in GitLab 13.3.
-
![Approvals premium merge request widget](img/approvals_premium_mr_widget_v13_3.png)
#### Scoped to Protected Branch **(PREMIUM)**
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 6f449fd060a..cea0cb3a19c 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -67,10 +67,11 @@ module API
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
post ':id/variables' do
- variable_params = declared_params(include_missing: false)
- variable_params = filter_variable_parameters(variable_params)
-
- variable = user_project.variables.create(variable_params)
+ variable = ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :create, variable_params: filter_variable_parameters(declared_params(include_missing: false)) }
+ ).execute
if variable.valid?
present variable, with: Entities::Ci::Variable
@@ -96,10 +97,17 @@ module API
variable = find_variable(params)
not_found!('Variable') unless variable
- variable_params = declared_params(include_missing: false).except(:key, :filter)
- variable_params = filter_variable_parameters(variable_params)
+ variable_params = filter_variable_parameters(
+ declared_params(include_missing: false)
+ .except(:key, :filter)
+ )
+ variable = ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :update, variable: variable, variable_params: variable_params }
+ ).execute
- if variable.update(variable_params)
+ if variable.valid?
present variable, with: Entities::Ci::Variable
else
render_validation_error!(variable)
@@ -119,8 +127,11 @@ module API
variable = find_variable(params)
not_found!('Variable') unless variable
- # Variables don't have a timestamp. Therefore, destroy unconditionally.
- variable.destroy
+ ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :destroy, variable: variable }
+ ).execute
no_content!
end
diff --git a/lib/gitlab/background_migration/backfill_designs_relative_position.rb b/lib/gitlab/background_migration/backfill_designs_relative_position.rb
index 4cfaa899eef..efbb1b950ad 100644
--- a/lib/gitlab/background_migration/backfill_designs_relative_position.rb
+++ b/lib/gitlab/background_migration/backfill_designs_relative_position.rb
@@ -2,52 +2,13 @@
module Gitlab
module BackgroundMigration
- # Backfill `relative_position` column in `design_management_designs` table
+ # This migration is not needed anymore and was disabled, because we're now
+ # also backfilling design positions immediately before moving a design.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39555
class BackfillDesignsRelativePosition
- # Define the issue model
- class Issue < ActiveRecord::Base
- self.table_name = 'issues'
- end
-
- # Define the design model
- class Design < ActiveRecord::Base
- include RelativePositioning if defined?(RelativePositioning)
-
- self.table_name = 'design_management_designs'
-
- def self.relative_positioning_query_base(design)
- where(issue_id: design.issue_id)
- end
-
- def self.relative_positioning_parent_column
- :issue_id
- end
-
- def self.move_nulls_to_start(designs)
- if defined?(super)
- super(designs)
- else
- logger.error "BackfillDesignsRelativePosition failed because move_nulls_to_start is no longer included in the RelativePositioning concern"
- end
- end
- end
-
def perform(issue_ids)
- issue_ids.each do |issue_id|
- migrate_issue(issue_id)
- end
- end
-
- private
-
- def migrate_issue(issue_id)
- issue = Issue.find_by(id: issue_id)
- return unless issue
-
- designs = Design.where(issue_id: issue.id).order(:id)
- return unless designs.any?
-
- Design.move_nulls_to_start(designs)
+ # no-op
end
end
end
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 5d0e1e4fd01..38767acf716 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -57,7 +57,7 @@ module Gitlab
end
def self.ci_if_parenthesis_enabled?
- ::Feature.enabled?(:ci_if_parenthesis_enabled)
+ ::Feature.enabled?(:ci_if_parenthesis_enabled, default_enabled: true)
end
def self.allow_to_create_merge_request_pipelines_in_target_project?(target_project)
@@ -65,7 +65,7 @@ module Gitlab
end
def self.ci_plan_needs_size_limit?(project)
- ::Feature.enabled?(:ci_plan_needs_size_limit, project)
+ ::Feature.enabled?(:ci_plan_needs_size_limit, project, default_enabled: true)
end
def self.job_entry_matches_all_keys?
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index f2a7da73120..f6914389637 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -604,8 +604,6 @@ module Gitlab
end
def action_monthly_active_users(time_period)
- return {} unless Feature.enabled?(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG)
-
counter = Gitlab::UsageDataCounters::TrackUniqueActions
project_count = redis_usage_data do
diff --git a/lib/gitlab/usage_data_counters/track_unique_actions.rb b/lib/gitlab/usage_data_counters/track_unique_actions.rb
index b7948b07251..0df982572a4 100644
--- a/lib/gitlab/usage_data_counters/track_unique_actions.rb
+++ b/lib/gitlab/usage_data_counters/track_unique_actions.rb
@@ -4,7 +4,6 @@ module Gitlab
module UsageDataCounters
module TrackUniqueActions
KEY_EXPIRY_LENGTH = 29.days
- FEATURE_FLAG = :track_unique_actions
WIKI_ACTION = :wiki_action
DESIGN_ACTION = :design_action
@@ -29,7 +28,6 @@ module Gitlab
class << self
def track_event(event_action:, event_target:, author_id:, time: Time.zone.now)
return unless Gitlab::CurrentSettings.usage_ping_enabled
- return unless Feature.enabled?(FEATURE_FLAG)
return unless valid_target?(event_target)
return unless valid_action?(event_action)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ff2627e69a5..eba71cacad1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6161,6 +6161,12 @@ msgstr ""
msgid "Comment/Reply (quoting selected text)"
msgstr ""
+msgid "Commenting on files that replace or are replaced by symbolic links is currently not supported."
+msgstr ""
+
+msgid "Commenting on symbolic links that replace or are replaced by files is currently not supported."
+msgstr ""
+
msgid "Comments"
msgstr ""
diff --git a/package.json b/package.json
index 5f70378f735..29d611c341d 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.158.0",
- "@gitlab/ui": "18.7.0",
+ "@gitlab/ui": "20.1.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",
diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb
index e0fa24fb484..f85b8f22210 100644
--- a/spec/bin/feature_flag_spec.rb
+++ b/spec/bin/feature_flag_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'bin/feature-flag' do
using RSpec::Parameterized::TableSyntax
describe FeatureFlagCreator do
- let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url] }
+ let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url -m http://url] }
let(:options) { FeatureFlagOptionParser.parse(argv) }
let(:creator) { described_class.new(options) }
let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') }
@@ -183,15 +183,51 @@ RSpec.describe 'bin/feature-flag' do
end
end
- describe '.rollout_issue_url' do
+ describe '.read_introduced_by_url' do
+ let(:url) { 'https://merge-request' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(url)
+ expect do
+ expect(described_class.read_introduced_by_url).to eq('https://merge-request')
+ end.to output(/can you paste the URL here/).to_stdout
+ end
+
+ context 'empty URL given' do
+ let(:url) { '' }
+
+ it 'skips entry' do
+ expect($stdin).to receive(:gets).and_return(url)
+ expect do
+ expect(described_class.read_introduced_by_url).to be_nil
+ end.to output(/can you paste the URL here/).to_stdout
+ end
+ end
+
+ context 'invalid URL given' do
+ let(:url) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(url)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_introduced_by_url }.to raise_error(/EOF/)
+ end.to output(/can you paste the URL here/).to_stdout
+ .and output(/URL needs to start with/).to_stderr
+ end
+ end
+ end
+
+ describe '.read_rollout_issue_url' do
let(:options) { OpenStruct.new(name: 'foo', type: :development) }
let(:url) { 'https://issue' }
it 'reads type from $stdin' do
expect($stdin).to receive(:gets).and_return(url)
expect do
- expect(described_class.read_issue_url(options)).to eq('https://issue')
- end.to output(/Paste URL here/).to_stdout
+ expect(described_class.read_rollout_issue_url(options)).to eq('https://issue')
+ end.to output(/Paste URL of `rollout issue` here/).to_stdout
end
context 'invalid URL given' do
@@ -202,8 +238,8 @@ RSpec.describe 'bin/feature-flag' do
expect($stdin).to receive(:gets).and_raise('EOF')
expect do
- expect { described_class.read_issue_url(options) }.to raise_error(/EOF/)
- end.to output(/Paste URL here/).to_stdout
+ expect { described_class.read_rollout_issue_url(options) }.to raise_error(/EOF/)
+ end.to output(/Paste URL of `rollout issue` here/).to_stdout
.and output(/URL needs to start/).to_stderr
end
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 59588978a8e..c585d7f6194 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe 'Visual tokens', :js do
end
it 'ends editing mode when document is clicked' do
- find('#content-body').click
+ find('.js-navbar').click
expect_filtered_search_input_empty
expect(page).to have_css('#js-dropdown-author', visible: false)
@@ -142,7 +142,7 @@ RSpec.describe 'Visual tokens', :js do
it 'does not tokenize incomplete token' do
filtered_search.send_keys('author:=')
- find('body').click
+ find('.js-navbar').click
token = page.all('.tokens-container .js-visual-token')[1]
expect_filtered_search_input_empty
diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
index 8070fee5804..29a27992a0d 100644
--- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe 'User uploads new design', :js do
include DesignManagementTestHelpers
- let_it_be(:project) { create(:project_empty_repo, :public) }
- let_it_be(:user) { project.owner }
- let_it_be(:issue) { create(:issue, project: project) }
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:user) { project.owner }
+ let(:issue) { create(:issue, project: project) }
before do
sign_in(user)
@@ -28,7 +28,7 @@ RSpec.describe 'User uploads new design', :js do
let(:feature_enabled) { true }
it 'uploads designs' do
- attach_file(:design_file, logo_fixture, make_visible: true)
+ upload_design(logo_fixture, count: 1)
expect(page).to have_selector('.js-design-list-item', count: 1)
@@ -36,9 +36,12 @@ RSpec.describe 'User uploads new design', :js do
expect(page).to have_content('dk.png')
end
- attach_file(:design_file, gif_fixture, make_visible: true)
+ upload_design(gif_fixture, count: 2)
+ # Known bug in the legacy implementation: new designs are inserted
+ # in the beginning on the frontend.
expect(page).to have_selector('.js-design-list-item', count: 2)
+ expect(page.all('.js-design-list-item').map(&:text)).to eq(['banana_sample.gif', 'dk.png'])
end
end
@@ -61,8 +64,8 @@ RSpec.describe 'User uploads new design', :js do
context "when the feature is available" do
let(:feature_enabled) { true }
- it 'uploads designs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225616' do
- attach_file(:design_file, logo_fixture, make_visible: true)
+ it 'uploads designs' do
+ upload_design(logo_fixture, count: 1)
expect(page).to have_selector('.js-design-list-item', count: 1)
@@ -70,9 +73,10 @@ RSpec.describe 'User uploads new design', :js do
expect(page).to have_content('dk.png')
end
- attach_file(:design_file, gif_fixture, make_visible: true)
+ upload_design(gif_fixture, count: 2)
expect(page).to have_selector('.js-design-list-item', count: 2)
+ expect(page.all('.js-design-list-item').map(&:text)).to eq(['dk.png', 'banana_sample.gif'])
end
end
@@ -92,4 +96,12 @@ RSpec.describe 'User uploads new design', :js do
def gif_fixture
Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
end
+
+ def upload_design(fixture, count:)
+ attach_file(:design_file, fixture, match: :first, make_visible: true)
+
+ wait_for('designs uploaded') do
+ issue.reload.designs.count == count
+ end
+ end
end
diff --git a/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js b/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
index 726ed0fa030..9fee8e18d26 100644
--- a/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
+++ b/spec/frontend/__mocks__/@toast-ui/vue-editor/index.js
@@ -17,6 +17,17 @@ export const Editor = {
type: String,
},
},
+ created() {
+ const mockEditorApi = {
+ eventManager: {
+ addEventType: jest.fn(),
+ listen: jest.fn(),
+ removeEventHandler: jest.fn(),
+ },
+ };
+
+ this.$emit('load', mockEditorApi);
+ },
render(h) {
return h('div');
},
diff --git a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
index f6036dcbc1d..f271aeb5d95 100644
--- a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
+++ b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
@@ -27,7 +27,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
<gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\">
<gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
<div class=\\"gl-display-flex gl-justify-content-end\\">
- <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
+ <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
</div>
<gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
@@ -37,7 +37,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
<gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
</gl-form-group-stub>
<div class=\\"gl-display-flex gl-justify-content-end\\">
- <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
+ <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
</div>
<div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\">
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 161c2bade05..745a163951a 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -56,7 +56,7 @@ exports[`Code navigation popover component renders popover 1`] = `
class="popover-body border-top"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="w-100"
data-testid="go-to-definition-btn"
href="http://gitlab.com/test.js"
diff --git a/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap
index aa914a8dff0..1900c64cd47 100644
--- a/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap
@@ -44,7 +44,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
/>
<gl-button-stub
- category="tertiary"
+ category="primary"
href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
icon="download"
size="medium"
diff --git a/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap b/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap
index c4112b46402..3d7939df28e 100644
--- a/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap
+++ b/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap
@@ -5,7 +5,7 @@ exports[`Design management upload button component renders inverted upload desig
isinverted="true"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
@@ -30,7 +30,7 @@ exports[`Design management upload button component renders inverted upload desig
exports[`Design management upload button component renders loading icon 1`] = `
<div>
<gl-button-stub
- category="tertiary"
+ category="primary"
disabled="true"
icon=""
size="small"
@@ -62,7 +62,7 @@ exports[`Design management upload button component renders loading icon 1`] = `
exports[`Design management upload button component renders upload design button 1`] = `
<div>
<gl-button-stub
- category="tertiary"
+ category="primary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
diff --git a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
index c3fcede5576..3881b2d7679 100644
--- a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
@@ -110,7 +110,7 @@ exports[`Design management index page designs renders designs list and header wi
class="qa-selector-toolbar gl-display-flex gl-align-items-center"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="gl-mr-3 js-select-all"
icon=""
size="small"
diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
index a7b754ba50d..823294efc38 100644
--- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap
@@ -65,7 +65,7 @@ exports[`Design management design index page renders design index 1`] = `
/>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
data-testid="resolved-comments"
icon="chevron-right"
diff --git a/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap
index c3417744524..dc5baf37fc6 100644
--- a/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap
@@ -65,7 +65,7 @@ exports[`Design management design index page renders design index 1`] = `
/>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
data-testid="resolved-comments"
icon="chevron-right"
diff --git a/spec/frontend/diffs/components/diff_table_cell_spec.js b/spec/frontend/diffs/components/diff_table_cell_spec.js
index 24daef62ee2..02f5c27eecb 100644
--- a/spec/frontend/diffs/components/diff_table_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_table_cell_spec.js
@@ -18,6 +18,12 @@ const TEST_LINE_CODE = 'LC_42';
const TEST_FILE_HASH = diffFileMockData.file_hash;
describe('DiffTableCell', () => {
+ const symlinkishFileTooltip =
+ 'Commenting on symbolic links that replace or are replaced by files is currently not supported.';
+ const realishFileTooltip =
+ 'Commenting on files that replace or are replaced by symbolic links is currently not supported.';
+ const otherFileTooltip = 'Add a comment to this line';
+
let wrapper;
let line;
let store;
@@ -67,6 +73,7 @@ describe('DiffTableCell', () => {
const findTd = () => wrapper.find({ ref: 'td' });
const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' });
const findLineNumber = () => wrapper.find({ ref: 'lineNumberRef' });
+ const findTooltip = () => wrapper.find({ ref: 'addNoteTooltip' });
const findAvatars = () => wrapper.find(DiffGutterAvatars);
describe('td', () => {
@@ -134,6 +141,53 @@ describe('DiffTableCell', () => {
});
},
);
+
+ it.each`
+ disabled | commentsDisabled
+ ${'disabled'} | ${true}
+ ${undefined} | ${false}
+ `(
+ 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
+ ({ disabled, commentsDisabled }) => {
+ line.commentsDisabled = commentsDisabled;
+
+ createComponent({
+ showCommentButton: true,
+ isHover: true,
+ });
+
+ wrapper.setData({ isCommentButtonRendered: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().attributes('disabled')).toBe(disabled);
+ });
+ },
+ );
+
+ it.each`
+ tooltip | commentsDisabled
+ ${symlinkishFileTooltip} | ${{ wasSymbolic: true }}
+ ${symlinkishFileTooltip} | ${{ isSymbolic: true }}
+ ${realishFileTooltip} | ${{ wasReal: true }}
+ ${realishFileTooltip} | ${{ isReal: true }}
+ ${otherFileTooltip} | ${false}
+ `(
+ 'has the correct tooltip when commentsDisabled=$commentsDisabled',
+ ({ tooltip, commentsDisabled }) => {
+ line.commentsDisabled = commentsDisabled;
+
+ createComponent({
+ showCommentButton: true,
+ isHover: true,
+ });
+
+ wrapper.setData({ isCommentButtonRendered: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findTooltip().attributes('title')).toBe(tooltip);
+ });
+ },
+ );
});
describe('line number', () => {
diff --git a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
index e59ee925cc7..6a00065c9fe 100644
--- a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
@@ -280,8 +280,8 @@ describe('Filtered Search Visual Tokens', () => {
);
});
- it('contains fa-close icon', () => {
- expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(expect.anything());
+ it('contains close icon', () => {
+ expect(tokenElement.querySelector('.remove-token .close-icon')).toEqual(expect.anything());
});
});
});
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
index 8a61f5565c6..0e16b726c4b 100644
--- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
+++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
@@ -17,7 +17,7 @@ exports[`grafana integration component default state to match the default snapsh
</h3>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-settings-toggle"
icon=""
size="medium"
diff --git a/spec/frontend/helpers/filtered_search_spec_helper.js b/spec/frontend/helpers/filtered_search_spec_helper.js
index ceb7982bbc3..ecf10694a16 100644
--- a/spec/frontend/helpers/filtered_search_spec_helper.js
+++ b/spec/frontend/helpers/filtered_search_spec_helper.js
@@ -15,7 +15,7 @@ export default class FilteredSearchSpecHelper {
<div class="value-container">
<div class="value">${value}</div>
<div class="remove-token" role="button">
- <i class="fa fa-close"></i>
+ <svg class="s16 close-icon"></svg>
</div>
</div>
</div>
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
index ef61a835918..f3f610e4bb7 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -85,7 +85,7 @@ exports[`Alert integration settings form default state should match the default
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-no-auto-disable"
data-qa-selector="save_changes_button"
icon=""
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
index 31978f8da64..3ad4c13382d 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -18,7 +18,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
</h4>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-settings-toggle"
icon=""
size="medium"
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
index c0fee56c9d8..78bb238fcb6 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
@@ -46,7 +46,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="gl-mt-3"
data-testid="webhook-reset-btn"
icon=""
@@ -80,7 +80,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-no-auto-disable"
icon=""
size="medium"
diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
index 4148c263d28..fc37a545511 100644
--- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
+++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
@@ -38,7 +38,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
/>
</form>
<gl-button-stub
- category="tertiary"
+ category="primary"
icon=""
size="medium"
variant="default"
diff --git a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
index 51fb9a0dede..a43acc8c002 100644
--- a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
+++ b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
@@ -82,7 +82,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
<template>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-modal-action-cancel"
icon=""
size="medium"
@@ -96,7 +96,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
<!---->
<gl-button-stub
- category="tertiary"
+ category="primary"
class="js-modal-action-primary"
disabled="true"
icon=""
diff --git a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
index e5e436278a3..22689080063 100644
--- a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
+++ b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
@@ -11,7 +11,7 @@ exports[`EmptyStateComponent should render content 1`] = `
<p>In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub>
</p>
<div>
- <gl-button-stub category=\\"tertiary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
+ <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
<!---->
</div>
</div>
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index 71cfa394957..e84eb7789d3 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -39,7 +39,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="d-inline-flex"
data-clipboard-text="ssh://foo.bar"
data-qa-selector="copy_ssh_url_button"
@@ -80,7 +80,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
- category="tertiary"
+ category="primary"
class="d-inline-flex"
data-clipboard-text="http://foo.bar"
data-qa-selector="copy_http_url_button"
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
index 78f27c9948b..16f60b5ff21 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
@@ -5,10 +5,13 @@ import {
registerHTMLToMarkdownRenderer,
addImage,
getMarkdown,
+ getEditorOptions,
} from '~/vue_shared/components/rich_content_editor/services/editor_service';
import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
+import buildCustomRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer');
+jest.mock('~/vue_shared/components/rich_content_editor/services/build_custom_renderer');
describe('Editor Service', () => {
let mockInstance;
@@ -120,4 +123,25 @@ describe('Editor Service', () => {
expect(mockInstance.toMarkOptions.renderer).toBe(extendedRenderer);
});
});
+
+ describe('getEditorOptions', () => {
+ const externalOptions = {
+ customRenderers: {},
+ };
+ const renderer = {};
+
+ beforeEach(() => {
+ buildCustomRenderer.mockReturnValueOnce(renderer);
+ });
+
+ it('generates a configuration object with a custom HTML renderer and toolbarItems', () => {
+ expect(getEditorOptions()).toHaveProp('customHTMLRenderer', renderer);
+ expect(getEditorOptions()).toHaveProp('toolbarItems');
+ });
+
+ it('passes external renderers to the buildCustomRenderers function', () => {
+ getEditorOptions(externalOptions);
+ expect(buildCustomRenderer).toHaveBeenCalledWith(externalOptions.customRenderers);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index b6ff6aa767c..3d54db7fe5c 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import {
- EDITOR_OPTIONS,
EDITOR_TYPES,
EDITOR_HEIGHT,
EDITOR_PREVIEW_STYLE,
@@ -14,6 +13,7 @@ import {
removeCustomEventListener,
addImage,
registerHTMLToMarkdownRenderer,
+ getEditorOptions,
} from '~/vue_shared/components/rich_content_editor/services/editor_service';
jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', () => ({
@@ -22,6 +22,7 @@ jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service',
removeCustomEventListener: jest.fn(),
addImage: jest.fn(),
registerHTMLToMarkdownRenderer: jest.fn(),
+ getEditorOptions: jest.fn(),
}));
describe('Rich Content Editor', () => {
@@ -32,13 +33,25 @@ describe('Rich Content Editor', () => {
const findEditor = () => wrapper.find({ ref: 'editor' });
const findAddImageModal = () => wrapper.find(AddImageModal);
- beforeEach(() => {
+ const buildWrapper = () => {
wrapper = shallowMount(RichContentEditor, {
propsData: { content, imageRoot },
});
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
describe('when content is loaded', () => {
+ const editorOptions = {};
+
+ beforeEach(() => {
+ getEditorOptions.mockReturnValueOnce(editorOptions);
+ buildWrapper();
+ });
+
it('renders an editor', () => {
expect(findEditor().exists()).toBe(true);
});
@@ -47,8 +60,8 @@ describe('Rich Content Editor', () => {
expect(findEditor().props().initialValue).toBe(content);
});
- it('provides the correct editor options', () => {
- expect(findEditor().props().options).toEqual(EDITOR_OPTIONS);
+ it('provides options generated by the getEditorOptions service', () => {
+ expect(findEditor().props().options).toBe(editorOptions);
});
it('has the correct preview style', () => {
@@ -65,6 +78,10 @@ describe('Rich Content Editor', () => {
});
describe('when content is changed', () => {
+ beforeEach(() => {
+ buildWrapper();
+ });
+
it('emits an input event with the changed content', () => {
const changedMarkdown = '## Changed Markdown';
const getMarkdownMock = jest.fn().mockReturnValueOnce(changedMarkdown);
@@ -77,6 +94,10 @@ describe('Rich Content Editor', () => {
});
describe('when content is reset', () => {
+ beforeEach(() => {
+ buildWrapper();
+ });
+
it('should reset the content via setMarkdown', () => {
const newContent = 'Just the body content excluding the front matter for example';
const mockInstance = { invoke: jest.fn() };
@@ -89,35 +110,33 @@ describe('Rich Content Editor', () => {
});
describe('when editor is loaded', () => {
- let mockEditorApi;
-
beforeEach(() => {
- mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
- findEditor().vm.$emit('load', mockEditorApi);
+ buildWrapper();
});
it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
- mockEditorApi,
+ wrapper.vm.editorApi,
CUSTOM_EVENTS.openAddImageModal,
wrapper.vm.onOpenAddImageModal,
);
});
it('registers HTML to markdown renderer', () => {
- expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(mockEditorApi);
+ expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(wrapper.vm.editorApi);
});
});
describe('when editor is destroyed', () => {
- it('removes the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
- const mockEditorApi = { eventManager: { removeEventHandler: jest.fn() } };
+ beforeEach(() => {
+ buildWrapper();
+ });
- wrapper.vm.editorApi = mockEditorApi;
+ it('removes the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
wrapper.vm.$destroy();
expect(removeCustomEventListener).toHaveBeenCalledWith(
- mockEditorApi,
+ wrapper.vm.editorApi,
CUSTOM_EVENTS.openAddImageModal,
wrapper.vm.onOpenAddImageModal,
);
@@ -125,6 +144,10 @@ describe('Rich Content Editor', () => {
});
describe('add image modal', () => {
+ beforeEach(() => {
+ buildWrapper();
+ });
+
it('renders an addImageModal component', () => {
expect(findAddImageModal().exists()).toBe(true);
});
diff --git a/spec/lib/gitlab/background_migration/backfill_designs_relative_position_spec.rb b/spec/lib/gitlab/background_migration/backfill_designs_relative_position_spec.rb
deleted file mode 100644
index 1b723502663..00000000000
--- a/spec/lib/gitlab/background_migration/backfill_designs_relative_position_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::BackfillDesignsRelativePosition do
- let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') }
- let(:project) { table(:projects).create!(namespace_id: namespace.id) }
- let(:issues) { table(:issues) }
- let(:designs) { table(:design_management_designs) }
-
- before do
- issues.create!(id: 1, project_id: project.id)
- issues.create!(id: 2, project_id: project.id)
- issues.create!(id: 3, project_id: project.id)
- issues.create!(id: 4, project_id: project.id)
-
- designs.create!(id: 1, issue_id: 1, project_id: project.id, filename: 'design1.jpg')
- designs.create!(id: 2, issue_id: 1, project_id: project.id, filename: 'design2.jpg')
- designs.create!(id: 3, issue_id: 2, project_id: project.id, filename: 'design3.jpg')
- designs.create!(id: 4, issue_id: 2, project_id: project.id, filename: 'design4.jpg')
- designs.create!(id: 5, issue_id: 3, project_id: project.id, filename: 'design5.jpg')
- end
-
- describe '#perform' do
- it 'backfills the position for the designs in each issue' do
- expect(described_class::Design).to receive(:move_nulls_to_start).with(
- a_collection_containing_exactly(
- an_object_having_attributes(id: 1, issue_id: 1),
- an_object_having_attributes(id: 2, issue_id: 1)
- )
- ).ordered.and_call_original
-
- expect(described_class::Design).to receive(:move_nulls_to_start).with(
- a_collection_containing_exactly(
- an_object_having_attributes(id: 3, issue_id: 2),
- an_object_having_attributes(id: 4, issue_id: 2)
- )
- ).ordered.and_call_original
-
- # We only expect calls to `move_nulls_to_start` with issues 1 and 2:
- # - Issue 3 should be skipped because we're not passing its ID
- # - Issue 4 should be skipped because it doesn't have any designs
- # - Issue 0 should be skipped because it doesn't exist
- subject.perform([1, 2, 4, 0])
-
- expect(designs.find(1).relative_position).to be < designs.find(2).relative_position
- expect(designs.find(3).relative_position).to be < designs.find(4).relative_position
- expect(designs.find(5).relative_position).to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
index 6db77f19877..bd348666729 100644
--- a/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
@@ -17,10 +17,9 @@ RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redi
context 'tracking an event' do
context 'when tracking successfully' do
- context 'when the feature flag and the application setting is enabled' do
+ context 'when the application setting is enabled' do
context 'when the target and the action is valid' do
before do
- stub_feature_flags(described_class::FEATURE_FLAG => true)
stub_application_setting(usage_ping_enabled: true)
end
@@ -59,17 +58,15 @@ RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redi
context 'when tracking unsuccessfully' do
using RSpec::Parameterized::TableSyntax
- where(:feature_flag, :application_setting, :target, :action) do
- true | true | Project | :invalid_action
- false | true | Project | :pushed
- true | false | Project | :pushed
- true | true | :invalid_target | :pushed
+ where(:application_setting, :target, :action) do
+ true | Project | :invalid_action
+ false | Project | :pushed
+ true | :invalid_target | :pushed
end
with_them do
before do
stub_application_setting(usage_ping_enabled: application_setting)
- stub_feature_flags(described_class::FEATURE_FLAG => feature_flag)
end
it 'returns the expected values' do
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 2fb30b3228a..e3da0163e8d 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -912,45 +912,29 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:time) { Time.zone.now }
before do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => feature_flag)
- end
-
- context 'when the feature flag is enabled' do
- let(:feature_flag) { true }
-
- before do
- counter = Gitlab::UsageDataCounters::TrackUniqueActions
- project = Event::TARGET_TYPES[:project]
- wiki = Event::TARGET_TYPES[:wiki]
- design = Event::TARGET_TYPES[:design]
-
- counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 2)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 3)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
- counter.track_event(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
- counter.track_event(event_action: :created, event_target: wiki, author_id: 3)
- counter.track_event(event_action: :created, event_target: design, author_id: 3)
- end
-
- it 'returns the distinct count of user actions within the specified time period' do
- expect(described_class.action_monthly_active_users(time_period)).to eq(
- {
- action_monthly_active_users_design_management: 1,
- action_monthly_active_users_project_repo: 3,
- action_monthly_active_users_wiki_repo: 1
- }
- )
- end
- end
-
- context 'when the feature flag is disabled' do
- let(:feature_flag) { false }
-
- it 'returns an empty hash' do
- expect(described_class.action_monthly_active_users(time_period)).to eq({})
- end
+ counter = Gitlab::UsageDataCounters::TrackUniqueActions
+ project = Event::TARGET_TYPES[:project]
+ wiki = Event::TARGET_TYPES[:wiki]
+ design = Event::TARGET_TYPES[:design]
+
+ counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_event(event_action: :pushed, event_target: project, author_id: 2)
+ counter.track_event(event_action: :pushed, event_target: project, author_id: 3)
+ counter.track_event(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
+ counter.track_event(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
+ counter.track_event(event_action: :created, event_target: wiki, author_id: 3)
+ counter.track_event(event_action: :created, event_target: design, author_id: 3)
+ end
+
+ it 'returns the distinct count of user actions within the specified time period' do
+ expect(described_class.action_monthly_active_users(time_period)).to eq(
+ {
+ action_monthly_active_users_design_management: 1,
+ action_monthly_active_users_project_repo: 3,
+ action_monthly_active_users_wiki_repo: 1
+ }
+ )
end
end
diff --git a/spec/migrations/backfill_designs_relative_position_spec.rb b/spec/migrations/backfill_designs_relative_position_spec.rb
deleted file mode 100644
index 878d3385bb3..00000000000
--- a/spec/migrations/backfill_designs_relative_position_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200724130639_backfill_designs_relative_position.rb')
-
-RSpec.describe BackfillDesignsRelativePosition do
- let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') }
- let(:project) { table(:projects).create!(namespace_id: namespace.id) }
- let(:issues) { table(:issues) }
- let(:designs) { table(:design_management_designs) }
-
- before do
- issues.create!(id: 1, project_id: project.id)
- issues.create!(id: 2, project_id: project.id)
- issues.create!(id: 3, project_id: project.id)
- issues.create!(id: 4, project_id: project.id)
-
- designs.create!(issue_id: 1, project_id: project.id, filename: 'design1.jpg')
- designs.create!(issue_id: 2, project_id: project.id, filename: 'design2.jpg')
- designs.create!(issue_id: 4, project_id: project.id, filename: 'design3.jpg')
-
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
- end
-
- it 'correctly schedules background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(2.minutes, [1, 2])
-
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(4.minutes, [4])
-
- # Issue 3 should be skipped because it doesn't have any designs
- expect(BackgroundMigrationWorker.jobs.size).to eq(2)
- end
- end
- end
-end
diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb
index a48dc457c38..de766d5ce09 100644
--- a/spec/models/design_management/design_collection_spec.rb
+++ b/spec/models/design_management/design_collection_spec.rb
@@ -37,9 +37,11 @@ RSpec.describe DesignManagement::DesignCollection do
it 'inserts the design after any existing designs' do
design1 = collection.find_or_create_design!(filename: 'design1.jpg')
+ design1.update!(relative_position: 100)
+
design2 = collection.find_or_create_design!(filename: 'design2.jpg')
- expect(design1.relative_position).to be < design2.relative_position
+ expect(collection.designs.ordered(issue.project)).to eq([design1, design2])
end
end
diff --git a/spec/presenters/packages/detail/package_presenter_spec.rb b/spec/presenters/packages/detail/package_presenter_spec.rb
index a29942c3dac..3a13aca6c7a 100644
--- a/spec/presenters/packages/detail/package_presenter_spec.rb
+++ b/spec/presenters/packages/detail/package_presenter_spec.rb
@@ -74,6 +74,15 @@ RSpec.describe ::Packages::Detail::PackagePresenter do
end
end
+ context 'with conan metadata' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:expected_package_details) { super().merge(conan_metadatum: package.conan_metadatum) }
+
+ it 'returns conan_metadatum' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
context 'with composer metadata' do
let(:package) { create(:composer_package, :with_metadatum, sha: '123', project: project) }
let(:expected_package_details) { super().merge(composer_metadatum: package.composer_metadatum) }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index edd585cb4b6..a91519a710f 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -202,7 +202,6 @@ RSpec.describe EventCreateService do
end
it 'records the event in the event counter' do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
@@ -244,7 +243,6 @@ RSpec.describe EventCreateService do
it_behaves_like 'service for creating a push event', PushEventPayloadService
it 'records the event in the event counter' do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
@@ -268,7 +266,6 @@ RSpec.describe EventCreateService do
it_behaves_like 'service for creating a push event', BulkPushEventPayloadService
it 'records the event in the event counter' do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
@@ -323,7 +320,6 @@ RSpec.describe EventCreateService do
end
it 'records the event in the event counter' do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
@@ -351,7 +347,6 @@ RSpec.describe EventCreateService do
end
it 'records the event in the event counter' do
- stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
diff --git a/spec/services/jira_import/cloud_users_mapper_service_spec.rb b/spec/services/jira_import/cloud_users_mapper_service_spec.rb
new file mode 100644
index 00000000000..591f80f3efc
--- /dev/null
+++ b/spec/services/jira_import/cloud_users_mapper_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe JiraImport::CloudUsersMapperService do
+ let(:start_at) { 7 }
+ let(:url) { "/rest/api/2/users?maxResults=50&startAt=#{start_at}" }
+ let(:jira_users) do
+ [
+ { 'accountId' => 'abcd', 'displayName' => 'user1' },
+ { 'accountId' => 'efg' },
+ { 'accountId' => 'hij', 'displayName' => 'user3', 'emailAddress' => 'user3@example.com' }
+ ]
+ end
+
+ describe '#execute' do
+ it_behaves_like 'mapping jira users'
+ end
+end
diff --git a/spec/services/jira_import/server_users_mapper_service_spec.rb b/spec/services/jira_import/server_users_mapper_service_spec.rb
new file mode 100644
index 00000000000..22cb0327cc5
--- /dev/null
+++ b/spec/services/jira_import/server_users_mapper_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe JiraImport::ServerUsersMapperService do
+ let(:start_at) { 7 }
+ let(:url) { "/rest/api/2/user/search?username=''&maxResults=50&startAt=#{start_at}" }
+ let(:jira_users) do
+ [
+ { 'key' => 'abcd', 'name' => 'user1' },
+ { 'key' => 'efg' },
+ { 'key' => 'hij', 'name' => 'user3', 'emailAddress' => 'user3@example.com' }
+ ]
+ end
+
+ describe '#execute' do
+ it_behaves_like 'mapping jira users'
+ end
+end
diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb
index 64cdc70f612..efb303dab9f 100644
--- a/spec/services/jira_import/users_importer_spec.rb
+++ b/spec/services/jira_import/users_importer_spec.rb
@@ -14,6 +14,27 @@ RSpec.describe JiraImport::UsersImporter do
subject { importer.execute }
describe '#execute' do
+ let(:mapped_users) do
+ [
+ {
+ jira_account_id: 'acc1',
+ jira_display_name: 'user1',
+ jira_email: 'sample@jira.com',
+ gitlab_id: nil,
+ gitlab_username: nil,
+ gitlab_name: nil
+ },
+ {
+ jira_account_id: 'acc2',
+ jira_display_name: 'user2',
+ jira_email: nil,
+ gitlab_id: nil,
+ gitlab_username: nil,
+ gitlab_name: nil
+ }
+ ]
+ end
+
before do
stub_jira_service_test
project.add_maintainer(user)
@@ -25,53 +46,83 @@ RSpec.describe JiraImport::UsersImporter do
end
end
- context 'when Jira import is configured correctly' do
- let_it_be(:jira_service) { create(:jira_service, project: project, active: true) }
- let(:client) { double }
+ RSpec.shared_examples 'maps jira users to gitlab users' do
+ context 'when Jira import is configured correctly' do
+ let_it_be(:jira_service) { create(:jira_service, project: project, active: true) }
+ let(:client) { double }
- before do
- expect(importer).to receive(:client).and_return(client)
- end
-
- context 'when jira client raises an error' do
- it 'returns an error response' do
- expect(client).to receive(:get).and_raise(Timeout::Error)
-
- expect(subject.error?).to be_truthy
- expect(subject.message).to include('There was an error when communicating to Jira')
- end
- end
-
- context 'when jira client returns result' do
before do
- allow(client).to receive(:get).with('/rest/api/2/users?maxResults=50&startAt=7')
- .and_return(jira_users)
+ expect(importer).to receive(:client).at_least(1).and_return(client)
+ allow(client).to receive_message_chain(:ServerInfo, :all, :deploymentType).and_return(deployment_type)
end
- context 'when jira client returns an empty array' do
- let(:jira_users) { [] }
+ context 'when jira client raises an error' do
+ it 'returns an error response' do
+ expect(client).to receive(:get).and_raise(Timeout::Error)
- it 'retturns nil payload' do
- expect(subject.success?).to be_truthy
- expect(subject.payload).to be_nil
+ expect(subject.error?).to be_truthy
+ expect(subject.message).to include('There was an error when communicating to Jira')
end
end
- context 'when jira client returns an results' do
- let(:jira_users) { [{ 'name' => 'user1' }, { 'name' => 'user2' }] }
- let(:mapped_users) { [{ jira_display_name: 'user1', gitlab_id: 5 }] }
+ context 'when jira client returns result' do
+ context 'when jira client returns an empty array' do
+ let(:jira_users) { [] }
- before do
- expect(JiraImport::UsersMapper).to receive(:new).with(project, jira_users)
- .and_return(double(execute: mapped_users))
+ it 'retturns nil payload' do
+ expect(subject.success?).to be_truthy
+ expect(subject.payload).to be_empty
+ end
end
- it 'returns the mapped users' do
- expect(subject.success?).to be_truthy
- expect(subject.payload).to eq(mapped_users)
+ context 'when jira client returns an results' do
+ it 'returns the mapped users' do
+ expect(subject.success?).to be_truthy
+ expect(subject.payload).to eq(mapped_users)
+ end
end
end
end
end
+
+ context 'when Jira instance is of Server deployment type' do
+ let(:deployment_type) { 'Server' }
+ let(:url) { "/rest/api/2/user/search?username=''&maxResults=50&startAt=#{start_at}" }
+ let(:jira_users) do
+ [
+ { 'key' => 'acc1', 'name' => 'user1', 'emailAddress' => 'sample@jira.com' },
+ { 'key' => 'acc2', 'name' => 'user2' }
+ ]
+ end
+
+ before do
+ allow_next_instance_of(JiraImport::ServerUsersMapperService) do |instance|
+ allow(instance).to receive(:client).and_return(client)
+ allow(client).to receive(:get).with(url).and_return(jira_users)
+ end
+ end
+
+ it_behaves_like 'maps jira users to gitlab users'
+ end
+
+ context 'when Jira instance is of Cloud deploymet type' do
+ let(:deployment_type) { 'Cloud' }
+ let(:url) { "/rest/api/2/users?maxResults=50&startAt=#{start_at}" }
+ let(:jira_users) do
+ [
+ { 'accountId' => 'acc1', 'displayName' => 'user1', 'emailAddress' => 'sample@jira.com' },
+ { 'accountId' => 'acc2', 'displayName' => 'user2' }
+ ]
+ end
+
+ before do
+ allow_next_instance_of(JiraImport::CloudUsersMapperService) do |instance|
+ allow(instance).to receive(:client).and_return(client)
+ allow(client).to receive(:get).with(url).and_return(jira_users)
+ end
+ end
+
+ it_behaves_like 'maps jira users to gitlab users'
+ end
end
end
diff --git a/spec/services/jira_import/users_mapper_spec.rb b/spec/services/jira_import/users_mapper_spec.rb
deleted file mode 100644
index e5e8279a6fb..00000000000
--- a/spec/services/jira_import/users_mapper_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe JiraImport::UsersMapper do
- let_it_be(:project) { create(:project) }
-
- subject { described_class.new(project, jira_users).execute }
-
- describe '#execute' do
- context 'jira_users is nil' do
- let(:jira_users) { nil }
-
- it 'returns an empty array' do
- expect(subject).to be_empty
- end
- end
-
- context 'when jira_users is present' do
- let(:jira_users) do
- [
- { 'accountId' => 'abcd', 'displayName' => 'user1' },
- { 'accountId' => 'efg' },
- { 'accountId' => 'hij', 'displayName' => 'user3', 'emailAddress' => 'user3@example.com' }
- ]
- end
-
- # TODO: now we only create an array in a proper format
- # mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023
- let(:mapped_users) do
- [
- { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
- { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
- { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
- ]
- end
-
- it 'returns users mapped to Gitlab' do
- expect(subject).to eq(mapped_users)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
new file mode 100644
index 00000000000..7fc7ff8a8de
--- /dev/null
+++ b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'mapping jira users' do
+ let(:client) { double }
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:jira_service) { create(:jira_service, project: project, active: true) }
+
+ before do
+ allow(subject).to receive(:client).and_return(client)
+ allow(client).to receive(:get).with(url).and_return(jira_users)
+ end
+
+ subject { described_class.new(jira_service, start_at) }
+
+ context 'jira_users is nil' do
+ let(:jira_users) { nil }
+
+ it 'returns an empty array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when jira_users is present' do
+ # TODO: now we only create an array in a proper format
+ # mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023
+ let(:mapped_users) do
+ [
+ { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
+ ]
+ end
+
+ it 'returns users mapped to Gitlab' do
+ expect(subject.execute).to eq(mapped_users)
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index c05f247332a..1e928bc3c57 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -848,22 +848,22 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.158.0.tgz#300d416184a2b0e05f15a96547f726e1825b08a1"
integrity sha512-5OJl+7TsXN9PJhY6/uwi+mTwmDZa9n/6119rf77orQ/joFYUypaYhBmy/1TcKVPsy5Zs6KCxE1kmGsfoXc1TYA==
-"@gitlab/ui@18.7.0":
- version "18.7.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-18.7.0.tgz#aee0054d50e50aaf9e7c4ea4b9e36ca4b97102bf"
- integrity sha512-y1Gix1aCHvVO+zh6TCDmsCr97nLLHFnfEZRtg69EBnLBCLgwBcucC3mNeR4Q2EHTWjy/5U035UkyW6LDRX05mA==
+"@gitlab/ui@20.1.1":
+ version "20.1.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.1.1.tgz#990ce3a0883af5c62b0f56be1e0b244b918a9159"
+ integrity sha512-xtWdvzC33p8i76afHtnQKuUN7fGWV89uIKfIf9/WyygXZqUFKbSW076m/9iLRxHaCYNW7ucJe3fbEW+iAgWcuA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
bootstrap-vue "2.13.1"
copy-to-clipboard "^3.0.8"
+ dompurify "^2.0.12"
echarts "^4.2.1"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
lodash "^4.17.14"
portal-vue "^2.1.6"
resize-observer-polyfill "^1.5.1"
- sanitize-html "^1.22.0"
url-search-params-polyfill "^5.0.0"
vue-runtime-helpers "^1.1.2"
@@ -4094,7 +4094,7 @@ dom-serialize@^2.2.0:
extend "^3.0.0"
void-elements "^2.0.0"
-dom-serializer@0, dom-serializer@^0.2.1:
+dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
@@ -4143,17 +4143,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
-domhandler@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.0.0.tgz#51cd13efca31da95bbb0c5bee3a48300e333b3e9"
- integrity sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==
- dependencies:
- domelementtype "^2.0.1"
-
-dompurify@^2.0.11:
- version "2.0.11"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.11.tgz#cd47935774230c5e478b183a572e726300b3891d"
- integrity sha512-qVoGPjIW9IqxRij7klDQQ2j6nSe4UNWANBhZNLnsS7ScTtLb+3YdxkRY8brNTpkUiTtcXsCJO+jS0UCDfenLuA==
+dompurify@^2.0.11, dompurify@^2.0.12:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.12.tgz#284a2b041e1c60b8e72d7b4d2fadad36141254ae"
+ integrity sha512-Fl8KseK1imyhErHypFPA8qpq9gPzlsJ/EukA6yk9o0gX23p1TzC+rh9LqNg1qvErRTc0UNMYlKxEGSfSh43NDg==
domutils@^1.5.1:
version "1.6.2"
@@ -4163,15 +4156,6 @@ domutils@^1.5.1:
dom-serializer "0"
domelementtype "1"
-domutils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.0.0.tgz#15b8278e37bfa8468d157478c58c367718133c08"
- integrity sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==
- dependencies:
- dom-serializer "^0.2.1"
- domelementtype "^2.0.1"
- domhandler "^3.0.0"
-
dot-prop@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
@@ -5843,16 +5827,6 @@ htmlparser2@^3.10.0:
inherits "^2.0.1"
readable-stream "^3.0.6"
-htmlparser2@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
- integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==
- dependencies:
- domelementtype "^2.0.1"
- domhandler "^3.0.0"
- domutils "^2.0.0"
- entities "^2.0.0"
-
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
@@ -7646,11 +7620,6 @@ lodash.differencewith@~4.5.0:
resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7"
integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc=
-lodash.escaperegexp@^4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
- integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
-
lodash.find@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
@@ -7706,11 +7675,6 @@ lodash.isplainobject@^4.0.6:
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
-lodash.isstring@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
- integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
-
lodash.kebabcase@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -7731,11 +7695,6 @@ lodash.mapvalues@^4.6.0:
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
-lodash.mergewith@^4.6.1:
- version "4.6.2"
- resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
- integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
-
lodash.pick@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@@ -9500,7 +9459,7 @@ postcss-value-parser@^4.0.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
-postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7:
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7:
version "7.0.30"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2"
integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==
@@ -10453,22 +10412,6 @@ sane@^4.0.3:
minimist "^1.1.1"
walker "~1.0.5"
-sanitize-html@^1.22.0:
- version "1.22.0"
- resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.22.0.tgz#9df779c53cf5755adb2322943c21c1c1dffca7bf"
- integrity sha512-3RPo65mbTKpOAdAYWU496MSty1YbB3Y5bjwL5OclgaSSMtv65xvM7RW/EHRumzaZ1UddEJowCbSdK0xl5sAu0A==
- dependencies:
- chalk "^2.4.1"
- htmlparser2 "^4.1.0"
- lodash.clonedeep "^4.5.0"
- lodash.escaperegexp "^4.1.2"
- lodash.isplainobject "^4.0.6"
- lodash.isstring "^4.0.1"
- lodash.mergewith "^4.6.1"
- postcss "^7.0.27"
- srcset "^2.0.1"
- xtend "^4.0.1"
-
sass-graph@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
@@ -10968,11 +10911,6 @@ sql.js@^0.4.0:
resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-0.4.0.tgz#23be9635520eb0ff43a741e7e830397266e88445"
integrity sha1-I76WNVIOsP9Dp0Hn6DA5cmbohEU=
-srcset@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/srcset/-/srcset-2.0.1.tgz#8f842d357487eb797f413d9c309de7a5149df5ac"
- integrity sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ==
-
sshpk@^1.7.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629"