summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-12 12:10:49 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-12 12:10:49 +0000
commitbbfd13e575237aaa69a49caf1e23ebd878c2f824 (patch)
treeecf9f7db38629b98b804dfdfc23ab7234bdd642d /app
parent9c07ab8c6975de1046bd65b36f3d34f5408dac13 (diff)
downloadgitlab-ce-bbfd13e575237aaa69a49caf1e23ebd878c2f824.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_column_new.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue16
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_list_header_new.vue28
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue_new.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue2
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_editable_item.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue2
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js8
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue156
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js4
-rw-r--r--app/assets/stylesheets/page_bundles/error_tracking_index.scss4
-rw-r--r--app/graphql/types/alert_management/domain_filter_enum.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb4
-rw-r--r--app/models/namespace/package_setting.rb16
-rw-r--r--app/models/packages/package.rb6
-rw-r--r--app/models/project.rb18
-rw-r--r--app/models/service.rb7
-rw-r--r--app/services/bulk_create_integration_service.rb4
-rw-r--r--app/services/packages/maven/find_or_create_package_service.rb6
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/bulk_import_worker.rb53
-rw-r--r--app/workers/bulk_imports/entity_worker.rb23
34 files changed, 341 insertions, 131 deletions
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 753e6941c43..ef7bfae02a2 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -11,6 +11,11 @@ export default {
BoardListHeader,
BoardList,
},
+ inject: {
+ boardId: {
+ default: '',
+ },
+ },
props: {
list: {
type: Object,
@@ -27,11 +32,6 @@ export default {
default: false,
},
},
- inject: {
- boardId: {
- default: '',
- },
- },
data() {
return {
detailIssue: boardsStore.detail,
diff --git a/app/assets/javascripts/boards/components/board_column_new.vue b/app/assets/javascripts/boards/components/board_column_new.vue
index 7839f45c48b..ad49936567d 100644
--- a/app/assets/javascripts/boards/components/board_column_new.vue
+++ b/app/assets/javascripts/boards/components/board_column_new.vue
@@ -9,6 +9,11 @@ export default {
BoardListHeader,
BoardList,
},
+ inject: {
+ boardId: {
+ default: '',
+ },
+ },
props: {
list: {
type: Object,
@@ -25,11 +30,6 @@ export default {
default: false,
},
},
- inject: {
- boardId: {
- default: '',
- },
- },
computed: {
...mapState(['filterParams']),
...mapGetters(['getIssuesByList']),
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 50782781538..4bce0de7c12 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -49,6 +49,14 @@ export default {
GlModal,
BoardConfigurationOptions,
},
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ rootPath: {
+ default: '',
+ },
+ },
props: {
canAdminBoard: {
type: Boolean,
@@ -92,14 +100,6 @@ export default {
required: true,
},
},
- inject: {
- fullPath: {
- default: '',
- },
- rootPath: {
- default: '',
- },
- },
data() {
return {
board: { ...boardDefaults, ...this.currentBoard },
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 3db5c2e0830..814d261e808 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -31,6 +31,11 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ inject: {
+ boardId: {
+ default: '',
+ },
+ },
props: {
list: {
type: Object,
@@ -47,11 +52,6 @@ export default {
default: false,
},
},
- inject: {
- boardId: {
- default: '',
- },
- },
data() {
return {
weightFeatureAvailable: false,
diff --git a/app/assets/javascripts/boards/components/board_list_header_new.vue b/app/assets/javascripts/boards/components/board_list_header_new.vue
index 44eb2aa34c2..06f39eceb08 100644
--- a/app/assets/javascripts/boards/components/board_list_header_new.vue
+++ b/app/assets/javascripts/boards/components/board_list_header_new.vue
@@ -37,6 +37,20 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ inject: {
+ boardId: {
+ default: '',
+ },
+ weightFeatureAvailable: {
+ default: false,
+ },
+ scopedLabelsAvailable: {
+ default: false,
+ },
+ currentUserId: {
+ default: null,
+ },
+ },
props: {
list: {
type: Object,
@@ -53,20 +67,6 @@ export default {
default: false,
},
},
- inject: {
- boardId: {
- default: '',
- },
- weightFeatureAvailable: {
- default: false,
- },
- scopedLabelsAvailable: {
- default: false,
- },
- currentUserId: {
- default: null,
- },
- },
computed: {
...mapState(['activeId']),
isLoggedIn() {
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index a9e6d768656..2b0ddbed7b3 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -16,13 +16,13 @@ export default {
GlButton,
},
mixins: [glFeatureFlagMixin()],
+ inject: ['groupId'],
props: {
list: {
type: Object,
required: true,
},
},
- inject: ['groupId'],
data() {
return {
title: '',
diff --git a/app/assets/javascripts/boards/components/board_new_issue_new.vue b/app/assets/javascripts/boards/components/board_new_issue_new.vue
index 5766484a3ff..674a49e01ef 100644
--- a/app/assets/javascripts/boards/components/board_new_issue_new.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue_new.vue
@@ -18,13 +18,13 @@ export default {
GlButton,
},
mixins: [glFeatureFlagMixin()],
+ inject: ['groupId', 'weightFeatureAvailable', 'boardWeight'],
props: {
list: {
type: Object,
required: true,
},
},
- inject: ['groupId', 'weightFeatureAvailable', 'boardWeight'],
data() {
return {
title: '',
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index a635eb012cc..457d0d4dcd6 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -27,6 +27,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
mixins: [issueCardInner],
+ inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
props: {
issue: {
type: Object,
@@ -43,7 +44,6 @@ export default {
default: false,
},
},
- inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
data() {
return {
limitBeforeCounter: 2,
diff --git a/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
index ac0b16914ef..75cf1f0b9e1 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
@@ -25,6 +25,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
mixins: [issueCardInner],
+ inject: ['groupId', 'rootPath'],
props: {
issue: {
type: Object,
@@ -41,7 +42,6 @@ export default {
default: false,
},
},
- inject: ['groupId', 'rootPath'],
data() {
return {
limitBeforeCounter: 2,
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index 6c16005e7f4..f6b00b695da 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -11,13 +11,13 @@ export default {
GlIcon,
GlTooltip,
},
+ inject: ['timeTrackingLimitToHours'],
props: {
estimate: {
type: Number,
required: true,
},
},
- inject: ['timeTrackingLimitToHours'],
computed: {
title() {
return stringifyTime(
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 446023e1072..aecb2125e04 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -33,13 +33,13 @@ export default {
GlDropdownText,
GlSearchBoxByType,
},
+ inject: ['groupId'],
props: {
list: {
type: Object,
required: true,
},
},
- inject: ['groupId'],
data() {
return {
initialLoading: true,
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
index b4fe16de695..61863bbe2a9 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
@@ -3,6 +3,7 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui';
export default {
components: { GlButton, GlLoadingIcon },
+ inject: ['canUpdate'],
props: {
title: {
type: String,
@@ -25,7 +26,6 @@ export default {
default: true,
},
},
- inject: ['canUpdate'],
data() {
return {
edit: false,
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
index d540ee5f3c7..dcf769e6fe5 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
@@ -14,12 +14,12 @@ export default {
LabelsSelect,
GlLabel,
},
+ inject: ['labelsFetchPath', 'labelsManagePath', 'labelsFilterBasePath'],
data() {
return {
loading: false,
};
},
- inject: ['labelsFetchPath', 'labelsManagePath', 'labelsFilterBasePath'],
computed: {
...mapGetters(['activeIssue', 'projectPathForActiveIssue']),
selectedLabels() {
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index 17a12e84a37..738c8fb927e 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -18,6 +18,10 @@ export default (params = {}) => {
BoardsSelector,
},
apolloProvider,
+ provide: {
+ fullPath: params.fullPath,
+ rootPath: params.rootPath,
+ },
data() {
const { dataset } = boardsSwitcherElement;
@@ -35,10 +39,6 @@ export default (params = {}) => {
return { boardsSelectorProps };
},
- provide: {
- fullPath: params.fullPath,
- rootPath: params.rootPath,
- },
render(createElement) {
return createElement(BoardsSelector, {
props: this.boardsSelectorProps,
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 2880d649075..9217466a0b5 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -1,5 +1,5 @@
<script>
-import { GlAlert, GlLoadingIcon, GlTabs } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -28,12 +28,13 @@ const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export default {
components: {
- CommitForm,
CiLint,
+ CommitForm,
EditorTab,
GlAlert,
GlLoadingIcon,
GlTabs,
+ GlTab,
PipelineGraph,
TextEditor,
ValidationSegment,
@@ -317,16 +318,15 @@ export default {
:commit-sha="lastCommitSha"
/>
</editor-tab>
- <editor-tab
+ <gl-tab
v-if="glFeatures.ciConfigVisualizationTab"
:lazy="true"
:title="$options.i18n.tabGraph"
- :title-link-attributes="{ 'data-testid': 'visualization-tab-btn' }"
data-testid="visualization-tab"
>
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" />
- </editor-tab>
+ </gl-tab>
<editor-tab :title="$options.i18n.tabLint">
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index 17a880036e7..20ac8f5a467 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -1,21 +1,37 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
+import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as Flash } from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../mr_widget_author.vue';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
import { __ } from '~/locale';
+import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
export default {
name: 'MRWidgetAutoMergeEnabled',
+ apollo: {
+ state: {
+ query: autoMergeEnabledQuery,
+ skip() {
+ return !this.glFeatures.mergeRequestWidgetGraphql;
+ },
+ variables() {
+ return this.mergeRequestQueryVariables;
+ },
+ update: (data) => data.project?.mergeRequest,
+ },
+ },
components: {
MrWidgetAuthor,
statusIcon,
GlLoadingIcon,
+ GlSkeletonLoader,
},
- mixins: [autoMergeMixin],
+ mixins: [autoMergeMixin, glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
mr: {
type: Object,
@@ -30,20 +46,47 @@ export default {
},
data() {
return {
+ state: {},
isCancellingAutoMerge: false,
isRemovingSourceBranch: false,
};
},
computed: {
+ loading() {
+ return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
+ },
+ mergeUser() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.mergeUser;
+ }
+
+ return this.mr.setToAutoMergeBy;
+ },
+ targetBranch() {
+ return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).targetBranch;
+ },
+ shouldRemoveSourceBranch() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.state.shouldRemoveSourceBranch || this.state.forceRemoveSourceBranch;
+ }
+
+ return this.mr.shouldRemoveSourceBranch;
+ },
+ autoMergeStrategy() {
+ return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).autoMergeStrategy;
+ },
canRemoveSourceBranch() {
- const {
- shouldRemoveSourceBranch,
- canRemoveSourceBranch,
- mergeUserId,
- currentUserId,
- } = this.mr;
+ const { currentUserId } = this.mr;
+ const mergeUserId = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.state.mergeUser?.id
+ : this.mr.mergeUserId;
+ const canRemoveSourceBranch = this.glFeatures.mergeRequestWidgetGraphql
+ ? this.state.userPermissions.removeSourceBranch
+ : this.mr.canRemoveSourceBranch;
- return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId;
+ return (
+ !this.shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId
+ );
},
},
methods: {
@@ -63,7 +106,7 @@ export default {
removeSourceBranch() {
const options = {
sha: this.mr.sha,
- auto_merge_strategy: this.mr.autoMergeStrategy,
+ auto_merge_strategy: this.autoMergeStrategy,
should_remove_source_branch: true,
};
@@ -86,49 +129,64 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
- <status-icon status="success" />
- <div class="media-body">
- <h4 class="d-flex align-items-start">
- <span class="gl-mr-3">
- <span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span>
- <mr-widget-author :author="mr.setToAutoMergeBy" />
- <span class="js-status-text-after-author">{{ statusTextAfterAuthor }}</span>
- </span>
- <a
- v-if="mr.canCancelAutomaticMerge"
- :disabled="isCancellingAutoMerge"
- role="button"
- href="#"
- class="btn btn-sm btn-default js-cancel-auto-merge"
- @click.prevent="cancelAutomaticMerge"
- >
- <gl-loading-icon v-if="isCancellingAutoMerge" inline class="gl-mr-1" />
- {{ cancelButtonText }}
- </a>
- </h4>
- <section class="mr-info-list">
- <p>
- {{ s__('mrWidget|The changes will be merged into') }}
- <a :href="mr.targetBranchPath" class="label-branch">{{ mr.targetBranch }}</a>
- </p>
- <p v-if="mr.shouldRemoveSourceBranch">
- {{ s__('mrWidget|The source branch will be deleted') }}
- </p>
- <p v-else class="d-flex align-items-start">
- <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span>
+ <div v-if="loading" class="gl-w-full mr-conflict-loader">
+ <gl-skeleton-loader :width="334" :height="30">
+ <rect x="0" y="3" width="24" height="24" rx="4" />
+ <rect x="32" y="7" width="150" height="16" rx="4" />
+ <rect x="190" y="7" width="144" height="16" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ <template v-else>
+ <status-icon status="success" />
+ <div class="media-body">
+ <h4 class="gl-display-flex">
+ <span class="gl-mr-3">
+ <span class="js-status-text-before-author" data-testid="beforeStatusText">{{
+ statusTextBeforeAuthor
+ }}</span>
+ <mr-widget-author :author="mergeUser" />
+ <span class="js-status-text-after-author" data-testid="afterStatusText">{{
+ statusTextAfterAuthor
+ }}</span>
+ </span>
<a
- v-if="canRemoveSourceBranch"
- :disabled="isRemovingSourceBranch"
+ v-if="mr.canCancelAutomaticMerge"
+ :disabled="isCancellingAutoMerge"
role="button"
- class="btn btn-sm btn-default js-remove-source-branch"
href="#"
- @click.prevent="removeSourceBranch"
+ class="btn btn-sm btn-default js-cancel-auto-merge"
+ data-testid="cancelAutomaticMergeButton"
+ @click.prevent="cancelAutomaticMerge"
>
- <gl-loading-icon v-if="isRemovingSourceBranch" inline class="gl-mr-1" />
- {{ s__('mrWidget|Delete source branch') }}
+ <gl-loading-icon v-if="isCancellingAutoMerge" inline class="gl-mr-1" />
+ {{ cancelButtonText }}
</a>
- </p>
- </section>
- </div>
+ </h4>
+ <section class="mr-info-list">
+ <p>
+ {{ s__('mrWidget|The changes will be merged into') }}
+ <a :href="mr.targetBranchPath" class="label-branch">{{ targetBranch }}</a>
+ </p>
+ <p v-if="shouldRemoveSourceBranch">
+ {{ s__('mrWidget|The source branch will be deleted') }}
+ </p>
+ <p v-else class="gl-display-flex">
+ <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span>
+ <a
+ v-if="canRemoveSourceBranch"
+ :disabled="isRemovingSourceBranch"
+ role="button"
+ class="btn btn-sm btn-default js-remove-source-branch"
+ href="#"
+ data-testid="removeSourceBranchButton"
+ @click.prevent="removeSourceBranch"
+ >
+ <gl-loading-icon v-if="isRemovingSourceBranch" inline class="gl-mr-1" />
+ {{ s__('mrWidget|Delete source branch') }}
+ </a>
+ </p>
+ </section>
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 30da9947859..a2771bc4bfb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -1,7 +1,10 @@
<script>
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
+import autoMergeFailedQuery from '../../queries/states/auto_merge_failed.query.graphql';
+import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
export default {
name: 'MRWidgetAutoMergeFailed',
@@ -10,6 +13,19 @@ export default {
GlLoadingIcon,
GlButton,
},
+ mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
+ apollo: {
+ mergeError: {
+ query: autoMergeFailedQuery,
+ skip() {
+ return !this.glFeatures.mergeRequestWidgetGraphql;
+ },
+ variables() {
+ return this.mergeRequestQueryVariables;
+ },
+ update: (data) => data.project?.mergeRequest?.mergeError,
+ },
+ },
props: {
mr: {
type: Object,
@@ -18,6 +34,7 @@ export default {
},
data() {
return {
+ mergeError: this.glFeatures.mergeRequestWidgetGraphql ? null : this.mr.mergeError,
isRefreshing: false,
};
},
@@ -36,7 +53,7 @@ export default {
<status-icon status="warning" />
<div class="media-body space-children gl-display-flex gl-flex-wrap gl-align-items-center">
<span class="bold">
- <template v-if="mr.mergeError">{{ mr.mergeError }}</template>
+ <template v-if="mergeError">{{ mergeError }}</template>
{{ s__('mrWidget|This merge request failed to be merged automatically') }}
</span>
<gl-button
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql
new file mode 100644
index 00000000000..64cd70fcf42
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.fragment.graphql
@@ -0,0 +1,15 @@
+fragment autoMergeEnabled on MergeRequest {
+ autoMergeStrategy
+ mergeUser {
+ name
+ username
+ webUrl
+ avatarUrl
+ }
+ targetBranch
+ shouldRemoveSourceBranch
+ forceRemoveSourceBranch
+ userPermissions {
+ removeSourceBranch
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql
new file mode 100644
index 00000000000..bdcb7a8206b
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql
@@ -0,0 +1,10 @@
+#import "./auto_merge_enabled.fragment.graphql"
+
+query autoMergeEnabledQuery($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $iid) {
+ ...autoMergeEnabled
+ mergeTrainsCount
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql
new file mode 100644
index 00000000000..2fe0d174b67
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_failed.query.graphql
@@ -0,0 +1,7 @@
+query autoMergeFailedQuery($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $iid) {
+ mergeError
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js
index 4949a73d372..ab0fe21cb99 100644
--- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js
+++ b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js
@@ -4,6 +4,9 @@ import { spriteIcon } from '~/lib/utils/common_utils';
const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings
+// Number of users to show in the autocomplete menu to avoid doing a mass fetch of 100+ avatars
+const memberLimit = 10;
+
const nonWordOrInteger = /\W|^\d+$/;
export const GfmAutocompleteType = {
@@ -74,6 +77,7 @@ export const tributeConfig = {
fillAttr: 'username',
lookup: (value) =>
value.type === groupType ? last(value.name.split(' / ')) : `${value.name}${value.username}`,
+ menuItemLimit: memberLimit,
menuItemTemplate: ({ original }) => {
const commonClasses = 'gl-avatar gl-avatar-s24 gl-flex-shrink-0';
const noAvatarClasses = `${commonClasses} gl-rounded-small
diff --git a/app/assets/stylesheets/page_bundles/error_tracking_index.scss b/app/assets/stylesheets/page_bundles/error_tracking_index.scss
index 65bddfb7890..5c49bcc0348 100644
--- a/app/assets/stylesheets/page_bundles/error_tracking_index.scss
+++ b/app/assets/stylesheets/page_bundles/error_tracking_index.scss
@@ -5,6 +5,10 @@
min-width: auto;
}
+ .filtered-search-box .form-control {
+ min-width: unset;
+ }
+
.sort-control {
.btn {
padding-right: 2rem;
diff --git a/app/graphql/types/alert_management/domain_filter_enum.rb b/app/graphql/types/alert_management/domain_filter_enum.rb
index 58dbc8bb2cf..a798cfb9ee9 100644
--- a/app/graphql/types/alert_management/domain_filter_enum.rb
+++ b/app/graphql/types/alert_management/domain_filter_enum.rb
@@ -6,7 +6,7 @@ module Types
graphql_name 'AlertManagementDomainFilter'
description 'Filters the alerts based on given domain'
- value 'operations', description: 'Alerts for operations domain '
+ value 'operations', description: 'Alerts for operations domain'
value 'threat_monitoring', description: 'Alerts for threat monitoring domain'
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 9f5ded0d2f0..ee7d5780f7a 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -175,6 +175,10 @@ module Types
calls_gitaly: true, description: 'Merge request commits excluding merge commits'
field :security_auto_fix, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if the merge request is created by @GitLab-Security-Bot.'
+ field :auto_merge_strategy, GraphQL::STRING_TYPE, null: true,
+ description: 'Selected auto merge strategy'
+ field :merge_user, Types::UserType, null: true,
+ description: 'User who merged this merge request'
def approved_by
object.approved_by_users
diff --git a/app/models/namespace/package_setting.rb b/app/models/namespace/package_setting.rb
index 2c34bd9edcc..a2064e020b3 100644
--- a/app/models/namespace/package_setting.rb
+++ b/app/models/namespace/package_setting.rb
@@ -4,9 +4,25 @@ class Namespace::PackageSetting < ApplicationRecord
self.primary_key = :namespace_id
self.table_name = 'namespace_package_settings'
+ PackageSettingNotImplemented = Class.new(StandardError)
+
+ PACKAGES_WITH_SETTINGS = %w[maven].freeze
+
belongs_to :namespace, inverse_of: :package_setting_relation
validates :namespace, presence: true
validates :maven_duplicates_allowed, inclusion: { in: [true, false] }
validates :maven_duplicate_exception_regex, untrusted_regexp: true, length: { maximum: 255 }
+
+ class << self
+ def duplicates_allowed?(package)
+ return true unless package
+ raise PackageSettingNotImplemented unless PACKAGES_WITH_SETTINGS.include?(package.package_type)
+
+ duplicates_allowed = package.package_settings["#{package.package_type}_duplicates_allowed"]
+ regex = ::Gitlab::UntrustedRegexp.new("\\A#{package.package_settings["#{package.package_type}_duplicate_exception_regex"]}\\z")
+
+ duplicates_allowed || regex.match?(package.name)
+ end
+ end
end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 32c43616877..2067a800ad5 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -200,6 +200,12 @@ class Packages::Package < ApplicationRecord
debian? && !version.nil?
end
+ def package_settings
+ strong_memoize(:package_settings) do
+ project.namespace.package_settings
+ end
+ end
+
private
def composer_tag_version?
diff --git a/app/models/project.rb b/app/models/project.rb
index fdb16640cb4..8939b2ee84d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1333,19 +1333,11 @@ class Project < ApplicationRecord
end
def external_wiki
- if has_external_wiki.nil?
- cache_has_external_wiki
- end
+ cache_has_external_wiki if has_external_wiki.nil?
- if has_external_wiki
- @external_wiki ||= services.external_wikis.first
- else
- nil
- end
- end
+ return unless has_external_wiki?
- def cache_has_external_wiki
- update_column(:has_external_wiki, services.external_wikis.any?) if Gitlab::Database.read_write?
+ @external_wiki ||= services.external_wikis.first
end
def find_or_initialize_services
@@ -2707,6 +2699,10 @@ class Project < ApplicationRecord
objects.each_batch { |relation| out.concat(relation.pluck(:oid)) }
end
end
+
+ def cache_has_external_wiki
+ update_column(:has_external_wiki, services.external_wikis.any?) if Gitlab::Database.read_write?
+ end
end
Project.prepend_if_ee('EE::Project')
diff --git a/app/models/service.rb b/app/models/service.rb
index 57c099d6f04..9f17279d0a3 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -48,7 +48,6 @@ class Service < ApplicationRecord
after_commit :reset_updated_properties
after_commit :cache_project_has_external_issue_tracker
- after_commit :cache_project_has_external_wiki
belongs_to :project, inverse_of: :services
belongs_to :group, inverse_of: :services
@@ -469,12 +468,6 @@ class Service < ApplicationRecord
end
end
- def cache_project_has_external_wiki
- if project && !project.destroyed?
- project.cache_has_external_wiki
- end
- end
-
def valid_recipients?
activated? && !importing?
end
diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb
index 61c5565db60..df78c3645c7 100644
--- a/app/services/bulk_create_integration_service.rb
+++ b/app/services/bulk_create_integration_service.rb
@@ -38,10 +38,6 @@ class BulkCreateIntegrationService
if integration.external_issue_tracker?
Project.where(id: batch.select(:id)).update_all(has_external_issue_tracker: true)
end
-
- if integration.external_wiki?
- Project.where(id: batch.select(:id)).update_all(has_external_wiki: true)
- end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb
index 4107f8c6731..bce685cacdf 100644
--- a/app/services/packages/maven/find_or_create_package_service.rb
+++ b/app/services/packages/maven/find_or_create_package_service.rb
@@ -10,6 +10,10 @@ module Packages
::Packages::Maven::PackageFinder.new(params[:path], current_user, project: project)
.execute
+ unless Namespace::PackageSetting.duplicates_allowed?(package)
+ return ServiceResponse.error(message: 'Duplicate package is not allowed')
+ end
+
unless package
# Maven uploads several files during `mvn deploy` in next order:
# - my-company/my-app/1.0-SNAPSHOT/my-app.jar
@@ -48,7 +52,7 @@ module Packages
package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present?
- package
+ ServiceResponse.success(payload: { package: package })
end
end
end
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 8f5fac1a40b..dc4172e2f09 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -4,7 +4,7 @@
%li{ class: "branch-item js-branch-item js-branch-#{branch.name}", data: { name: branch.name } }
.branch-info
.branch-title
- = sprite_icon('fork', size: 12)
+ = sprite_icon('fork', size: 12, css_class: 'gl-flex-shrink-0')
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
= branch.name
- if branch.name == @repository.root_ref
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 9c319c8e906..05a41fceafe 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1434,6 +1434,14 @@
:tags: []
- :name: bulk_import
:feature_category: :importers
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent:
+ :tags: []
+- :name: bulk_imports_entity
+ :feature_category: :importers
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
diff --git a/app/workers/bulk_import_worker.rb b/app/workers/bulk_import_worker.rb
index 7828d046036..81099d4e5f7 100644
--- a/app/workers/bulk_import_worker.rb
+++ b/app/workers/bulk_import_worker.rb
@@ -7,9 +7,58 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker
sidekiq_options retry: false, dead: false
- worker_has_external_dependencies!
+ PERFORM_DELAY = 5.seconds
+ DEFAULT_BATCH_SIZE = 5
def perform(bulk_import_id)
- BulkImports::Importers::GroupsImporter.new(bulk_import_id).execute
+ @bulk_import = BulkImport.find_by_id(bulk_import_id)
+
+ return unless @bulk_import
+ return if @bulk_import.finished?
+ return @bulk_import.finish! if all_entities_processed? && @bulk_import.started?
+ return re_enqueue if max_batch_size_exceeded? # Do not start more jobs if max allowed are already running
+
+ @bulk_import.start! if @bulk_import.created?
+
+ created_entities.first(next_batch_size).each do |entity|
+ entity.start!
+
+ BulkImports::EntityWorker.perform_async(entity.id)
+ end
+
+ re_enqueue
+ end
+
+ private
+
+ def entities
+ @entities ||= @bulk_import.entities
+ end
+
+ def started_entities
+ entities.with_status(:started)
+ end
+
+ def created_entities
+ entities.with_status(:created)
+ end
+
+ def all_entities_processed?
+ entities.all? { |entity| entity.finished? || entity.failed? }
+ end
+
+ def max_batch_size_exceeded?
+ started_entities.count >= DEFAULT_BATCH_SIZE
+ end
+
+ def next_batch_size
+ [DEFAULT_BATCH_SIZE - started_entities.count, 0].max
+ end
+
+ # A new BulkImportWorker job is enqueued to either
+ # - Process the new BulkImports::Entity created during import (e.g. for the subgroups)
+ # - Or to mark the `bulk_import` as finished
+ def re_enqueue
+ BulkImportWorker.perform_in(PERFORM_DELAY, @bulk_import.id)
end
end
diff --git a/app/workers/bulk_imports/entity_worker.rb b/app/workers/bulk_imports/entity_worker.rb
new file mode 100644
index 00000000000..9b29ad8f326
--- /dev/null
+++ b/app/workers/bulk_imports/entity_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class EntityWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ feature_category :importers
+
+ sidekiq_options retry: false, dead: false
+
+ worker_has_external_dependencies!
+
+ def perform(entity_id)
+ entity = BulkImports::Entity.with_status(:started).find_by_id(entity_id)
+
+ if entity
+ entity.update!(jid: jid)
+
+ BulkImports::Importers::GroupImporter.new(entity).execute
+ end
+ end
+ end
+end