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--.rubocop_todo/layout/hash_alignment.yml15
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js4
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue2
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue1
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewers.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue60
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql28
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue17
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue107
-rw-r--r--app/assets/javascripts/work_items/constants.js4
-rw-r--r--app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql1
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/project.rb4
-rw-r--r--app/serializers/environment_serializer.rb4
-rw-r--r--config/feature_flags/development/work_items_create_from_markdown.yml8
-rw-r--r--db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb2
-rw-r--r--db/migrate/20210804150320_create_base_work_item_types.rb6
-rw-r--r--db/migrate/20210831203408_upsert_base_work_item_types.rb6
-rw-r--r--db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb5
-rw-r--r--db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb2
-rw-r--r--db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb2
-rw-r--r--db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb2
-rw-r--r--db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb8
-rw-r--r--db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb2
-rw-r--r--db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb2
-rw-r--r--db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb22
-rw-r--r--db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb2
-rw-r--r--db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb10
-rw-r--r--db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb7
-rw-r--r--db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb8
-rw-r--r--doc/operations/incident_management/incidents.md4
-rw-r--r--lib/gitlab/import_export/decompressed_archive_size_validator.rb20
-rw-r--r--locale/gitlab.pot15
-rw-r--r--qa/qa/ce/strategy.rb10
-rw-r--r--qa/qa/resource/personal_access_token.rb23
-rw-r--r--qa/qa/resource/personal_access_token_cache.rb12
-rwxr-xr-xscripts/static-analysis4
-rw-r--r--spec/factories/ci/builds.rb14
-rw-r--r--spec/frontend/issues/show/components/description_spec.js12
-rw-r--r--spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js21
-rw-r--r--spec/frontend/sidebar/reviewers_spec.js64
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js46
-rw-r--r--spec/frontend/work_items/mock_data.js1
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb5
-rw-r--r--spec/models/group_spec.rb7
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/serializers/environment_serializer_spec.rb31
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb2
56 files changed, 518 insertions, 176 deletions
diff --git a/.rubocop_todo/layout/hash_alignment.yml b/.rubocop_todo/layout/hash_alignment.yml
index 96bd299cb48..6f88f121a88 100644
--- a/.rubocop_todo/layout/hash_alignment.yml
+++ b/.rubocop_todo/layout/hash_alignment.yml
@@ -164,21 +164,6 @@ Layout/HashAlignment:
- 'app/models/user_status.rb'
- 'app/models/wiki.rb'
- 'app/models/work_items/type.rb'
- - 'db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb'
- - 'db/migrate/20210804150320_create_base_work_item_types.rb'
- - 'db/migrate/20210831203408_upsert_base_work_item_types.rb'
- - 'db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb'
- - 'db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb'
- - 'db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb'
- - 'db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb'
- - 'db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb'
- - 'db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb'
- - 'db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb'
- - 'db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb'
- - 'db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb'
- - 'db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb'
- - 'db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb'
- - 'db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb'
- 'ee/app/controllers/ee/search_controller.rb'
- 'ee/app/controllers/projects/integrations/zentao/issues_controller.rb'
- 'ee/app/graphql/mutations/iterations/cadences/create.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index d89e6180f1a..abcb6861189 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-157e6b6ad8fd7aa0ebdd43727f00b81f34b100a1
+0a03f045e065b9e7a157322fbe486e1f02fd8617
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index b151e1605da..b2e554bc913 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -285,7 +285,7 @@ export default {
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
/>
<div>
- <div data-testid="project-name">{{ project.name }}</div>
+ <div data-testid="project-name" data-qa-selector="project_name">{{ project.name }}</div>
<div class="gl-text-gray-500" data-testid="project-full-path">
{{ project.fullPath }}
</div>
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 0e33c712470..01d218438cf 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -7,6 +7,7 @@ import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
import { s__, __, sprintf } from '~/locale';
import { isUserBusy } from '~/set_status_modal/utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';
+import { state } from '~/sidebar/components/reviewers/sidebar_reviewers.vue';
import AjaxCache from './lib/utils/ajax_cache';
import { spriteIcon } from './lib/utils/common_utils';
import { parsePikadayDate } from './lib/utils/datetime_utility';
@@ -352,8 +353,7 @@ class GfmAutoComplete {
// Cache assignees & reviewers list for easier filtering later
assignees =
SidebarMediator.singleton?.store?.assignees?.map(createMemberSearchString) || [];
- reviewers =
- SidebarMediator.singleton?.store?.reviewers?.map(createMemberSearchString) || [];
+ reviewers = state.issuable?.reviewers?.nodes?.map(createMemberSearchString) || [];
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
return match && match.length ? match[1] : null;
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 8a8c50c7138..34c7ed69515 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -133,7 +133,7 @@ export default {
},
computed: {
workItemsEnabled() {
- return this.glFeatures.workItems;
+ return this.glFeatures.workItemsCreateFromMarkdown;
},
taskWorkItemType() {
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index d02526160fd..1343ad8246c 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -256,6 +256,7 @@ export default {
:error-message="i18n.branchesErrorMessage"
:show-header="showSectionHeaders"
data-testid="branches-section"
+ data-qa-selector="branches_section"
@selected="selectRef($event)"
/>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
index 9502b2e78b3..6f82178b6fd 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
@@ -33,7 +33,7 @@ export default {
return this.users.length > 2;
},
allReviewersCanMerge() {
- return this.users.every((user) => user.can_merge);
+ return this.users.every((user) => user.mergeRequestInteraction?.canMerge);
},
sidebarAvatarCounter() {
if (this.users.length > DEFAULT_MAX_COUNTER) {
@@ -48,7 +48,7 @@ export default {
return this.users.slice(0, collapsedLength);
},
tooltipTitleMergeStatus() {
- const mergeLength = this.users.filter((u) => u.can_merge).length;
+ const mergeLength = this.users.filter((u) => u.mergeRequestInteraction?.canMerge).length;
if (mergeLength === this.users.length) {
return '';
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar.vue
index 7961b7cd679..a7db3b3d09f 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar.vue
@@ -23,10 +23,10 @@ export default {
return sprintf(__("%{userName}'s avatar"), { userName: this.user.name });
},
avatarUrl() {
- return this.user.avatar || this.user.avatar_url || gon.default_avatar_url;
+ return this.user.avatarUrl || this.user.avatar_url || gon.default_avatar_url;
},
hasMergeIcon() {
- return !this.user.can_merge;
+ return !this.user.mergeRequestInteraction?.canMerge;
},
},
};
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
index c9b0a4ae2b3..f69c027e201 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
@@ -40,7 +40,7 @@ export default {
},
computed: {
cannotMerge() {
- return this.issuableType === 'merge_request' && !this.user.can_merge;
+ return this.issuableType === 'merge_request' && !this.user.mergeRequestInteraction?.canMerge;
},
tooltipTitle() {
if (this.cannotMerge && this.tooltipHasName) {
@@ -59,7 +59,7 @@ export default {
};
},
reviewerUrl() {
- return this.user.web_url;
+ return this.user.webUrl;
},
},
};
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
index 5729b958b5d..5e1172ad835 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
@@ -36,8 +36,8 @@ export default {
return !this.users.length;
},
sortedReviewers() {
- const canMergeUsers = this.users.filter((user) => user.can_merge);
- const canNotMergeUsers = this.users.filter((user) => !user.can_merge);
+ const canMergeUsers = this.users.filter((user) => user.mergeRequestInteraction?.canMerge);
+ const canNotMergeUsers = this.users.filter((user) => !user.mergeRequestInteraction?.canMerge);
return [...canMergeUsers, ...canNotMergeUsers];
},
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index e414aaf719b..b0d820ddd15 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -1,15 +1,23 @@
<script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
+import Vue from 'vue';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import createFlash from '~/flash';
import { __ } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import getMergeRequestReviewersQuery from '~/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql';
import ReviewerTitle from './reviewer_title.vue';
import Reviewers from './reviewers.vue';
+export const state = Vue.observable({
+ issuable: {},
+ loading: false,
+ initialLoading: true,
+});
+
export default {
name: 'SidebarReviewers',
components: {
@@ -40,18 +48,49 @@ export default {
required: true,
},
},
+ apollo: {
+ issuable: {
+ query: getMergeRequestReviewersQuery,
+ variables() {
+ return {
+ iid: this.issuableIid,
+ fullPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data.workspace?.issuable;
+ },
+ result() {
+ this.initialLoading = false;
+ },
+ error() {
+ createFlash({ message: __('An error occurred while fetching reviewers.') });
+ },
+ },
+ },
data() {
- return {
- store: new Store(),
- loading: false,
- };
+ return state;
},
computed: {
relativeUrlRoot() {
return gon.relative_url_root ?? '';
},
+ reviewers() {
+ return this.issuable.reviewers?.nodes || [];
+ },
+ graphqlFetching() {
+ return this.$apollo.queries.issuable.loading;
+ },
+ isLoading() {
+ return this.loading || this.$apollo.queries.issuable.loading;
+ },
+ canUpdate() {
+ return this.issuable.userPermissions?.updateMergeRequest || false;
+ },
},
created() {
+ this.store = new Store();
+
this.removeReviewer = this.store.removeReviewer.bind(this.store);
this.addReviewer = this.store.addReviewer.bind(this.store);
this.removeAllReviewers = this.store.removeAllReviewers.bind(this.store);
@@ -77,6 +116,7 @@ export default {
.then(() => {
this.loading = false;
refreshUserMergeRequestCounts();
+ this.$apollo.queries.issuable.refetch();
})
.catch(() => {
this.loading = false;
@@ -95,15 +135,15 @@ export default {
<template>
<div>
<reviewer-title
- :number-of-reviewers="store.reviewers.length"
- :loading="loading || store.isFetching.reviewers"
- :editable="store.editable"
+ :number-of-reviewers="reviewers.length"
+ :loading="isLoading"
+ :editable="canUpdate"
/>
<reviewers
- v-if="!store.isFetching.reviewers"
+ v-if="!initialLoading"
:root-path="relativeUrlRoot"
- :users="store.reviewers"
- :editable="store.editable"
+ :users="reviewers"
+ :editable="canUpdate"
:issuable-type="issuableType"
@request-review="requestReview"
/>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index 3aeb49eb422..217ca2e2548 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -105,7 +105,7 @@ export default {
</div>
</reviewer-avatar-link>
<gl-icon
- v-if="user.approved"
+ v-if="user.mergeRequestInteraction.approved"
v-gl-tooltip.left
:size="16"
:title="approvedByTooltipTitle(user)"
@@ -121,7 +121,7 @@ export default {
data-testid="re-request-success"
/>
<gl-button
- v-else-if="user.can_update_merge_request && user.reviewed"
+ v-else-if="user.mergeRequestInteraction.canUpdate && user.mergeRequestInteraction.reviewed"
v-gl-tooltip.left
:title="$options.i18n.reRequestReview"
:aria-label="$options.i18n.reRequestReview"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql
new file mode 100644
index 00000000000..3ee622ae3f2
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql
@@ -0,0 +1,28 @@
+#import "~/graphql_shared/fragments/user.fragment.graphql"
+#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
+
+query mergeRequestReviewers($fullPath: ID!, $iid: String!) {
+ workspace: project(fullPath: $fullPath) {
+ __typename
+ id
+ issuable: mergeRequest(iid: $iid) {
+ __typename
+ id
+ reviewers {
+ nodes {
+ ...User
+ ...UserAvailability
+ mergeRequestInteraction {
+ canMerge
+ canUpdate
+ approved
+ reviewed
+ }
+ }
+ }
+ userPermissions {
+ updateMergeRequest
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index e65f0cfb1f2..6a43fdb541f 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -95,8 +95,14 @@ export default {
toggle() {
this.isOpen = !this.isOpen;
},
- toggleAddForm() {
- this.isShownAddForm = !this.isShownAddForm;
+ showAddForm() {
+ this.isShownAddForm = true;
+ this.$nextTick(() => {
+ this.$refs.wiLinksForm.$refs.wiTitleInput?.$el.focus();
+ });
+ },
+ hideAddForm() {
+ this.isShownAddForm = false;
},
addChild(child) {
this.children = [child, ...this.children];
@@ -122,10 +128,10 @@ export default {
>
<h5 class="gl-m-0 gl-line-height-32 gl-flex-grow-1">{{ $options.i18n.title }}</h5>
<gl-button
- v-if="!isShownAddForm && canUpdate"
+ v-if="canUpdate"
category="secondary"
data-testid="toggle-add-form"
- @click="toggleAddForm"
+ @click="showAddForm"
>
{{ $options.i18n.addChildButtonLabel }}
</gl-button>
@@ -154,10 +160,11 @@ export default {
</div>
<work-item-links-form
v-if="isShownAddForm"
+ ref="wiLinksForm"
data-testid="add-links-form"
:issuable-gid="issuableGid"
:children-ids="childrenIds"
- @cancel="toggleAddForm"
+ @cancel="hideAddForm"
@addWorkItemChild="addChild"
/>
<div
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index fadba0753db..d01d59050e6 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -1,9 +1,11 @@
<script>
-import { GlAlert, GlForm, GlFormCombobox, GlButton } from '@gitlab/ui';
+import { GlAlert, GlFormGroup, GlForm, GlFormCombobox, GlButton, GlFormInput } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
+import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
+import { WORK_ITEM_TYPE_IDS } from '../../constants';
export default {
components: {
@@ -11,6 +13,8 @@ export default {
GlForm,
GlFormCombobox,
GlButton,
+ GlFormGroup,
+ GlFormInput,
},
inject: ['projectPath'],
props: {
@@ -28,6 +32,7 @@ export default {
apollo: {
availableWorkItems: {
query: projectWorkItemsQuery,
+ debounce: 200,
variables() {
return {
projectPath: this.projectPath,
@@ -50,8 +55,29 @@ export default {
availableWorkItems: [],
search: '',
error: null,
+ childToCreateTitle: null,
};
},
+ computed: {
+ actionsList() {
+ return [
+ {
+ label: this.$options.i18n.createChildOptionLabel,
+ fn: () => {
+ this.childToCreateTitle = this.search?.title || this.search;
+ },
+ },
+ ];
+ },
+ addOrCreateButtonLabel() {
+ return this.childToCreateTitle
+ ? this.$options.i18n.createChildOptionLabel
+ : this.$options.i18n.addTaskButtonLabel;
+ },
+ addOrCreateMethod() {
+ return this.childToCreateTitle ? this.createChild : this.addChild;
+ },
+ },
methods: {
getIdFromGraphQLId,
unsetError() {
@@ -79,35 +105,77 @@ export default {
}
})
.catch(() => {
- this.error = this.$options.i18n.errorMessage;
+ this.error = this.$options.i18n.addChildErrorMessage;
})
.finally(() => {
this.search = '';
});
},
+ createChild() {
+ this.$apollo
+ .mutate({
+ mutation: createWorkItemMutation,
+ variables: {
+ input: {
+ title: this.search?.title || this.search,
+ projectPath: this.projectPath,
+ workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
+ hierarchyWidget: {
+ parentId: this.issuableGid,
+ },
+ },
+ },
+ })
+ .then(({ data }) => {
+ if (data.workItemCreate?.errors?.length) {
+ [this.error] = data.workItemCreate.errors;
+ } else {
+ this.unsetError();
+ this.$emit('addWorkItemChild', data.workItemCreate.workItem);
+ }
+ })
+ .catch(() => {
+ this.error = this.$options.i18n.createChildErrorMessage;
+ })
+ .finally(() => {
+ this.search = '';
+ this.childToCreateTitle = null;
+ });
+ },
},
i18n: {
- inputLabel: __('Children'),
- errorMessage: s__(
+ inputLabel: __('Title'),
+ addTaskButtonLabel: s__('WorkItem|Add task'),
+ addChildErrorMessage: s__(
'WorkItem|Something went wrong when trying to add a child. Please try again.',
),
+ createChildOptionLabel: s__('WorkItem|Create task'),
+ createChildErrorMessage: s__(
+ 'WorkItem|Something went wrong when trying to create a child. Please try again.',
+ ),
+ placeholder: s__('WorkItem|Add a title'),
+ fieldValidationMessage: __('Maximum of 255 characters'),
},
};
</script>
<template>
<gl-form
- class="gl-mb-3 gl-bg-white gl-mb-3 gl-py-3 gl-px-4 gl-border gl-border-gray-100 gl-rounded-base"
+ class="gl-bg-white gl-mb-3 gl-p-4 gl-border gl-border-gray-100 gl-rounded-base"
+ @submit.prevent="createChild"
>
<gl-alert v-if="error" variant="danger" class="gl-mb-3" @dismiss="unsetError">
{{ error }}
</gl-alert>
+ <!-- Follow up issue to turn this functionality back on https://gitlab.com/gitlab-org/gitlab/-/issues/368757 -->
<gl-form-combobox
+ v-if="false"
v-model="search"
:token-list="availableWorkItems"
match-value-to-attr="title"
class="gl-mb-4"
:label-text="$options.i18n.inputLabel"
+ :action-list="actionsList"
label-sr-only
autofocus
>
@@ -117,11 +185,34 @@ export default {
<div>{{ item.title }}</div>
</div>
</template>
+ <template #action="{ item }">
+ <span class="gl-text-blue-500">{{ item.label }}</span>
+ </template>
</gl-form-combobox>
- <gl-button category="secondary" data-testid="add-child-button" @click="addChild">
- {{ s__('WorkItem|Add task') }}
+ <gl-form-group
+ :label="$options.i18n.inputLabel"
+ :description="$options.i18n.fieldValidationMessage"
+ >
+ <gl-form-input
+ ref="wiTitleInput"
+ v-model="search"
+ :placeholder="$options.i18n.placeholder"
+ maxlength="255"
+ class="gl-mb-3"
+ autofocus
+ />
+ </gl-form-group>
+ <gl-button
+ category="primary"
+ variant="confirm"
+ size="small"
+ type="submit"
+ :disabled="search.length === 0"
+ data-testid="add-child-button"
+ >
+ {{ $options.i18n.createChildOptionLabel }}
</gl-button>
- <gl-button category="tertiary" @click="$emit('cancel')">
+ <gl-button category="secondary" size="small" @click="$emit('cancel')">
{{ s__('WorkItem|Cancel') }}
</gl-button>
</gl-form>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 2b44642877b..00a5f050b77 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -35,3 +35,7 @@ export const WORK_ITEM_STATUS_TEXT = {
CLOSED: s__('WorkItem|Closed'),
OPEN: s__('WorkItem|Open'),
};
+
+export const WORK_ITEM_TYPE_IDS = {
+ TASK: 'gid://gitlab/WorkItems::Type/5',
+};
diff --git a/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql
index 0a406187a09..4cc23fa0071 100644
--- a/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql
@@ -5,5 +5,6 @@ mutation createWorkItem($input: WorkItemCreateInput!) {
workItem {
...WorkItem
}
+ errors
}
}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 9cafd442913..537c08c9ebb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -53,6 +53,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:realtime_labels, project)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_hierarchy, project)
+ push_force_frontend_feature_flag(:work_items_create_from_markdown, project&.work_items_create_from_markdown_feature_flag_enabled?)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/models/group.rb b/app/models/group.rb
index 07badd18965..4bf91474405 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -859,6 +859,10 @@ class Group < Namespace
feature_flag_enabled_for_self_or_ancestor?(:work_items_mvc_2)
end
+ def work_items_create_from_markdown_feature_flag_enabled?
+ feature_flag_enabled_for_self_or_ancestor?(:work_items_create_from_markdown)
+ end
+
# Check for enabled features, similar to `Project#feature_available?`
# NOTE: We still want to keep this after removing `Namespace#feature_available?`.
override :feature_available?
diff --git a/app/models/project.rb b/app/models/project.rb
index cfa726ede41..1a30a2fe4ec 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2995,6 +2995,10 @@ class Project < ApplicationRecord
group&.work_items_mvc_2_feature_flag_enabled? || Feature.enabled?(:work_items_mvc_2)
end
+ def work_items_create_from_markdown_feature_flag_enabled?
+ work_items_feature_flag_enabled? && (group&.work_items_create_from_markdown_feature_flag_enabled? || Feature.enabled?(:work_items_create_from_markdown))
+ end
+
def enqueue_record_project_target_platforms
return unless Gitlab.com?
return unless Feature.enabled?(:record_projects_target_platforms, self)
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index c73b1ad6073..6363d6276a7 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -34,8 +34,8 @@ class EnvironmentSerializer < BaseSerializer
# rubocop: disable CodeReuse/ActiveRecord
def itemize(resource)
items = resource.order('folder ASC')
- .group('COALESCE(environment_type, name)')
- .select('COALESCE(environment_type, name) AS folder',
+ .group('COALESCE(environment_type, id::text)', 'COALESCE(environment_type, name)')
+ .select('COALESCE(environment_type, id::text), COALESCE(environment_type, name) AS folder',
'COUNT(*) AS size', 'MAX(id) AS last_id')
# It makes a difference when you call `paginate` method, because
diff --git a/config/feature_flags/development/work_items_create_from_markdown.yml b/config/feature_flags/development/work_items_create_from_markdown.yml
new file mode 100644
index 00000000000..9a954001f2b
--- /dev/null
+++ b/config/feature_flags/development/work_items_create_from_markdown.yml
@@ -0,0 +1,8 @@
+---
+name: work_items_create_from_markdown
+introduced_by_url:
+rollout_issue_url:
+milestone: '15.3'
+type: development
+group: group::project management
+default_enabled: false
diff --git a/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb b/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb
index 611619e496c..1109d814ee9 100644
--- a/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb
+++ b/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb
@@ -9,7 +9,7 @@ class GroupProtectedEnvironmentsAddIndexAndConstraint < ActiveRecord::Migration[
def up
add_concurrent_index :protected_environments, [:group_id, :name], unique: true,
- name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ name: INDEX_NAME, where: 'group_id IS NOT NULL'
add_concurrent_foreign_key :protected_environments, :namespaces, column: :group_id, on_delete: :cascade
add_check_constraint :protected_environments,
diff --git a/db/migrate/20210804150320_create_base_work_item_types.rb b/db/migrate/20210804150320_create_base_work_item_types.rb
index b7a44eaabe0..836264aae6e 100644
--- a/db/migrate/20210804150320_create_base_work_item_types.rb
+++ b/db/migrate/20210804150320_create_base_work_item_types.rb
@@ -8,9 +8,9 @@ class CreateBaseWorkItemTypes < ActiveRecord::Migration[6.1]
self.table_name = 'work_item_types'
enum base_type: {
- issue: 0,
- incident: 1,
- test_case: 2,
+ issue: 0,
+ incident: 1,
+ test_case: 2,
requirement: 3
}
diff --git a/db/migrate/20210831203408_upsert_base_work_item_types.rb b/db/migrate/20210831203408_upsert_base_work_item_types.rb
index 314412d8d3d..9a556bd6c2c 100644
--- a/db/migrate/20210831203408_upsert_base_work_item_types.rb
+++ b/db/migrate/20210831203408_upsert_base_work_item_types.rb
@@ -6,9 +6,9 @@ class UpsertBaseWorkItemTypes < ActiveRecord::Migration[6.1]
self.table_name = 'work_item_types'
enum base_type: {
- issue: 0,
- incident: 1,
- test_case: 2,
+ issue: 0,
+ incident: 1,
+ test_case: 2,
requirement: 3
}
end
diff --git a/db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb b/db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb
index 77b9e5297a7..e0c9d932b38 100644
--- a/db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb
+++ b/db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb
@@ -7,8 +7,9 @@ class AddIndexOnNameAndIdToPublicGroups < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
- add_concurrent_index :namespaces, [:name, :id], name: INDEX_NAME,
- where: "type = 'Group' AND visibility_level = #{PUBLIC_VISIBILITY_LEVEL}"
+ add_concurrent_index :namespaces, [:name, :id],
+ name: INDEX_NAME,
+ where: "type = 'Group' AND visibility_level = #{PUBLIC_VISIBILITY_LEVEL}"
end
def down
diff --git a/db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb b/db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb
index bb444f5b407..0d610f1dde1 100644
--- a/db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb
+++ b/db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb
@@ -11,7 +11,7 @@ class BackfillPushEventPayloadEventIdForBigintConversion < ActiveRecord::Migrati
return unless should_run?
backfill_conversion_of_integer_to_bigint :push_event_payloads, :event_id, primary_key: :event_id,
- batch_size: 15000, sub_batch_size: 100
+ batch_size: 15000, sub_batch_size: 100
end
def down
diff --git a/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb b/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb
index 715bc392c68..1f0bdfc5012 100644
--- a/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb
+++ b/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb
@@ -31,7 +31,7 @@ class FinalizeEventsBigintConversion < ActiveRecord::Migration[6.1]
add_concurrent_index TABLE_NAME, [:project_id, :id_convert_to_bigint], name: 'index_events_on_project_id_and_id_convert_to_bigint'
# This is to replace the existing "index_events_on_project_id_and_id_desc_on_merged_action" btree (project_id, id DESC) WHERE action = 7
add_concurrent_index TABLE_NAME, [:project_id, :id_convert_to_bigint], order: { id_convert_to_bigint: :desc },
- where: "action = 7", name: 'index_events_on_project_id_and_id_bigint_desc_on_merged_action'
+ where: "action = 7", name: 'index_events_on_project_id_and_id_bigint_desc_on_merged_action'
# Add a FK on `push_event_payloads(event_id)` to `id_convert_to_bigint`, the old FK (fk_36c74129da)
# will be removed when events_pkey constraint is droppped.
diff --git a/db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb b/db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb
index 161366590be..776f1ad545d 100644
--- a/db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb
+++ b/db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb
@@ -30,7 +30,7 @@ class FinalizeCiBuildsStageIdBigintConversion < ActiveRecord::Migration[6.1]
# Create a copy of the original column's FK on the new column
add_concurrent_foreign_key TABLE_NAME, :ci_stages, column: :stage_id_convert_to_bigint, on_delete: :cascade,
- reverse_lock_order: true
+ reverse_lock_order: true
with_lock_retries(raise_on_exhaustion: true) do
quoted_table_name = quote_table_name(TABLE_NAME)
diff --git a/db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb b/db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb
index dbefbeb26cb..33a54b298f0 100644
--- a/db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb
+++ b/db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb
@@ -37,10 +37,10 @@ class FinalizeCiStagesBigintConversion < ActiveRecord::Migration[6.1]
fk_stage_id = concurrent_foreign_key_name(:ci_builds, :stage_id)
fk_stage_id_tmp = "#{fk_stage_id}_tmp"
add_concurrent_foreign_key :ci_builds, :ci_stages, column: :stage_id,
- target_column: :id_convert_to_bigint,
- name: fk_stage_id_tmp,
- on_delete: :cascade,
- reverse_lock_order: true
+ target_column: :id_convert_to_bigint,
+ name: fk_stage_id_tmp,
+ on_delete: :cascade,
+ reverse_lock_order: true
# Now it's time to do things in a transaction
with_lock_retries(raise_on_exhaustion: true) do
diff --git a/db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb b/db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb
index c2444ccbc6c..3879c3c368a 100644
--- a/db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb
+++ b/db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb
@@ -40,7 +40,7 @@ class FinalizeCiBuildsMetadataBigintConversion < Gitlab::Database::Migration[1.0
# rubocop:enable Migration/PreventIndexCreation
add_concurrent_foreign_key TABLE_NAME, :ci_builds, column: :build_id_convert_to_bigint, on_delete: :cascade,
- reverse_lock_order: true
+ reverse_lock_order: true
with_lock_retries(raise_on_exhaustion: true) do
execute "LOCK TABLE ci_builds, #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE"
diff --git a/db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb b/db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb
index 6dab29e10d4..2a8c63eba71 100644
--- a/db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb
+++ b/db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb
@@ -40,7 +40,7 @@ class FinalizePushEventPayloadsBigintConversion3 < ActiveRecord::Migration[6.1]
# Add a foreign key on `event_id_convert_to_bigint` before we swap the columns and drop the old FK (fk_36c74129da)
add_concurrent_foreign_key TABLE_NAME, :events, column: :event_id_convert_to_bigint,
- on_delete: :cascade, reverse_lock_order: true
+ on_delete: :cascade, reverse_lock_order: true
with_lock_retries(raise_on_exhaustion: true) do
# We'll need ACCESS EXCLUSIVE lock on the related tables,
diff --git a/db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb b/db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb
index 8115465e311..81e73effe7b 100644
--- a/db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb
+++ b/db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb
@@ -5,19 +5,23 @@ class PrepareIndexesForCiJobArtifactBigintConversion < ActiveRecord::Migration[6
def up
prepare_async_index :ci_job_artifacts, :id_convert_to_bigint, unique: true,
- name: :index_ci_job_artifact_on_id_convert_to_bigint
+ name: :index_ci_job_artifact_on_id_convert_to_bigint
- prepare_async_index :ci_job_artifacts, [:project_id, :id_convert_to_bigint], where: 'file_type = 18',
- name: :index_ci_job_artifacts_for_terraform_reports_bigint
+ prepare_async_index :ci_job_artifacts,
+ [:project_id, :id_convert_to_bigint],
+ where: 'file_type = 18', name: :index_ci_job_artifacts_for_terraform_reports_bigint
- prepare_async_index :ci_job_artifacts, :id_convert_to_bigint, where: 'file_type = 18',
- name: :index_ci_job_artifacts_id_for_terraform_reports_bigint
+ prepare_async_index :ci_job_artifacts, :id_convert_to_bigint,
+ where: 'file_type = 18',
+ name: :index_ci_job_artifacts_id_for_terraform_reports_bigint
- prepare_async_index :ci_job_artifacts, [:expire_at, :job_id_convert_to_bigint],
- name: :index_ci_job_artifacts_on_expire_at_and_job_id_bigint
+ prepare_async_index :ci_job_artifacts,
+ [:expire_at, :job_id_convert_to_bigint],
+ name: :index_ci_job_artifacts_on_expire_at_and_job_id_bigint
- prepare_async_index :ci_job_artifacts, [:job_id_convert_to_bigint, :file_type], unique: true,
- name: :index_ci_job_artifacts_on_job_id_and_file_type_bigint
+ prepare_async_index :ci_job_artifacts,
+ [:job_id_convert_to_bigint, :file_type],
+ unique: true, name: :index_ci_job_artifacts_on_job_id_and_file_type_bigint
end
def down
diff --git a/db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb b/db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb
index 98f90bafce3..ab6691dea1f 100644
--- a/db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb
+++ b/db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb
@@ -5,7 +5,7 @@ class PrepareIndexesForTaggingBigintConversion < ActiveRecord::Migration[6.1]
def up
prepare_async_index :taggings, :id_convert_to_bigint, unique: true,
- name: :index_taggings_on_id_convert_to_bigint
+ name: :index_taggings_on_id_convert_to_bigint
prepare_async_index :taggings, [:taggable_id_convert_to_bigint, :taggable_type],
name: :i_taggings_on_taggable_id_convert_to_bigint_and_taggable_type
diff --git a/db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb b/db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb
index 82af595b2d3..16f3e87000c 100644
--- a/db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb
+++ b/db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb
@@ -5,14 +5,16 @@ class PrepareIndexesForCiStageBigintConversion < ActiveRecord::Migration[6.1]
def up
prepare_async_index :ci_stages, :id_convert_to_bigint, unique: true,
- name: :index_ci_stages_on_id_convert_to_bigint
+ name: :index_ci_stages_on_id_convert_to_bigint
- prepare_async_index :ci_stages, [:pipeline_id, :id_convert_to_bigint], where: 'status in (0, 1, 2, 8, 9, 10)',
- name: :index_ci_stages_on_pipeline_id_and_id_convert_to_bigint
+ prepare_async_index :ci_stages, [:pipeline_id, :id_convert_to_bigint],
+ where: 'status in (0, 1, 2, 8, 9, 10)',
+ name: :index_ci_stages_on_pipeline_id_and_id_convert_to_bigint
end
def down
- unprepare_async_index_by_name :ci_stages, :index_ci_stages_on_pipeline_id_and_id_convert_to_bigint
+ unprepare_async_index_by_name :ci_stages,
+ :index_ci_stages_on_pipeline_id_and_id_convert_to_bigint
unprepare_async_index_by_name :ci_stages, :index_ci_stages_on_id_convert_to_bigint
end
diff --git a/db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb b/db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb
index 1d102d6216c..e1ed28f6826 100644
--- a/db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb
+++ b/db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb
@@ -7,13 +7,14 @@ class PrepareIndexesForEventsBigintConversion < ActiveRecord::Migration[6.1]
def up
prepare_async_index TABLE_NAME, :id_convert_to_bigint, unique: true,
- name: :index_events_on_id_convert_to_bigint
+ name: :index_events_on_id_convert_to_bigint
prepare_async_index TABLE_NAME, [:project_id, :id_convert_to_bigint],
name: :index_events_on_project_id_and_id_convert_to_bigint
- prepare_async_index TABLE_NAME, [:project_id, :id_convert_to_bigint], order: { id_convert_to_bigint: :desc },
- where: 'action = 7', name: :index_events_on_project_id_and_id_bigint_desc_on_merged_action
+ prepare_async_index TABLE_NAME, [:project_id, :id_convert_to_bigint],
+ order: { id_convert_to_bigint: :desc },
+ where: 'action = 7', name: :index_events_on_project_id_and_id_bigint_desc_on_merged_action
end
def down
diff --git a/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb b/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb
index f63645b4ffa..bf95aa98295 100644
--- a/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb
+++ b/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb
@@ -5,13 +5,13 @@ class PrepareCiBuildsMetadataAndCiBuildAsyncIndexes < ActiveRecord::Migration[6.
def up
prepare_async_index :ci_builds_metadata, :id_convert_to_bigint, unique: true,
- name: :index_ci_builds_metadata_on_id_convert_to_bigint
+ name: :index_ci_builds_metadata_on_id_convert_to_bigint
prepare_async_index :ci_builds_metadata, :build_id_convert_to_bigint, unique: true,
- name: :index_ci_builds_metadata_on_build_id_convert_to_bigint
+ name: :index_ci_builds_metadata_on_build_id_convert_to_bigint
prepare_async_index :ci_builds_metadata, :build_id_convert_to_bigint, where: 'has_exposed_artifacts IS TRUE',
- name: :index_ci_builds_metadata_on_build_id_int8_and_exposed_artifacts
+ name: :index_ci_builds_metadata_on_build_id_int8_and_exposed_artifacts
prepare_async_index_from_sql(:ci_builds_metadata, :index_ci_builds_metadata_on_build_id_int8_where_interruptible, <<~SQL.squish)
CREATE INDEX CONCURRENTLY "index_ci_builds_metadata_on_build_id_int8_where_interruptible"
@@ -20,7 +20,7 @@ class PrepareCiBuildsMetadataAndCiBuildAsyncIndexes < ActiveRecord::Migration[6.
SQL
prepare_async_index :ci_builds, :id_convert_to_bigint, unique: true,
- name: :index_ci_builds_on_converted_id
+ name: :index_ci_builds_on_converted_id
end
def down
diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md
index b8ca0e5b899..c1a4c1eb93e 100644
--- a/doc/operations/incident_management/incidents.md
+++ b/doc/operations/incident_management/incidents.md
@@ -205,10 +205,10 @@ field populated.
### Timeline events
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344059) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `incident_timeline`. Enabled on GitLab.com. Disabled on self-managed.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344059) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `incident_timeline`. Enabled by default.
FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `incident_timeline`.
+On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `incident_timeline`.
On GitLab.com, this feature is available.
Incident timelines are an important part of record keeping for incidents.
diff --git a/lib/gitlab/import_export/decompressed_archive_size_validator.rb b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
index a185eb4df1c..c98dcf7b848 100644
--- a/lib/gitlab/import_export/decompressed_archive_size_validator.rb
+++ b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
@@ -28,25 +28,26 @@ module Gitlab
private
def validate
- pgrp = nil
+ pgrps = nil
valid_archive = true
validate_archive_path
Timeout.timeout(TIMEOUT_LIMIT) do
- stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
- stdin.close
+ stderr_r, stderr_w = IO.pipe
+ stdout, wait_threads = Open3.pipeline_r(*command, pgroup: true, err: stderr_w )
# When validation is performed on a small archive (e.g. 100 bytes)
# `wait_thr` finishes before we can get process group id. Do not
# raise exception in this scenario.
- pgrp = begin
+ pgrps = wait_threads.map do |wait_thr|
Process.getpgid(wait_thr[:pid])
rescue Errno::ESRCH
nil
end
+ pgrps.compact!
- status = wait_thr.value
+ status = wait_threads.last.value
if status.success?
result = stdout.readline
@@ -64,20 +65,21 @@ module Gitlab
ensure
stdout.close
- stderr.close
+ stderr_w.close
+ stderr_r.close
end
valid_archive
rescue Timeout::Error
log_error('Timeout reached during archive decompression')
- Process.kill(-1, pgrp) if pgrp
+ pgrps.each { |pgrp| Process.kill(-1, pgrp) } if pgrps
false
rescue StandardError => e
log_error(e.message)
- Process.kill(-1, pgrp) if pgrp
+ pgrps.each { |pgrp| Process.kill(-1, pgrp) } if pgrps
false
end
@@ -91,7 +93,7 @@ module Gitlab
end
def command
- "gzip -dc #{@archive_path} | wc -c"
+ [['gzip', '-dc', @archive_path], ['wc', '-c']]
end
def log_error(error)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b7ae62b370..8f0e86b144b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4101,6 +4101,9 @@ msgstr ""
msgid "An error occurred while fetching reference"
msgstr ""
+msgid "An error occurred while fetching reviewers."
+msgstr ""
+
msgid "An error occurred while fetching tags. Retry the search."
msgstr ""
@@ -7972,9 +7975,6 @@ msgstr ""
msgid "Child issues and epics"
msgstr ""
-msgid "Children"
-msgstr ""
-
msgid "Chinese language support using"
msgstr ""
@@ -27438,6 +27438,9 @@ msgstr ""
msgid "OperationsDashboard|Operations Dashboard"
msgstr ""
+msgid "OperationsDashboard|The Operations and Environments dashboards share the same list of projects. When you add or remove a project from one, GitLab adds or removes the project from the other. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
@@ -44165,6 +44168,9 @@ msgstr ""
msgid "WorkItem|Add a task"
msgstr ""
+msgid "WorkItem|Add a title"
+msgstr ""
+
msgid "WorkItem|Add assignee"
msgstr ""
@@ -44251,6 +44257,9 @@ msgstr ""
msgid "WorkItem|Something went wrong when trying to add a child. Please try again."
msgstr ""
+msgid "WorkItem|Something went wrong when trying to create a child. Please try again."
+msgstr ""
+
msgid "WorkItem|Something went wrong while updating the work item. Please try again."
msgstr ""
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index bf08f887c7d..981b60d1920 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -6,6 +6,16 @@ module QA
extend self
def perform_before_hooks
+ if QA::Runtime::Env.admin_personal_access_token.present?
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username,
+ QA::Runtime::Env.admin_personal_access_token)
+ end
+
+ if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present?
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username,
+ QA::Runtime::Env.personal_access_token)
+ end
+
# The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests.
QA::Runtime::Logger.info("Performing sanity check for environment!")
diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb
index a12c210ea0e..ad0f183c603 100644
--- a/qa/qa/resource/personal_access_token.rb
+++ b/qa/qa/resource/personal_access_token.rb
@@ -17,20 +17,17 @@ module QA
# If Runtime::Env.admin_personal_access_token is provided, fabricate via the API,
# else, fabricate via the browser.
def fabricate_via_api!
- QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username).tap do |cached_token|
- @token = cached_token if cached_token
- end
- return if @token
+ return if find_and_set_value
resource = if Runtime::Env.admin_personal_access_token && !@user.nil?
self.api_client = Runtime::API::Client.as_admin
-
super
else
fabricate!
end
- QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, token) if @user
+ self.token = api_response[:token] unless api_response.nil?
+ cache_token
resource
end
@@ -60,7 +57,17 @@ module QA
# this particular resource does not expose a web_url property
end
+ def find_and_set_value
+ @token ||= QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username)
+ end
+
+ def cache_token
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, self.token) if @user && self.token
+ end
+
def fabricate!
+ return if find_and_set_value
+
Flow::Login.sign_in_unless_signed_in(user: user)
Page::Main::Menu.perform(&:click_edit_profile_link)
@@ -74,6 +81,10 @@ module QA
token_page.click_create_token_button
self.token = Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
+
+ cache_token
+
+ self.token
end
end
end
diff --git a/qa/qa/resource/personal_access_token_cache.rb b/qa/qa/resource/personal_access_token_cache.rb
index 3e9dc3fd7df..0874618201a 100644
--- a/qa/qa/resource/personal_access_token_cache.rb
+++ b/qa/qa/resource/personal_access_token_cache.rb
@@ -6,7 +6,17 @@ module QA
@personal_access_tokens = {}
def self.get_token_for_username(username)
- @personal_access_tokens[username]
+ token = @personal_access_tokens[username]
+
+ log_message = if token
+ %Q[Retrieved cached token for username: #{username}, last six chars of token:#{token[-6..]}]
+ else
+ %Q[No cached token found for username: #{username}]
+ end
+
+ QA::Runtime::Logger.info(log_message)
+
+ token
end
def self.set_token_for_username(username, token)
diff --git a/scripts/static-analysis b/scripts/static-analysis
index 14d6a3deccb..144d40d36c2 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -11,7 +11,9 @@ class StaticAnalysis
# https://github.com/browserslist/browserslist/blob/d0ec62eb48c41c218478cd3ac28684df051cc865/node.js#L329
# warns if caniuse-lite package is older than 6 months. Ignore this
# warning message so that GitLab backports don't fail.
- "Browserslist: caniuse-lite is outdated. Please run next command `yarn upgrade`"
+ "Browserslist: caniuse-lite is outdated. Please run next command `yarn upgrade`",
+ # https://github.com/mime-types/mime-types-data/pull/50#issuecomment-1060908930
+ "Type application/netcdf is already registered as a variant of application/netcdf"
].freeze
Task = Struct.new(:command, :duration) do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 97ddbf21b99..0751f4aa670 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -504,6 +504,20 @@ FactoryBot.define do
artifacts_expire_at { 1.minute.ago }
end
+ trait :with_artifacts_paths do
+ options do
+ {
+ artifacts: {
+ name: 'artifacts_file',
+ untracked: false,
+ paths: ['out/'],
+ when: 'always',
+ expire_in: '7d'
+ }
+ }
+ end
+ end
+
trait :with_commit do
after(:build) do |build|
commit = build(:commit, :without_author)
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index 8ee57f97754..823121c24d8 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -266,7 +266,7 @@ describe('Description component', () => {
});
});
- describe('with work items feature flag is enabled', () => {
+ describe('with work_items_create_from_markdown feature flag enabled', () => {
describe('empty description', () => {
beforeEach(() => {
createComponent({
@@ -275,7 +275,7 @@ describe('Description component', () => {
},
provide: {
glFeatures: {
- workItems: true,
+ workItemsCreateFromMarkdown: true,
},
},
});
@@ -295,7 +295,7 @@ describe('Description component', () => {
},
provide: {
glFeatures: {
- workItems: true,
+ workItemsCreateFromMarkdown: true,
},
},
});
@@ -344,7 +344,7 @@ describe('Description component', () => {
descriptionHtml: descriptionHtmlWithTask,
},
provide: {
- glFeatures: { workItems: true },
+ glFeatures: { workItemsCreateFromMarkdown: true },
},
});
return nextTick();
@@ -406,7 +406,7 @@ describe('Description component', () => {
createComponent({
props: { descriptionHtml: descriptionHtmlWithTask },
- provide: { glFeatures: { workItems: true } },
+ provide: { glFeatures: { workItemsCreateFromMarkdown: true } },
});
expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
@@ -422,7 +422,7 @@ describe('Description component', () => {
descriptionHtml: descriptionHtmlWithTask,
},
provide: {
- glFeatures: { workItems: true },
+ glFeatures: { workItemsCreateFromMarkdown: true },
},
});
return nextTick();
diff --git a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
index 8ac85d4da81..2c24df2436a 100644
--- a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
@@ -2,7 +2,21 @@ import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import ReviewerAvatarLink from '~/sidebar/components/reviewers/reviewer_avatar_link.vue';
import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
-import userDataMock from '../../user_data_mock';
+
+const userDataMock = () => ({
+ id: 1,
+ name: 'Root',
+ state: 'active',
+ username: 'root',
+ webUrl: `${TEST_HOST}/root`,
+ avatarUrl: `${TEST_HOST}/avatar/root.png`,
+ mergeRequestInteraction: {
+ canMerge: true,
+ canUpdate: true,
+ reviewed: true,
+ approved: false,
+ },
+});
describe('UncollapsedReviewerList component', () => {
let wrapper;
@@ -69,7 +83,10 @@ describe('UncollapsedReviewerList component', () => {
id: 2,
name: 'nonrooty-nonrootersen',
username: 'hello-world',
- approved: true,
+ mergeRequestInteraction: {
+ ...user.mergeRequestInteraction,
+ approved: true,
+ },
};
beforeEach(() => {
diff --git a/spec/frontend/sidebar/reviewers_spec.js b/spec/frontend/sidebar/reviewers_spec.js
index 351dfc9a6ed..88bacc9b7f7 100644
--- a/spec/frontend/sidebar/reviewers_spec.js
+++ b/spec/frontend/sidebar/reviewers_spec.js
@@ -1,9 +1,23 @@
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
-import UsersMockHelper from 'helpers/user_mock_data_helper';
+import { TEST_HOST } from 'helpers/test_constants';
import Reviewer from '~/sidebar/components/reviewers/reviewers.vue';
-import UsersMock from './mock_data';
+
+const usersMock = (id = 1) => ({
+ id,
+ name: 'Root',
+ state: 'active',
+ username: 'root',
+ webUrl: `${TEST_HOST}/root`,
+ avatarUrl: `${TEST_HOST}/avatar/root.png`,
+ mergeRequestInteraction: {
+ canMerge: true,
+ canUpdate: true,
+ reviewed: true,
+ approved: false,
+ },
+});
describe('Reviewer component', () => {
const getDefaultProps = () => ({
@@ -42,23 +56,23 @@ describe('Reviewer component', () => {
it('displays one reviewer icon when collapsed', () => {
createWrapper({
...getDefaultProps(),
- users: [UsersMock.user],
+ users: [usersMock()],
});
const collapsedChildren = findCollapsedChildren();
const reviewer = collapsedChildren.at(0);
expect(collapsedChildren.length).toBe(1);
- expect(reviewer.find('.avatar').attributes('src')).toBe(UsersMock.user.avatar);
- expect(reviewer.find('.avatar').attributes('alt')).toBe(`${UsersMock.user.name}'s avatar`);
+ expect(reviewer.find('.avatar').attributes('src')).toContain('avatar/root.png');
+ expect(reviewer.find('.avatar').attributes('alt')).toBe(`Root's avatar`);
- expect(trimText(reviewer.find('.author').text())).toBe(UsersMock.user.name);
+ expect(trimText(reviewer.find('.author').text())).toBe('Root');
});
});
describe('Two or more reviewers/users', () => {
it('displays two reviewer icons when collapsed', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
+ const users = [usersMock(), usersMock(2)];
createWrapper({
...getDefaultProps(),
users,
@@ -70,21 +84,21 @@ describe('Reviewer component', () => {
const first = collapsedChildren.at(0);
- expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar_url);
+ expect(first.find('.avatar').attributes('src')).toBe(users[0].avatarUrl);
expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(trimText(first.find('.author').text())).toBe(users[0].name);
const second = collapsedChildren.at(1);
- expect(second.find('.avatar').attributes('src')).toBe(users[1].avatar_url);
+ expect(second.find('.avatar').attributes('src')).toBe(users[1].avatarUrl);
expect(second.find('.avatar').attributes('alt')).toBe(`${users[1].name}'s avatar`);
expect(trimText(second.find('.author').text())).toBe(users[1].name);
});
it('displays one reviewer icon and counter when collapsed', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
+ const users = [usersMock(), usersMock(2), usersMock(3)];
createWrapper({
...getDefaultProps(),
users,
@@ -96,7 +110,7 @@ describe('Reviewer component', () => {
const first = collapsedChildren.at(0);
- expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar_url);
+ expect(first.find('.avatar').attributes('src')).toBe(users[0].avatarUrl);
expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(trimText(first.find('.author').text())).toBe(users[0].name);
@@ -107,7 +121,7 @@ describe('Reviewer component', () => {
});
it('Shows two reviewers', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
+ const users = [usersMock(), usersMock(2)];
createWrapper({
...getDefaultProps(),
users,
@@ -118,10 +132,10 @@ describe('Reviewer component', () => {
});
it('shows sorted reviewer where "can merge" users are sorted first', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = false;
- users[1].can_merge = false;
- users[2].can_merge = true;
+ const users = [usersMock(), usersMock(2), usersMock(3)];
+ users[0].mergeRequestInteraction.canMerge = false;
+ users[1].mergeRequestInteraction.canMerge = false;
+ users[2].mergeRequestInteraction.canMerge = true;
createWrapper({
...getDefaultProps(),
@@ -129,14 +143,14 @@ describe('Reviewer component', () => {
editable: true,
});
- expect(wrapper.vm.sortedReviewers[0].can_merge).toBe(true);
+ expect(wrapper.vm.sortedReviewers[0].mergeRequestInteraction.canMerge).toBe(true);
});
it('passes the sorted reviewers to the uncollapsed-reviewer-list', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = false;
- users[1].can_merge = false;
- users[2].can_merge = true;
+ const users = [usersMock(), usersMock(2), usersMock(3)];
+ users[0].mergeRequestInteraction.canMerge = false;
+ users[1].mergeRequestInteraction.canMerge = false;
+ users[2].mergeRequestInteraction.canMerge = true;
createWrapper({
...getDefaultProps(),
@@ -149,10 +163,10 @@ describe('Reviewer component', () => {
});
it('passes the sorted reviewers to the collapsed-reviewer-list', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = false;
- users[1].can_merge = false;
- users[2].can_merge = true;
+ const users = [usersMock(), usersMock(2), usersMock(3)];
+ users[0].mergeRequestInteraction.canMerge = false;
+ users[1].mergeRequestInteraction.canMerge = false;
+ users[2].mergeRequestInteraction.canMerge = true;
createWrapper({
...getDefaultProps(),
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
index 93bf7286aa7..56c1d8054fd 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -1,13 +1,18 @@
import Vue from 'vue';
-import { GlForm, GlFormCombobox } from '@gitlab/ui';
+import { GlForm, GlFormInput, GlFormCombobox } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
+import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
-import { availableWorkItemsResponse, updateWorkItemMutationResponse } from '../../mock_data';
+import {
+ availableWorkItemsResponse,
+ createWorkItemMutationResponse,
+ updateWorkItemMutationResponse,
+} from '../../mock_data';
Vue.use(VueApollo);
@@ -15,12 +20,14 @@ describe('WorkItemLinksForm', () => {
let wrapper;
const updateMutationResolver = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+ const createMutationResolver = jest.fn().mockResolvedValue(createWorkItemMutationResponse);
const createComponent = async ({ listResponse = availableWorkItemsResponse } = {}) => {
wrapper = shallowMountExtended(WorkItemLinksForm, {
apolloProvider: createMockApollo([
[projectWorkItemsQuery, jest.fn().mockResolvedValue(listResponse)],
[updateWorkItemMutation, updateMutationResolver],
+ [createWorkItemMutation, createMutationResolver],
]),
propsData: { issuableGid: 'gid://gitlab/WorkItem/1' },
provide: {
@@ -33,6 +40,7 @@ describe('WorkItemLinksForm', () => {
const findForm = () => wrapper.findComponent(GlForm);
const findCombobox = () => wrapper.findComponent(GlFormCombobox);
+ const findInput = () => wrapper.findComponent(GlFormInput);
const findAddChildButton = () => wrapper.findByTestId('add-child-button');
beforeEach(async () => {
@@ -47,19 +55,41 @@ describe('WorkItemLinksForm', () => {
expect(findForm().exists()).toBe(true);
});
- it('passes available work items as prop when typing in combobox', async () => {
- findCombobox().vm.$emit('input', 'Task');
- await waitForPromises();
+ it('creates child task', async () => {
+ findInput().vm.$emit('input', 'Create task test');
- expect(findCombobox().exists()).toBe(true);
- expect(findCombobox().props('tokenList').length).toBe(2);
+ findForm().vm.$emit('submit', {
+ preventDefault: jest.fn(),
+ });
+ await waitForPromises();
+ expect(createMutationResolver).toHaveBeenCalled();
});
- it('selects and add child', async () => {
+ // Follow up issue to turn this functionality back on https://gitlab.com/gitlab-org/gitlab/-/issues/368757
+ // eslint-disable-next-line jest/no-disabled-tests
+ it.skip('selects and add child', async () => {
findCombobox().vm.$emit('input', availableWorkItemsResponse.data.workspace.workItems.edges[0]);
findAddChildButton().vm.$emit('click');
await waitForPromises();
expect(updateMutationResolver).toHaveBeenCalled();
});
+
+ // eslint-disable-next-line jest/no-disabled-tests
+ describe.skip('when typing in combobox', () => {
+ beforeEach(async () => {
+ findCombobox().vm.$emit('input', 'Task');
+ await waitForPromises();
+ await jest.runOnlyPendingTimers();
+ });
+
+ it('passes available work items as prop', () => {
+ expect(findCombobox().exists()).toBe(true);
+ expect(findCombobox().props('tokenList').length).toBe(2);
+ });
+
+ it('passes action to create task', () => {
+ expect(findCombobox().props('actionList').length).toBe(1);
+ });
+ });
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 7232d588add..05ca66d2523 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -219,6 +219,7 @@ export const createWorkItemMutationResponse = {
},
widgets: [],
},
+ errors: [],
},
},
};
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 97cd4777b4d..feaa1f6595c 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -2,8 +2,9 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Branch, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
+RSpec.describe Gitlab::Git::Branch do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
subject { repository.branches }
@@ -54,14 +55,14 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
describe '#size' do
subject { super().size }
- it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
+ it { is_expected.to eq(TestEnv::BRANCH_SHA.size) }
end
describe 'first branch' do
let(:branch) { repository.branches.first }
- it { expect(branch.name).to eq(SeedRepo::Repo::BRANCHES.first) }
- it { expect(branch.dereferenced_target.sha).to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") }
+ it { expect(branch.name).to eq(TestEnv::BRANCH_SHA.keys.min) }
+ it { expect(branch.dereferenced_target.sha).to start_with(TestEnv::BRANCH_SHA[TestEnv::BRANCH_SHA.keys.min]) }
end
describe 'master branch' do
@@ -69,14 +70,10 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
repository.branches.find { |branch| branch.name == 'master' }
end
- it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) }
+ it { expect(branch.dereferenced_target.sha).to start_with(TestEnv::BRANCH_SHA['master']) }
end
context 'with active, stale and future branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project')
- end
-
let(:user) { create(:user) }
let(:stale_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
let(:active_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
@@ -88,10 +85,6 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
repository.create_branch('future-1', future_sha)
end
- after do
- ensure_seeds
- end
-
describe 'examine if the branch is active or stale' do
let(:stale_branch) { repository.find_branch('stale-1') }
let(:active_branch) { repository.find_branch('active-1') }
@@ -117,8 +110,6 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
end
end
- it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
-
def create_commit
repository.multi_action(
user,
diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
index dea584e5019..9af72cc0dea 100644
--- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
+++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
@@ -51,10 +51,11 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
shared_examples 'logs raised exception and terminates validator process group' do
let(:std) { double(:std, close: nil, value: nil) }
let(:wait_thr) { double }
+ let(:wait_threads) { [wait_thr, wait_thr] }
before do
allow(Process).to receive(:getpgid).and_return(2)
- allow(Open3).to receive(:popen3).and_return([std, std, std, wait_thr])
+ allow(Open3).to receive(:pipeline_r).and_return([std, wait_threads])
allow(wait_thr).to receive(:[]).with(:pid).and_return(1)
allow(wait_thr).to receive(:value).and_raise(exception)
end
@@ -67,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
import_upload_archive_size: File.size(filepath),
message: error_message
)
- expect(Process).to receive(:kill).with(-1, 2)
+ expect(Process).to receive(:kill).with(-1, 2).twice
expect(subject.valid?).to eq(false)
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4531ca713f0..83ec4a5585d 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -3390,6 +3390,13 @@ RSpec.describe Group do
end
end
+ describe '#work_items_create_from_markdown_feature_flag_enabled?' do
+ it_behaves_like 'checks self and root ancestor feature flag' do
+ let(:feature_flag) { :work_items_create_from_markdown }
+ let(:feature_flag_method) { :work_items_create_from_markdown_feature_flag_enabled? }
+ end
+ end
+
describe 'group shares' do
let!(:sub_group) { create(:group, parent: group) }
let!(:sub_sub_group) { create(:group, parent: sub_group) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index fb6fc01ed88..7870326c29a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -8281,6 +8281,16 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#work_items_create_from_markdown_feature_flag_enabled?' do
+ let_it_be(:group_project) { create(:project, :in_subgroup) }
+
+ it_behaves_like 'checks parent group feature flag' do
+ let(:feature_flag_method) { :work_items_create_from_markdown_feature_flag_enabled? }
+ let(:feature_flag) { :work_items_create_from_markdown }
+ let(:subject_project) { group_project }
+ end
+ end
+
describe 'serialization' do
let(:object) { build(:project) }
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 05644dad151..01d1e47b5bb 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -101,6 +101,37 @@ RSpec.describe EnvironmentSerializer do
expect(subject.third[:latest][:environment_type]).to be_nil
end
end
+
+ context 'when folders and standalone environments share the same name' do
+ before do
+ create(:environment, project: project, name: 'staging/my-review-1')
+ create(:environment, project: project, name: 'staging/my-review-2')
+ create(:environment, project: project, name: 'production/my-review-3')
+ create(:environment, project: project, name: 'staging')
+ create(:environment, project: project, name: 'testing')
+ end
+
+ it 'does not group standalone environments with folders that have the same name' do
+ expect(subject.count).to eq 4
+
+ expect(subject.first[:name]).to eq 'production'
+ expect(subject.first[:size]).to eq 1
+ expect(subject.first[:latest][:name]).to eq 'production/my-review-3'
+ expect(subject.first[:latest][:environment_type]).to eq 'production'
+ expect(subject.second[:name]).to eq 'staging'
+ expect(subject.second[:size]).to eq 1
+ expect(subject.second[:latest][:name]).to eq 'staging'
+ expect(subject.second[:latest][:environment_type]).to be_nil
+ expect(subject.third[:name]).to eq 'staging'
+ expect(subject.third[:size]).to eq 2
+ expect(subject.third[:latest][:name]).to eq 'staging/my-review-2'
+ expect(subject.third[:latest][:environment_type]).to eq 'staging'
+ expect(subject.fourth[:name]).to eq 'testing'
+ expect(subject.fourth[:size]).to eq 1
+ expect(subject.fourth[:latest][:name]).to eq 'testing'
+ expect(subject.fourth[:latest][:environment_type]).to be_nil
+ end
+ end
end
context 'when used with pagination' do
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index 255c4e6f882..ca2fe8a6c54 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -66,7 +66,7 @@ Integration.available_integration_names.each do |integration|
hash.merge!(k => 'foo@bar.com')
elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior
hash.merge!(k => "match_any")
- elsif integration == 'campfire' && k = :room
+ elsif integration == 'campfire' && k == :room
hash.merge!(k => '1234')
else
hash.merge!(k => "someword")