diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-12 12:10:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-12 12:10:49 +0000 |
commit | bbfd13e575237aaa69a49caf1e23ebd878c2f824 (patch) | |
tree | ecf9f7db38629b98b804dfdfc23ab7234bdd642d /app | |
parent | 9c07ab8c6975de1046bd65b36f3d34f5408dac13 (diff) | |
download | gitlab-ce-bbfd13e575237aaa69a49caf1e23ebd878c2f824.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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 |