Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3b060a68f3
commit
9da482ecb8
43 changed files with 1156 additions and 371 deletions
|
@ -2,29 +2,35 @@
|
|||
import '~/commons/bootstrap';
|
||||
import {
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlTooltip,
|
||||
GlTooltipDirective,
|
||||
GlButton,
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
} from '@gitlab/ui';
|
||||
import IssueDueDate from '~/boards/components/issue_due_date.vue';
|
||||
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { setUrlParams, updateHistory } from '~/lib/utils/url_utility';
|
||||
import { sprintf } from '~/locale';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||
import relatedIssuableMixin from '../mixins/related_issuable_mixin';
|
||||
import IssueAssignees from './issue_assignees.vue';
|
||||
import IssueMilestone from './issue_milestone.vue';
|
||||
|
||||
export default {
|
||||
name: 'IssueItem',
|
||||
components: {
|
||||
IssueMilestone,
|
||||
IssueAssignees,
|
||||
CiIcon,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlTooltip,
|
||||
IssueWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
|
||||
IssueDueDate,
|
||||
GlButton,
|
||||
WorkItemDetailModal,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -47,6 +53,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
workItemType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
stateTitle() {
|
||||
|
@ -62,6 +73,27 @@ export default {
|
|||
iconClasses() {
|
||||
return `${this.iconClass} ic-${this.iconName}`;
|
||||
},
|
||||
workItemId() {
|
||||
return convertToGraphQLId(TYPE_WORK_ITEM, this.idKey);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTitleClick(event) {
|
||||
if (this.workItemType === 'TASK') {
|
||||
event.preventDefault();
|
||||
this.$refs.modal.show();
|
||||
this.updateWorkItemIdUrlQuery(this.idKey);
|
||||
}
|
||||
},
|
||||
handleWorkItemDeleted(workItemId) {
|
||||
this.$emit('relatedIssueRemoveRequest', workItemId);
|
||||
},
|
||||
updateWorkItemIdUrlQuery(workItemId) {
|
||||
updateHistory({
|
||||
url: setUrlParams({ work_item_id: workItemId }),
|
||||
replace: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -102,7 +134,13 @@ export default {
|
|||
class="confidential-icon gl-mr-2 align-self-baseline align-self-md-auto mt-xl-0"
|
||||
:aria-label="__('Confidential')"
|
||||
/>
|
||||
<a :href="computedPath" class="sortable-link gl-font-weight-normal">{{ title }}</a>
|
||||
<gl-link
|
||||
:href="computedPath"
|
||||
class="sortable-link gl-font-weight-normal"
|
||||
@click="handleTitleClick"
|
||||
>
|
||||
{{ title }}
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<!-- Info area: meta, path, and assignees -->
|
||||
|
@ -178,16 +216,15 @@ export default {
|
|||
|
||||
<span
|
||||
v-if="isLocked"
|
||||
ref="lockIcon"
|
||||
v-gl-tooltip
|
||||
class="gl-px-3 gl-display-inline-block gl-cursor-not-allowed"
|
||||
:title="lockedMessage"
|
||||
data-testid="lockIcon"
|
||||
>
|
||||
<gl-icon name="lock" />
|
||||
</span>
|
||||
<gl-button
|
||||
v-else-if="canRemove"
|
||||
ref="removeButton"
|
||||
v-gl-tooltip
|
||||
icon="close"
|
||||
category="tertiary"
|
||||
|
@ -198,5 +235,11 @@ export default {
|
|||
:aria-label="__('Remove')"
|
||||
@click="onRemoveRequest"
|
||||
/>
|
||||
<work-item-detail-modal
|
||||
ref="modal"
|
||||
:work-item-id="workItemId"
|
||||
@close="updateWorkItemIdUrlQuery"
|
||||
@workItemDeleted="handleWorkItemDeleted"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IssueType } from '~/issues/constants';
|
|||
import Issue from '~/issues/issue';
|
||||
import { initTitleSuggestions, initTypePopover } from '~/issues/new';
|
||||
import { initRelatedMergeRequests } from '~/issues/related_merge_requests';
|
||||
import initRelatedIssues from '~/related_issues';
|
||||
import { initRelatedIssues } from '~/related_issues';
|
||||
import {
|
||||
initHeaderActions,
|
||||
initIncidentApp,
|
||||
|
|
|
@ -326,6 +326,7 @@ export default {
|
|||
}
|
||||
const workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
|
||||
this.addHoverListeners(taskLink, workItemId);
|
||||
taskLink.classList.add('gl-link');
|
||||
taskLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.openWorkItemDetailModal(taskLink);
|
||||
|
|
|
@ -5,12 +5,17 @@ import { first } from 'lodash';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { truncateSha } from '~/lib/utils/text_utility';
|
||||
import { s__, n__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
|
||||
import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/constants';
|
||||
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import {
|
||||
GRAPHQL_PACKAGE_PIPELINES_PAGE_SIZE,
|
||||
FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE,
|
||||
TRACKING_ACTION_CLICK_PIPELINE_LINK,
|
||||
TRACKING_ACTION_CLICK_COMMIT_LINK,
|
||||
TRACKING_LABEL_PACKAGE_HISTORY,
|
||||
} from '../../constants';
|
||||
import getPackagePipelinesQuery from '../../graphql/queries/get_package_pipelines.query.graphql';
|
||||
import PackageHistoryLoader from './package_history_loader.vue';
|
||||
|
@ -37,6 +42,9 @@ export default {
|
|||
PackageHistoryLoader,
|
||||
TimeAgoTooltip,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
TRACKING_ACTION_CLICK_PIPELINE_LINK,
|
||||
TRACKING_ACTION_CLICK_COMMIT_LINK,
|
||||
props: {
|
||||
packageEntity: {
|
||||
type: Object,
|
||||
|
@ -97,6 +105,11 @@ export default {
|
|||
first: GRAPHQL_PACKAGE_PIPELINES_PAGE_SIZE,
|
||||
};
|
||||
},
|
||||
tracking() {
|
||||
return {
|
||||
category: packageTypeToTrackCategory(this.packageType),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
truncate(value) {
|
||||
|
@ -105,6 +118,12 @@ export default {
|
|||
convertToBaseId(value) {
|
||||
return getIdFromGraphQLId(value);
|
||||
},
|
||||
trackPipelineClick() {
|
||||
this.track(TRACKING_ACTION_CLICK_PIPELINE_LINK, { label: TRACKING_LABEL_PACKAGE_HISTORY });
|
||||
},
|
||||
trackCommitClick() {
|
||||
this.track(TRACKING_ACTION_CLICK_COMMIT_LINK, { label: TRACKING_LABEL_PACKAGE_HISTORY });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -140,7 +159,9 @@ export default {
|
|||
<history-item icon="commit" data-testid="first-pipeline-commit">
|
||||
<gl-sprintf :message="$options.i18n.createdByCommitText">
|
||||
<template #link>
|
||||
<gl-link :href="firstPipeline.commitPath">#{{ truncate(firstPipeline.sha) }}</gl-link>
|
||||
<gl-link :href="firstPipeline.commitPath" @click="trackCommitClick"
|
||||
>#{{ truncate(firstPipeline.sha) }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #branch>
|
||||
<strong>{{ firstPipeline.ref }}</strong>
|
||||
|
@ -150,7 +171,9 @@ export default {
|
|||
<history-item icon="pipeline" data-testid="first-pipeline-pipeline">
|
||||
<gl-sprintf :message="$options.i18n.createdByPipelineText">
|
||||
<template #link>
|
||||
<gl-link :href="firstPipeline.path">#{{ convertToBaseId(firstPipeline.id) }}</gl-link>
|
||||
<gl-link :href="firstPipeline.path" @click="trackPipelineClick"
|
||||
>#{{ convertToBaseId(firstPipeline.id) }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="firstPipeline.createdAt" />
|
||||
|
@ -189,13 +212,17 @@ export default {
|
|||
>
|
||||
<gl-sprintf :message="$options.i18n.combinedUpdateText">
|
||||
<template #link>
|
||||
<gl-link :href="pipeline.commitPath">#{{ truncate(pipeline.sha) }}</gl-link>
|
||||
<gl-link :href="pipeline.commitPath" @click="trackCommitClick"
|
||||
>#{{ truncate(pipeline.sha) }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #branch>
|
||||
<strong>{{ pipeline.ref }}</strong>
|
||||
</template>
|
||||
<template #pipeline>
|
||||
<gl-link :href="pipeline.path">#{{ convertToBaseId(pipeline.id) }}</gl-link>
|
||||
<gl-link :href="pipeline.path" @click="trackPipelineClick"
|
||||
>#{{ convertToBaseId(pipeline.id) }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="pipeline.createdAt" />
|
||||
|
|
|
@ -69,6 +69,11 @@ export const TRACKING_ACTION_DOWNLOAD_PACKAGE_ASSET = 'download_package_asset';
|
|||
export const TRACKING_ACTION_EXPAND_PACKAGE_ASSET = 'expand_package_asset';
|
||||
export const TRACKING_ACTION_COPY_PACKAGE_ASSET_SHA = 'copy_package_asset_sha';
|
||||
|
||||
export const TRACKING_ACTION_CLICK_PIPELINE_LINK = 'click_pipeline_link_from_package';
|
||||
export const TRACKING_ACTION_CLICK_COMMIT_LINK = 'click_commit_link_from_package';
|
||||
|
||||
export const TRACKING_LABEL_PACKAGE_HISTORY = 'package_history';
|
||||
|
||||
export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
|
||||
export const DELETE_PACKAGE_FILE_ERROR_MESSAGE = s__(
|
||||
'PackageRegistry|Something went wrong while deleting the package file.',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { initShow } from '~/issues';
|
||||
import { store } from '~/notes/stores';
|
||||
import initRelatedIssues from '~/related_issues';
|
||||
import { initRelatedIssues } from '~/related_issues';
|
||||
import initSidebarBundle from '~/sidebar/sidebar_bundle';
|
||||
import initWorkItemLinks from '~/work_items/components/work_item_links';
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue
|
|||
import { defaultSortableOptions } from '~/sortable/constants';
|
||||
|
||||
export default {
|
||||
name: 'RelatedIssuesList',
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
RelatedIssuableItem,
|
||||
|
@ -141,6 +140,7 @@ export default {
|
|||
:path-id-separator="pathIdSeparator"
|
||||
:is-locked="issue.lockIssueRemoval"
|
||||
:locked-message="issue.lockedMessage"
|
||||
:work-item-type="issue.type"
|
||||
event-namespace="relatedIssue"
|
||||
data-qa-selector="related_issuable_content"
|
||||
@relatedIssueRemoveRequest="$emit('relatedIssueRemoveRequest', $event)"
|
||||
|
|
|
@ -24,6 +24,7 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways:
|
|||
|
||||
*/
|
||||
import createFlash from '~/flash';
|
||||
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
|
||||
import { __ } from '~/locale';
|
||||
import {
|
||||
relatedIssuesRemoveErrorMap,
|
||||
|
@ -123,6 +124,14 @@ export default {
|
|||
return this.state.relatedIssues.find((issue) => issue.id === id);
|
||||
},
|
||||
onRelatedIssueRemoveRequest(idToRemove) {
|
||||
if (isGid(idToRemove)) {
|
||||
const deletedId = getIdFromGraphQLId(idToRemove);
|
||||
this.state.relatedIssues = this.state.relatedIssues.filter(
|
||||
(issue) => issue.id !== deletedId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const issueToRemove = this.findRelatedIssueById(idToRemove);
|
||||
|
||||
if (issueToRemove) {
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
import Vue from 'vue';
|
||||
import apolloProvider from '~/issues/show/graphql';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import RelatedIssuesRoot from './components/related_issues_root.vue';
|
||||
|
||||
export default function initRelatedIssues(issueType = 'issue') {
|
||||
const relatedIssuesRootElement = document.querySelector('.js-related-issues-root');
|
||||
if (relatedIssuesRootElement) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: relatedIssuesRootElement,
|
||||
name: 'RelatedIssuesRoot',
|
||||
components: {
|
||||
relatedIssuesRoot: RelatedIssuesRoot,
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement('related-issues-root', {
|
||||
props: {
|
||||
endpoint: relatedIssuesRootElement.dataset.endpoint,
|
||||
canAdmin: parseBoolean(relatedIssuesRootElement.dataset.canAddRelatedIssues),
|
||||
helpPath: relatedIssuesRootElement.dataset.helpPath,
|
||||
showCategorizedIssues: parseBoolean(
|
||||
relatedIssuesRootElement.dataset.showCategorizedIssues,
|
||||
),
|
||||
issuableType: issueType,
|
||||
autoCompleteEpics: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
export function initRelatedIssues(issueType = 'issue') {
|
||||
const el = document.querySelector('.js-related-issues-root');
|
||||
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'RelatedIssuesRoot',
|
||||
apolloProvider,
|
||||
provide: {
|
||||
fullPath: el.dataset.fullPath,
|
||||
hasIssueWeightsFeature: parseBoolean(el.dataset.hasIssueWeightsFeature),
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(RelatedIssuesRoot, {
|
||||
props: {
|
||||
endpoint: el.dataset.endpoint,
|
||||
canAdmin: parseBoolean(el.dataset.canAddRelatedIssues),
|
||||
helpPath: el.dataset.helpPath,
|
||||
showCategorizedIssues: parseBoolean(el.dataset.showCategorizedIssues),
|
||||
issuableType: issueType,
|
||||
autoCompleteEpics: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,9 +2,13 @@
|
|||
import { GlAlert, GlModal } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import deleteWorkItemFromTaskMutation from '../graphql/delete_task_from_work_item.mutation.graphql';
|
||||
import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql';
|
||||
import WorkItemDetail from './work_item_detail.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
errorMessage: s__('WorkItem|Something went wrong when deleting the task. Please try again.'),
|
||||
},
|
||||
components: {
|
||||
GlAlert,
|
||||
GlModal,
|
||||
|
@ -45,6 +49,13 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
deleteWorkItem() {
|
||||
if (this.lockVersion != null && this.lineNumberStart && this.lineNumberEnd) {
|
||||
this.deleteWorkItemWithTaskData();
|
||||
} else {
|
||||
this.deleteWorkItemWithoutTaskData();
|
||||
}
|
||||
},
|
||||
deleteWorkItemWithTaskData() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: deleteWorkItemFromTaskMutation,
|
||||
|
@ -70,17 +81,33 @@ export default {
|
|||
},
|
||||
}) => {
|
||||
if (errors?.length) {
|
||||
throw new Error(errors[0].message);
|
||||
throw new Error(errors[0]);
|
||||
}
|
||||
|
||||
this.$emit('workItemDeleted', descriptionHtml);
|
||||
this.$refs.modal.hide();
|
||||
this.hide();
|
||||
},
|
||||
)
|
||||
.catch((e) => {
|
||||
this.error =
|
||||
e.message ||
|
||||
s__('WorkItem|Something went wrong when deleting the task. Please try again.');
|
||||
.catch((error) => {
|
||||
this.setErrorMessage(error.message);
|
||||
});
|
||||
},
|
||||
deleteWorkItemWithoutTaskData() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: deleteWorkItemMutation,
|
||||
variables: { input: { id: this.workItemId } },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.workItemDelete.errors?.length) {
|
||||
throw new Error(data.workItemDelete.errors[0]);
|
||||
}
|
||||
|
||||
this.$emit('workItemDeleted', this.workItemId);
|
||||
this.hide();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setErrorMessage(error.message);
|
||||
});
|
||||
},
|
||||
closeModal() {
|
||||
|
@ -91,7 +118,7 @@ export default {
|
|||
this.$refs.modal.hide();
|
||||
},
|
||||
setErrorMessage(message) {
|
||||
this.error = message;
|
||||
this.error = message || this.$options.i18n.errorMessage;
|
||||
},
|
||||
show() {
|
||||
this.$refs.modal.show();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
- if can?(current_user, :read_issue_link, @project)
|
||||
.js-related-issues-root{ data: { endpoint: project_issue_links_path(@project, @issue),
|
||||
can_add_related_issues: "#{can?(current_user, :admin_issue_link, @issue)}",
|
||||
full_path: @project.full_path,
|
||||
has_issue_weights_feature: @project.licensed_feature_available?(:issue_weights).to_s,
|
||||
help_path: help_page_path('user/project/issues/related_issues'),
|
||||
show_categorized_issues: "false" } }
|
||||
show_categorized_issues: @project.licensed_feature_available?(:blocked_issues).to_s } }
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
= render_if_exists 'projects/issues/work_item_links'
|
||||
= render_if_exists 'projects/issues/linked_resources'
|
||||
= render_if_exists 'projects/issues/related_issues'
|
||||
= render 'projects/issues/related_issues'
|
||||
|
||||
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: issuable.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
|
||||
|
||||
|
|
|
@ -86,6 +86,12 @@
|
|||
- 'i_code_review_merge_request_widget_test_summary_expand_success'
|
||||
- 'i_code_review_merge_request_widget_test_summary_expand_warning'
|
||||
- 'i_code_review_merge_request_widget_test_summary_expand_failed'
|
||||
- 'i_code_review_merge_request_widget_accessibility_view'
|
||||
- 'i_code_review_merge_request_widget_accessibility_full_report_clicked'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_success'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_warning'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_failed'
|
||||
- name: code_review_category_monthly_active_users
|
||||
operator: OR
|
||||
source: redis
|
||||
|
@ -160,6 +166,12 @@
|
|||
- 'i_code_review_merge_request_widget_test_summary_expand_success'
|
||||
- 'i_code_review_merge_request_widget_test_summary_expand_warning'
|
||||
- 'i_code_review_merge_request_widget_test_summary_expand_failed'
|
||||
- 'i_code_review_merge_request_widget_accessibility_view'
|
||||
- 'i_code_review_merge_request_widget_accessibility_full_report_clicked'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_success'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_warning'
|
||||
- 'i_code_review_merge_request_widget_accessibility_expand_failed'
|
||||
- name: code_review_extension_category_monthly_active_users
|
||||
operator: OR
|
||||
source: redis
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_view_monthly
|
||||
description: The count of unique users (monthly) who were able to see the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_view
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_full_report_clicked_monthly
|
||||
description: The count of unique users (monthly) who clicked the Full Report button on the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_monthly
|
||||
description: The count of unique users (monthly) who expanded the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_success_monthly
|
||||
description: The count of unique users (monthly) who expanded the Accessibility widget extension while it is in its Success state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_success
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_warning_monthly
|
||||
description: The count of unique users (monthly) who expanded the Accessibility widget extension while it is in its Warning state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_warning
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_failed_monthly
|
||||
description: The count of unique users (monthly) who expanded the Accessibility widget extension while it is in its Failed state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_failed
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_view_weekly
|
||||
description: The count of unique users (weekly) who were able to see the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_view
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_full_report_clicked_weekly
|
||||
description: The count of unique users (weekly) who clicked the Full Report button on the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_weekly
|
||||
description: The count of unique users (weekly) who expanded the Accessibility widget extension
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_success_weekly
|
||||
description: The count of unique users (weekly) who expanded the Accessibility widget extension while it is in its Success state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_success
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_warning_weekly
|
||||
description: The count of unique users (weekly) who expanded the Accessibility widget extension while it is in its Warning state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_warning
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_merge_request_widget_accessibility_expand_failed_weekly
|
||||
description: The count of unique users (weekly) who expanded the Accessibility widget extension while it is in its Failed state
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_expand_failed
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_view
|
||||
description: Total number of times the Accessibility widget extension was viewed (rendered to the screen)
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_view
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_full_report_clicked
|
||||
description: Total number of times the Accessibility widget extension Full Report button was clicked
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_full_report_clicked
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_expand
|
||||
description: Total number of times the Accessibility widget extension was expanded (in any state)
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_expand
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_expand_success
|
||||
description: Total number of times the Accessibility widget extension was expanded (while in its Success state)
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_expand_success
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_expand_warning
|
||||
description: Total number of times the Accessibility widget extension was expanded (while in its Warning state)
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_expand_warning
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: counts.i_code_review_merge_request_widget_accessibility_count_expand_failed
|
||||
description: Total number of times the Accessibility widget extension was expanded (while in its Failed state)
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_category: optional
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_count_expand_failed
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateVulnerabilitiesProjectIdIdIndex < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NEW_INDEX_NAME = 'idx_vulnerabilities_partial_devops_adoption_and_default_branch'
|
||||
OLD_INDEX_NAME = 'idx_vulnerabilities_partial_devops_adoption'
|
||||
|
||||
def up
|
||||
add_concurrent_index :vulnerabilities, [:project_id, :created_at, :present_on_default_branch],
|
||||
where: 'state != 1',
|
||||
name: NEW_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name(:vulnerabilities, OLD_INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :vulnerabilities, [:project_id, :created_at], where: 'state != 1', name: OLD_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name(:vulnerabilities, NEW_INDEX_NAME)
|
||||
end
|
||||
end
|
1
db/schema_migrations/20220603173103
Normal file
1
db/schema_migrations/20220603173103
Normal file
|
@ -0,0 +1 @@
|
|||
4a618d15ee56e7cb9a20385824cc63cf12f8a2eb3604c787f79356398094a3b6
|
|
@ -27241,7 +27241,7 @@ CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON v
|
|||
|
||||
CREATE UNIQUE INDEX idx_vuln_signatures_uniqueness_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, algorithm_type, signature_sha);
|
||||
|
||||
CREATE INDEX idx_vulnerabilities_partial_devops_adoption ON vulnerabilities USING btree (project_id, created_at) WHERE (state <> 1);
|
||||
CREATE INDEX idx_vulnerabilities_partial_devops_adoption_and_default_branch ON vulnerabilities USING btree (project_id, created_at, present_on_default_branch) WHERE (state <> 1);
|
||||
|
||||
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue ON vulnerability_external_issue_links USING btree (vulnerability_id, external_type, external_project_key, external_issue_key);
|
||||
|
||||
|
|
|
@ -97,16 +97,6 @@ Example response:
|
|||
"type": "development",
|
||||
"group": "group::geo",
|
||||
"default_enabled": true
|
||||
},
|
||||
{
|
||||
"name": "analytics_devops_adoption_codeowners",
|
||||
"introduced_by_url": "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59874",
|
||||
"rollout_issue_url": "https://gitlab.com/gitlab-org/gitlab/-/issues/328542",
|
||||
"milestone": "13.12",
|
||||
"log_state_changes": null,
|
||||
"type": "development",
|
||||
"group": "group::optimize",
|
||||
"default_enabled": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -45,6 +45,12 @@ Do not include the deprecation announcement in the merge request that introduces
|
|||
Use a separate MR to create a deprecation entry. For steps to create a deprecation entry, see
|
||||
[Deprecations](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations).
|
||||
|
||||
## How are Community Contributions to a deprecated feature handled?
|
||||
|
||||
Development on deprecated features is restricted to Priority 1 / Severity 1 bug fixes. Any community contributions to deprecated features are unlikely to be prioritized during milestone planning.
|
||||
|
||||
However, at GitLab, we [give agency](https://about.gitlab.com/handbook/values/#give-agency) to our team members. So, a member of the team associated with the contribution may decide to review and merge it at their discretion.
|
||||
|
||||
## When can a feature be removed/changed?
|
||||
|
||||
Generally, feature or configuration can be removed/changed only on major release.
|
||||
|
|
|
@ -325,3 +325,28 @@
|
|||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
## Accessibility
|
||||
- name: i_code_review_merge_request_widget_accessibility_view
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_accessibility_expand
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_accessibility_expand_success
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_accessibility_expand_warning
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_accessibility_expand_failed
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
class MergeRequestWidgetExtensionCounter < BaseCounter
|
||||
KNOWN_EVENTS = %w[view full_report_clicked expand expand_success expand_warning expand_failed].freeze
|
||||
PREFIX = 'i_code_review_merge_request_widget'
|
||||
WIDGETS = %w[test_summary].freeze
|
||||
WIDGETS = %w[accessibility test_summary].freeze
|
||||
|
||||
class << self
|
||||
private
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { GlIcon, GlLink, GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import IssueDueDate from '~/boards/components/issue_due_date.vue';
|
||||
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue';
|
||||
import IssueMilestone from '~/issuable/components/issue_milestone.vue';
|
||||
import IssueAssignees from '~/issuable/components/issue_assignees.vue';
|
||||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||
import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
...jest.requireActual('~/lib/utils/url_utility'),
|
||||
updateHistory: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('RelatedIssuableItem', () => {
|
||||
let wrapper;
|
||||
|
||||
function mountComponent({ mountMethod = mount, stubs = {}, props = {}, slots = {} } = {}) {
|
||||
wrapper = mountMethod(RelatedIssuableItem, {
|
||||
propsData: props,
|
||||
slots,
|
||||
stubs,
|
||||
});
|
||||
}
|
||||
|
||||
const props = {
|
||||
const defaultProps = {
|
||||
idKey: 1,
|
||||
displayReference: 'gitlab-org/gitlab-test#1',
|
||||
pathIdSeparator: '#',
|
||||
|
@ -31,84 +33,94 @@ describe('RelatedIssuableItem', () => {
|
|||
assignees: defaultAssignees,
|
||||
eventNamespace: 'relatedIssue',
|
||||
};
|
||||
const slots = {
|
||||
dueDate: '<div class="js-due-date-slot"></div>',
|
||||
weight: '<div class="js-weight-slot"></div>',
|
||||
};
|
||||
|
||||
const findRemoveButton = () => wrapper.find({ ref: 'removeButton' });
|
||||
const findLockIcon = () => wrapper.find({ ref: 'lockIcon' });
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findIssueDueDate = () => wrapper.findComponent(IssueDueDate);
|
||||
const findLockIcon = () => wrapper.find('[data-testid="lockIcon"]');
|
||||
const findRemoveButton = () => wrapper.findComponent(GlButton);
|
||||
const findTitleLink = () => wrapper.findComponent(GlLink);
|
||||
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
|
||||
|
||||
beforeEach(() => {
|
||||
mountComponent({ props, slots });
|
||||
});
|
||||
function mountComponent({ data = {}, props = {} } = {}) {
|
||||
wrapper = shallowMount(RelatedIssuableItem, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('contains issuable-info-container class when canReorder is false', () => {
|
||||
expect(wrapper.props('canReorder')).toBe(false);
|
||||
expect(wrapper.find('.issuable-info-container').exists()).toBe(true);
|
||||
mountComponent({ props: { canReorder: false } });
|
||||
|
||||
expect(wrapper.classes('issuable-info-container')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render token state', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(wrapper.find('.text-secondary svg').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render remove button', () => {
|
||||
expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false);
|
||||
mountComponent();
|
||||
|
||||
expect(findRemoveButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('token title', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('links to computedPath', () => {
|
||||
expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path'));
|
||||
expect(findTitleLink().attributes('href')).toBe(defaultProps.path);
|
||||
});
|
||||
|
||||
it('renders confidential icon', () => {
|
||||
expect(wrapper.find('.confidential-icon').exists()).toBe(true);
|
||||
expect(findIcon().attributes('title')).toBe(__('Confidential'));
|
||||
});
|
||||
|
||||
it('renders title', () => {
|
||||
expect(wrapper.find('.item-title a').text()).toEqual(props.title);
|
||||
expect(findTitleLink().text()).toBe(defaultProps.title);
|
||||
});
|
||||
});
|
||||
|
||||
describe('token state', () => {
|
||||
const tokenState = () => wrapper.find({ ref: 'iconElementXL' });
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({ state: 'opened' });
|
||||
});
|
||||
|
||||
it('renders if hasState', () => {
|
||||
expect(tokenState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders state title', () => {
|
||||
const stateTitle = tokenState().attributes('title');
|
||||
const formattedCreateDate = formatDate(props.createdAt);
|
||||
mountComponent({ props: { state: 'opened' } });
|
||||
const stateTitle = findIcon().attributes('title');
|
||||
const formattedCreateDate = formatDate(defaultProps.createdAt);
|
||||
|
||||
expect(stateTitle).toContain('<span class="bold">Created</span>');
|
||||
expect(stateTitle).toContain(`<span class="text-tertiary">${formattedCreateDate}</span>`);
|
||||
});
|
||||
|
||||
it('renders aria label', () => {
|
||||
expect(tokenState().attributes('aria-label')).toEqual('opened');
|
||||
mountComponent({ props: { state: 'opened' } });
|
||||
|
||||
expect(findIcon().attributes('arialabel')).toBe('opened');
|
||||
});
|
||||
|
||||
it('renders open icon when open state', () => {
|
||||
expect(tokenState().classes('issue-token-state-icon-open')).toBe(true);
|
||||
mountComponent({ props: { state: 'opened' } });
|
||||
|
||||
expect(findIcon().props('name')).toBe('issue-open-m');
|
||||
expect(findIcon().classes('issue-token-state-icon-open')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders close icon when close state', async () => {
|
||||
wrapper.setProps({
|
||||
state: 'closed',
|
||||
closedAt: '2018-12-01T00:00:00.00Z',
|
||||
});
|
||||
await nextTick();
|
||||
it('renders close icon when close state', () => {
|
||||
mountComponent({ props: { state: 'closed', closedAt: '2018-12-01T00:00:00.00Z' } });
|
||||
|
||||
expect(tokenState().classes('issue-token-state-icon-closed')).toBe(true);
|
||||
expect(findIcon().props('name')).toBe('issue-close');
|
||||
expect(findIcon().classes('issue-token-state-icon-closed')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -116,75 +128,66 @@ describe('RelatedIssuableItem', () => {
|
|||
const tokenMetadata = () => wrapper.find('.item-meta');
|
||||
|
||||
it('renders item path and ID', () => {
|
||||
mountComponent();
|
||||
const pathAndID = tokenMetadata().find('.item-path-id').text();
|
||||
|
||||
expect(pathAndID).toContain('gitlab-org/gitlab-test');
|
||||
expect(pathAndID).toContain('#1');
|
||||
});
|
||||
|
||||
it('renders milestone icon and name', () => {
|
||||
const milestoneIcon = tokenMetadata().find('.item-milestone svg');
|
||||
const milestoneTitle = tokenMetadata().find('.item-milestone .milestone-title');
|
||||
it('renders milestone', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(milestoneIcon.attributes('data-testid')).toBe('clock-icon');
|
||||
expect(milestoneTitle.text()).toContain('Milestone title');
|
||||
expect(wrapper.findComponent(IssueMilestone).props('milestone')).toEqual(
|
||||
defaultProps.milestone,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders due date component with correct due date', () => {
|
||||
expect(wrapper.find(IssueDueDate).props('date')).toBe(props.dueDate);
|
||||
mountComponent();
|
||||
|
||||
expect(findIssueDueDate().props('date')).toBe(defaultProps.dueDate);
|
||||
});
|
||||
|
||||
it('does not render red icon for overdue issue that is closed', async () => {
|
||||
mountComponent({
|
||||
props: {
|
||||
...props,
|
||||
closedAt: '2018-12-01T00:00:00.00Z',
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
it('does not render red icon for overdue issue that is closed', () => {
|
||||
mountComponent({ props: { closedAt: '2018-12-01T00:00:00.00Z' } });
|
||||
|
||||
expect(wrapper.find(IssueDueDate).props('closed')).toBe(true);
|
||||
expect(findIssueDueDate().props('closed')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('token assignees', () => {
|
||||
it('renders assignees avatars', () => {
|
||||
// Expect 2 times 2 because assignees are rendered twice, due to layout issues
|
||||
expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBeDefined();
|
||||
mountComponent();
|
||||
|
||||
expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2');
|
||||
expect(wrapper.findComponent(IssueAssignees).props('assignees')).toEqual(
|
||||
defaultProps.assignees,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove button', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({ canRemove: true });
|
||||
mountComponent({ props: { canRemove: true }, data: { removeDisabled: true } });
|
||||
});
|
||||
|
||||
it('renders if canRemove', () => {
|
||||
expect(findRemoveButton().exists()).toBe(true);
|
||||
expect(findRemoveButton().props('icon')).toBe('close');
|
||||
expect(findRemoveButton().attributes('aria-label')).toBe(__('Remove'));
|
||||
});
|
||||
|
||||
it('does not render the lock icon', () => {
|
||||
expect(findLockIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders disabled button when removeDisabled', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ removeDisabled: true });
|
||||
await nextTick();
|
||||
|
||||
expect(findRemoveButton().attributes('disabled')).toEqual('disabled');
|
||||
it('renders disabled button when removeDisabled', () => {
|
||||
expect(findRemoveButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('triggers onRemoveRequest when clicked', async () => {
|
||||
findRemoveButton().trigger('click');
|
||||
await nextTick();
|
||||
const { relatedIssueRemoveRequest } = wrapper.emitted();
|
||||
it('triggers onRemoveRequest when clicked', () => {
|
||||
findRemoveButton().vm.$emit('click');
|
||||
|
||||
expect(relatedIssueRemoveRequest.length).toBe(1);
|
||||
expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]);
|
||||
expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[defaultProps.idKey]]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -192,10 +195,7 @@ describe('RelatedIssuableItem', () => {
|
|||
const lockedMessage = 'Issues created from a vulnerability cannot be removed';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({
|
||||
isLocked: true,
|
||||
lockedMessage,
|
||||
});
|
||||
mountComponent({ props: { isLocked: true, lockedMessage } });
|
||||
});
|
||||
|
||||
it('does not render the remove button', () => {
|
||||
|
@ -206,4 +206,67 @@ describe('RelatedIssuableItem', () => {
|
|||
expect(findLockIcon().attributes('title')).toBe(lockedMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('work item modal', () => {
|
||||
const workItem = 'gid://gitlab/WorkItem/1';
|
||||
|
||||
it('renders', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findWorkItemDetailModal().props('workItemId')).toBe(workItem);
|
||||
});
|
||||
|
||||
describe('when work item is issue and the related issue title is clicked', () => {
|
||||
it('does not open', () => {
|
||||
mountComponent({ props: { workItemType: 'ISSUE' } });
|
||||
wrapper.vm.$refs.modal.show = jest.fn();
|
||||
|
||||
findTitleLink().vm.$emit('click', { preventDefault: () => {} });
|
||||
|
||||
expect(wrapper.vm.$refs.modal.show).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when work item is task and the related issue title is clicked', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ props: { workItemType: 'TASK' } });
|
||||
wrapper.vm.$refs.modal.show = jest.fn();
|
||||
findTitleLink().vm.$emit('click', { preventDefault: () => {} });
|
||||
});
|
||||
|
||||
it('opens', () => {
|
||||
expect(wrapper.vm.$refs.modal.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('updates the url params with the work item id', () => {
|
||||
expect(updateHistory).toHaveBeenCalledWith({
|
||||
url: `${TEST_HOST}/?work_item_id=1`,
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it emits "workItemDeleted" event', () => {
|
||||
it('emits "relatedIssueRemoveRequest" event', () => {
|
||||
mountComponent();
|
||||
|
||||
findWorkItemDetailModal().vm.$emit('workItemDeleted', workItem);
|
||||
|
||||
expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[workItem]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it emits "close" event', () => {
|
||||
it('removes the work item id from the url params', () => {
|
||||
mountComponent();
|
||||
|
||||
findWorkItemDetailModal().vm.$emit('close');
|
||||
|
||||
expect(updateHistory).toHaveBeenCalledWith({
|
||||
url: `${TEST_HOST}/`,
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
@ -9,8 +9,9 @@ import {
|
|||
} from 'jest/issuable/components/related_issuable_mock_data';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
|
||||
import { linkedIssueTypesMap } from '~/related_issues/constants';
|
||||
import RelatedIssuesBlock from '~/related_issues/components/related_issues_block.vue';
|
||||
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
|
||||
import relatedIssuesService from '~/related_issues/services/related_issues_service';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
@ -19,6 +20,8 @@ describe('RelatedIssuesRoot', () => {
|
|||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const findRelatedIssuesBlock = () => wrapper.findComponent(RelatedIssuesBlock);
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(defaultProps.endpoint).reply(200, []);
|
||||
|
@ -26,100 +29,114 @@ describe('RelatedIssuesRoot', () => {
|
|||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
if (wrapper) {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
}
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const createComponent = (mountFn = mount) => {
|
||||
wrapper = mountFn(RelatedIssuesRoot, {
|
||||
propsData: defaultProps,
|
||||
const createComponent = ({ props = {}, data = {} } = {}) => {
|
||||
wrapper = mount(RelatedIssuesRoot, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for fetch request `fetchRelatedIssues` to complete before starting to test
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
describe('methods', () => {
|
||||
describe('onRelatedIssueRemoveRequest', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(relatedIssuesService.prototype, 'fetchRelatedIssues')
|
||||
.mockReturnValue(Promise.reject());
|
||||
|
||||
return createComponent().then(() => {
|
||||
describe('events', () => {
|
||||
describe('when "relatedIssueRemoveRequest" event is emitted', () => {
|
||||
describe('when emitted value is a numerical issue', () => {
|
||||
beforeEach(async () => {
|
||||
jest
|
||||
.spyOn(relatedIssuesService.prototype, 'fetchRelatedIssues')
|
||||
.mockReturnValue(Promise.reject());
|
||||
await createComponent();
|
||||
wrapper.vm.store.setRelatedIssues([issuable1]);
|
||||
});
|
||||
});
|
||||
|
||||
it('remove related issue and succeeds', () => {
|
||||
mock.onDelete(issuable1.referencePath).reply(200, { issues: [] });
|
||||
it('removes related issue on API success', async () => {
|
||||
mock.onDelete(issuable1.referencePath).reply(200, { issues: [] });
|
||||
|
||||
wrapper.vm.onRelatedIssueRemoveRequest(issuable1.id);
|
||||
findRelatedIssuesBlock().vm.$emit('relatedIssueRemoveRequest', issuable1.id);
|
||||
await axios.waitForAll();
|
||||
|
||||
return axios.waitForAll().then(() => {
|
||||
expect(wrapper.vm.state.relatedIssues).toEqual([]);
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not remove related issue on API error', async () => {
|
||||
mock.onDelete(issuable1.referencePath).reply(422, {});
|
||||
|
||||
findRelatedIssuesBlock().vm.$emit('relatedIssueRemoveRequest', issuable1.id);
|
||||
await axios.waitForAll();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toEqual([
|
||||
expect.objectContaining({ id: issuable1.id }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('remove related issue, fails, and restores to related issues', () => {
|
||||
mock.onDelete(issuable1.referencePath).reply(422, {});
|
||||
describe('when emitted value is a work item id', () => {
|
||||
it('removes related issue', async () => {
|
||||
const workItem = `gid://gitlab/WorkItem/${issuable1.id}`;
|
||||
createComponent({ data: { state: { relatedIssues: [issuable1] } } });
|
||||
|
||||
wrapper.vm.onRelatedIssueRemoveRequest(issuable1.id);
|
||||
findRelatedIssuesBlock().vm.$emit('relatedIssueRemoveRequest', workItem);
|
||||
await nextTick();
|
||||
|
||||
return axios.waitForAll().then(() => {
|
||||
expect(wrapper.vm.state.relatedIssues).toHaveLength(1);
|
||||
expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onToggleAddRelatedIssuesForm', () => {
|
||||
beforeEach(() => createComponent(shallowMount));
|
||||
describe('when "toggleAddRelatedIssuesForm" event is emitted', () => {
|
||||
it('toggles related issues form to visible from hidden', async () => {
|
||||
createComponent();
|
||||
|
||||
it('toggle related issues form to visible', () => {
|
||||
wrapper.vm.onToggleAddRelatedIssuesForm();
|
||||
findRelatedIssuesBlock().vm.$emit('toggleAddRelatedIssuesForm');
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.isFormVisible).toEqual(true);
|
||||
expect(findRelatedIssuesBlock().props('isFormVisible')).toBe(true);
|
||||
});
|
||||
|
||||
it('show add related issues form to hidden', () => {
|
||||
wrapper.vm.isFormVisible = true;
|
||||
it('toggles related issues form to hidden from visible', async () => {
|
||||
createComponent({ data: { isFormVisible: true } });
|
||||
|
||||
wrapper.vm.onToggleAddRelatedIssuesForm();
|
||||
findRelatedIssuesBlock().vm.$emit('toggleAddRelatedIssuesForm');
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.isFormVisible).toEqual(false);
|
||||
expect(findRelatedIssuesBlock().props('isFormVisible')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onPendingIssueRemoveRequest', () => {
|
||||
beforeEach(() =>
|
||||
createComponent().then(() => {
|
||||
wrapper.vm.store.setPendingReferences([issuable1.reference]);
|
||||
}),
|
||||
);
|
||||
|
||||
it('remove pending related issue', () => {
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(1);
|
||||
|
||||
wrapper.vm.onPendingIssueRemoveRequest(0);
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onPendingFormSubmit', () => {
|
||||
describe('when "pendingIssuableRemoveRequest" event is emitted', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
wrapper.vm.store.setPendingReferences([issuable1.reference]);
|
||||
});
|
||||
|
||||
it('removes pending related issue', async () => {
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(1);
|
||||
|
||||
findRelatedIssuesBlock().vm.$emit('pendingIssuableRemoveRequest', 0);
|
||||
await nextTick();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "addIssuableFormSubmit" event is emitted', () => {
|
||||
beforeEach(async () => {
|
||||
jest
|
||||
.spyOn(relatedIssuesService.prototype, 'fetchRelatedIssues')
|
||||
.mockReturnValue(Promise.reject());
|
||||
|
||||
return createComponent().then(() => {
|
||||
jest.spyOn(wrapper.vm, 'processAllReferences');
|
||||
jest.spyOn(wrapper.vm.service, 'addRelatedIssues');
|
||||
createFlash.mockClear();
|
||||
});
|
||||
await createComponent();
|
||||
jest.spyOn(wrapper.vm, 'processAllReferences');
|
||||
jest.spyOn(wrapper.vm.service, 'addRelatedIssues');
|
||||
createFlash.mockClear();
|
||||
});
|
||||
|
||||
it('processes references before submitting', () => {
|
||||
|
@ -130,23 +147,22 @@ describe('RelatedIssuesRoot', () => {
|
|||
linkedIssueType,
|
||||
};
|
||||
|
||||
wrapper.vm.onPendingFormSubmit(emitObj);
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormSubmit', emitObj);
|
||||
|
||||
expect(wrapper.vm.processAllReferences).toHaveBeenCalledWith(input);
|
||||
expect(wrapper.vm.service.addRelatedIssues).toHaveBeenCalledWith([input], linkedIssueType);
|
||||
});
|
||||
|
||||
it('submit zero pending issue as related issue', () => {
|
||||
it('submits zero pending issues as related issue', () => {
|
||||
wrapper.vm.store.setPendingReferences([]);
|
||||
wrapper.vm.onPendingFormSubmit({});
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
|
||||
expect(wrapper.vm.state.relatedIssues).toHaveLength(0);
|
||||
});
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormSubmit', {});
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(0);
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('submit pending issue as related issue', () => {
|
||||
it('submits pending issue as related issue', async () => {
|
||||
mock.onPost(defaultProps.endpoint).reply(200, {
|
||||
issuables: [issuable1],
|
||||
result: {
|
||||
|
@ -154,18 +170,18 @@ describe('RelatedIssuesRoot', () => {
|
|||
status: 'success',
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.store.setPendingReferences([issuable1.reference]);
|
||||
wrapper.vm.onPendingFormSubmit({});
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
|
||||
expect(wrapper.vm.state.relatedIssues).toHaveLength(1);
|
||||
expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
|
||||
});
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormSubmit', {});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(0);
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toEqual([
|
||||
expect.objectContaining({ id: issuable1.id }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('submit multiple pending issues as related issues', () => {
|
||||
it('submits multiple pending issues as related issues', async () => {
|
||||
mock.onPost(defaultProps.endpoint).reply(200, {
|
||||
issuables: [issuable1, issuable2],
|
||||
result: {
|
||||
|
@ -173,202 +189,149 @@ describe('RelatedIssuesRoot', () => {
|
|||
status: 'success',
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.store.setPendingReferences([issuable1.reference, issuable2.reference]);
|
||||
wrapper.vm.onPendingFormSubmit({});
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
|
||||
expect(wrapper.vm.state.relatedIssues).toHaveLength(2);
|
||||
expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
|
||||
expect(wrapper.vm.state.relatedIssues[1].id).toEqual(issuable2.id);
|
||||
});
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormSubmit', {});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(0);
|
||||
expect(findRelatedIssuesBlock().props('relatedIssues')).toEqual([
|
||||
expect.objectContaining({ id: issuable1.id }),
|
||||
expect.objectContaining({ id: issuable2.id }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('displays a message from the backend upon error', () => {
|
||||
it('displays a message from the backend upon error', async () => {
|
||||
const input = '#123';
|
||||
const message = 'error';
|
||||
|
||||
mock.onPost(defaultProps.endpoint).reply(409, { message });
|
||||
wrapper.vm.store.setPendingReferences([issuable1.reference, issuable2.reference]);
|
||||
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
wrapper.vm.onPendingFormSubmit(input);
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message,
|
||||
});
|
||||
});
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormSubmit', input);
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({ message });
|
||||
});
|
||||
});
|
||||
|
||||
describe('onPendingFormCancel', () => {
|
||||
beforeEach(() =>
|
||||
createComponent().then(() => {
|
||||
wrapper.vm.isFormVisible = true;
|
||||
wrapper.vm.inputValue = 'foo';
|
||||
}),
|
||||
);
|
||||
|
||||
it('when canceling and hiding add issuable form', async () => {
|
||||
wrapper.vm.onPendingFormCancel();
|
||||
describe('when "addIssuableFormCancel" event is emitted', () => {
|
||||
beforeEach(() => createComponent({ data: { isFormVisible: true, inputValue: 'foo' } }));
|
||||
|
||||
it('hides form and resets input', async () => {
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormCancel');
|
||||
await nextTick();
|
||||
expect(wrapper.vm.isFormVisible).toEqual(false);
|
||||
expect(wrapper.vm.inputValue).toEqual('');
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
|
||||
|
||||
expect(findRelatedIssuesBlock().props('isFormVisible')).toBe(false);
|
||||
expect(findRelatedIssuesBlock().props('inputValue')).toBe('');
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchRelatedIssues', () => {
|
||||
beforeEach(() => createComponent());
|
||||
|
||||
it('sets isFetching while fetching', async () => {
|
||||
wrapper.vm.fetchRelatedIssues();
|
||||
|
||||
expect(wrapper.vm.isFetching).toEqual(true);
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.vm.isFetching).toEqual(false);
|
||||
});
|
||||
|
||||
it('should fetch related issues', async () => {
|
||||
mock.onGet(defaultProps.endpoint).reply(200, [issuable1, issuable2]);
|
||||
|
||||
wrapper.vm.fetchRelatedIssues();
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.vm.state.relatedIssues).toHaveLength(2);
|
||||
expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
|
||||
expect(wrapper.vm.state.relatedIssues[1].id).toEqual(issuable2.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onInput', () => {
|
||||
beforeEach(() => createComponent());
|
||||
|
||||
it('fill in issue number reference and adds to pending related issues', () => {
|
||||
describe('when "addIssuableFormInput" event is emitted', () => {
|
||||
it('updates pending references with issue reference', async () => {
|
||||
const input = '#123 ';
|
||||
wrapper.vm.onInput({
|
||||
createComponent();
|
||||
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: [input.trim()],
|
||||
touchedReference: input,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(1);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('#123');
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toEqual([input.trim()]);
|
||||
});
|
||||
|
||||
it('fill in with full reference', () => {
|
||||
it('updates pending references with full reference', async () => {
|
||||
const input = 'asdf/qwer#444 ';
|
||||
wrapper.vm.onInput({ untouchedRawReferences: [input.trim()], touchedReference: input });
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(1);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('asdf/qwer#444');
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: [input.trim()],
|
||||
touchedReference: input,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toEqual([input.trim()]);
|
||||
});
|
||||
|
||||
it('fill in with issue link', () => {
|
||||
it('updates pending references with issue link', async () => {
|
||||
const link = 'http://localhost:3000/foo/bar/issues/111';
|
||||
const input = `${link} `;
|
||||
wrapper.vm.onInput({ untouchedRawReferences: [input.trim()], touchedReference: input });
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(1);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual(link);
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: [input.trim()],
|
||||
touchedReference: input,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toEqual([link]);
|
||||
});
|
||||
|
||||
it('fill in with multiple references', () => {
|
||||
it('updates pending references with multiple references', async () => {
|
||||
const input = 'asdf/qwer#444 #12 ';
|
||||
wrapper.vm.onInput({
|
||||
createComponent();
|
||||
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: input.trim().split(/\s/),
|
||||
touchedReference: '2',
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(2);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('asdf/qwer#444');
|
||||
expect(wrapper.vm.state.pendingReferences[1]).toEqual('#12');
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toEqual([
|
||||
'asdf/qwer#444',
|
||||
'#12',
|
||||
]);
|
||||
});
|
||||
|
||||
it('fill in with some invalid things', () => {
|
||||
it('updates pending references with invalid values', async () => {
|
||||
const input = 'something random ';
|
||||
wrapper.vm.onInput({
|
||||
createComponent();
|
||||
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: input.trim().split(/\s/),
|
||||
touchedReference: '2',
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(2);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('something');
|
||||
expect(wrapper.vm.state.pendingReferences[1]).toEqual('random');
|
||||
expect(findRelatedIssuesBlock().props('pendingReferences')).toEqual([
|
||||
'something',
|
||||
'random',
|
||||
]);
|
||||
});
|
||||
|
||||
it.each`
|
||||
pathIdSeparator
|
||||
${'#'}
|
||||
${'&'}
|
||||
`(
|
||||
'prepends $pathIdSeparator when user enters a numeric value [0-9]',
|
||||
async ({ pathIdSeparator }) => {
|
||||
it.each(['#', '&'])(
|
||||
'prepends %s when user enters a numeric value [0-9]',
|
||||
async (pathIdSeparator) => {
|
||||
const input = '23';
|
||||
createComponent({ props: { pathIdSeparator } });
|
||||
|
||||
await wrapper.setProps({
|
||||
pathIdSeparator,
|
||||
});
|
||||
|
||||
wrapper.vm.onInput({
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormInput', {
|
||||
untouchedRawReferences: input.trim().split(/\s/),
|
||||
touchedReference: input,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.inputValue).toBe(`${pathIdSeparator}${input}`);
|
||||
expect(findRelatedIssuesBlock().props('inputValue')).toBe(`${pathIdSeparator}${input}`);
|
||||
},
|
||||
);
|
||||
|
||||
it('prepends # when user enters a number', async () => {
|
||||
const input = 23;
|
||||
|
||||
wrapper.vm.onInput({
|
||||
untouchedRawReferences: String(input).trim().split(/\s/),
|
||||
touchedReference: input,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.inputValue).toBe(`#${input}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onBlur', () => {
|
||||
beforeEach(() =>
|
||||
createComponent().then(() => {
|
||||
jest.spyOn(wrapper.vm, 'processAllReferences').mockImplementation(() => {});
|
||||
}),
|
||||
);
|
||||
describe('when "addIssuableFormBlur" event is emitted', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
jest.spyOn(wrapper.vm, 'processAllReferences').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('add any references to pending when blurring', () => {
|
||||
it('adds any references to pending when blurring', () => {
|
||||
const input = '#123';
|
||||
|
||||
wrapper.vm.onBlur(input);
|
||||
findRelatedIssuesBlock().vm.$emit('addIssuableFormBlur', input);
|
||||
|
||||
expect(wrapper.vm.processAllReferences).toHaveBeenCalledWith(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processAllReferences', () => {
|
||||
beforeEach(() => createComponent());
|
||||
|
||||
it('add valid reference to pending', () => {
|
||||
const input = '#123';
|
||||
wrapper.vm.processAllReferences(input);
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(1);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('#123');
|
||||
});
|
||||
|
||||
it('add any valid references to pending', () => {
|
||||
const input = 'asdf #123';
|
||||
wrapper.vm.processAllReferences(input);
|
||||
|
||||
expect(wrapper.vm.state.pendingReferences).toHaveLength(2);
|
||||
expect(wrapper.vm.state.pendingReferences[0]).toEqual('asdf');
|
||||
expect(wrapper.vm.state.pendingReferences[1]).toEqual('#123');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,12 @@ import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
|
|||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import getPackagePipelines from '~/packages_and_registries/package_registry/graphql/queries/get_package_pipelines.query.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import {
|
||||
TRACKING_ACTION_CLICK_PIPELINE_LINK,
|
||||
TRACKING_ACTION_CLICK_COMMIT_LINK,
|
||||
TRACKING_LABEL_PACKAGE_HISTORY,
|
||||
} from '~/packages_and_registries/package_registry/constants';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
@ -181,7 +187,6 @@ describe('Package History', () => {
|
|||
it('link', () => {
|
||||
const linkElement = findElementLink(element);
|
||||
const exist = Boolean(link);
|
||||
|
||||
expect(linkElement.exists()).toBe(exist);
|
||||
if (exist) {
|
||||
expect(linkElement.attributes('href')).toBe(link);
|
||||
|
@ -189,4 +194,29 @@ describe('Package History', () => {
|
|||
});
|
||||
},
|
||||
);
|
||||
describe('tracking', () => {
|
||||
let eventSpy;
|
||||
const category = 'UI::Packages';
|
||||
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
it('clicking pipeline link tracks the right action', () => {
|
||||
wrapper.vm.trackPipelineClick();
|
||||
expect(eventSpy).toHaveBeenCalledWith(category, TRACKING_ACTION_CLICK_PIPELINE_LINK, {
|
||||
category,
|
||||
label: TRACKING_LABEL_PACKAGE_HISTORY,
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking commit link tracks the right action', () => {
|
||||
wrapper.vm.trackCommitClick();
|
||||
expect(eventSpy).toHaveBeenCalledWith(category, TRACKING_ACTION_CLICK_COMMIT_LINK, {
|
||||
category,
|
||||
label: TRACKING_LABEL_PACKAGE_HISTORY,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,13 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
|
||||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||
import deleteWorkItemFromTaskMutation from '~/work_items/graphql/delete_task_from_work_item.mutation.graphql';
|
||||
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
|
||||
import {
|
||||
deleteWorkItemFromTaskMutationErrorResponse,
|
||||
deleteWorkItemFromTaskMutationResponse,
|
||||
deleteWorkItemMutationErrorResponse,
|
||||
deleteWorkItemResponse,
|
||||
} from '../mock_data';
|
||||
|
||||
describe('WorkItemDetailModal component', () => {
|
||||
let wrapper;
|
||||
|
@ -25,28 +32,38 @@ describe('WorkItemDetailModal component', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const defaultPropsData = {
|
||||
issueGid: 'gid://gitlab/WorkItem/1',
|
||||
workItemId: 'gid://gitlab/WorkItem/2',
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findWorkItemDetail = () => wrapper.findComponent(WorkItemDetail);
|
||||
|
||||
const createComponent = ({ workItemId = '1', issueGid = '2', error = false } = {}) => {
|
||||
const createComponent = ({
|
||||
lockVersion,
|
||||
lineNumberStart,
|
||||
lineNumberEnd,
|
||||
error = false,
|
||||
deleteWorkItemFromTaskMutationHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(deleteWorkItemFromTaskMutationResponse),
|
||||
deleteWorkItemMutationHandler = jest.fn().mockResolvedValue(deleteWorkItemResponse),
|
||||
} = {}) => {
|
||||
const apolloProvider = createMockApollo([
|
||||
[
|
||||
deleteWorkItemFromTaskMutation,
|
||||
jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workItemDeleteTask: {
|
||||
workItem: { id: 123, descriptionHtml: 'updated work item desc' },
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
[deleteWorkItemFromTaskMutation, deleteWorkItemFromTaskMutationHandler],
|
||||
[deleteWorkItemMutation, deleteWorkItemMutationHandler],
|
||||
]);
|
||||
|
||||
wrapper = shallowMount(WorkItemDetailModal, {
|
||||
apolloProvider,
|
||||
propsData: { workItemId, issueGid },
|
||||
propsData: {
|
||||
...defaultPropsData,
|
||||
lockVersion,
|
||||
lineNumberStart,
|
||||
lineNumberEnd,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error,
|
||||
|
@ -67,8 +84,8 @@ describe('WorkItemDetailModal component', () => {
|
|||
|
||||
expect(findWorkItemDetail().props()).toEqual({
|
||||
isModal: true,
|
||||
workItemId: '1',
|
||||
workItemParentId: '2',
|
||||
workItemId: defaultPropsData.workItemId,
|
||||
workItemParentId: defaultPropsData.issueGid,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -109,16 +126,85 @@ describe('WorkItemDetailModal component', () => {
|
|||
});
|
||||
|
||||
describe('delete work item', () => {
|
||||
it('emits workItemDeleted and closes modal', async () => {
|
||||
createComponent();
|
||||
const newDesc = 'updated work item desc';
|
||||
describe('when there is task data', () => {
|
||||
it('emits workItemDeleted and closes modal', async () => {
|
||||
const mutationMock = jest.fn().mockResolvedValue(deleteWorkItemFromTaskMutationResponse);
|
||||
createComponent({
|
||||
lockVersion: 1,
|
||||
lineNumberStart: '3',
|
||||
lineNumberEnd: '3',
|
||||
deleteWorkItemFromTaskMutationHandler: mutationMock,
|
||||
});
|
||||
const newDesc = 'updated work item desc';
|
||||
|
||||
findWorkItemDetail().vm.$emit('deleteWorkItem');
|
||||
findWorkItemDetail().vm.$emit('deleteWorkItem');
|
||||
await waitForPromises();
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.emitted('workItemDeleted')).toEqual([[newDesc]]);
|
||||
expect(hideModal).toHaveBeenCalled();
|
||||
expect(mutationMock).toHaveBeenCalledWith({
|
||||
input: {
|
||||
id: defaultPropsData.issueGid,
|
||||
lockVersion: 1,
|
||||
taskData: { id: defaultPropsData.workItemId, lineNumberEnd: 3, lineNumberStart: 3 },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('workItemDeleted')).toEqual([[newDesc]]);
|
||||
expect(hideModal).toHaveBeenCalled();
|
||||
it.each`
|
||||
errorType | mutationMock | errorMessage
|
||||
${'an error in the mutation response'} | ${jest.fn().mockResolvedValue(deleteWorkItemFromTaskMutationErrorResponse)} | ${'Error'}
|
||||
${'a network error'} | ${jest.fn().mockRejectedValue(new Error('GraphQL networkError'))} | ${'GraphQL networkError'}
|
||||
`(
|
||||
'shows an error message when there is $errorType',
|
||||
async ({ mutationMock, errorMessage }) => {
|
||||
createComponent({
|
||||
lockVersion: 1,
|
||||
lineNumberStart: '3',
|
||||
lineNumberEnd: '3',
|
||||
deleteWorkItemFromTaskMutationHandler: mutationMock,
|
||||
});
|
||||
|
||||
findWorkItemDetail().vm.$emit('deleteWorkItem');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('workItemDeleted')).toBeUndefined();
|
||||
expect(hideModal).not.toHaveBeenCalled();
|
||||
expect(findAlert().text()).toBe(errorMessage);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('when there is no task data', () => {
|
||||
it('emits workItemDeleted and closes modal', async () => {
|
||||
const mutationMock = jest.fn().mockResolvedValue(deleteWorkItemResponse);
|
||||
createComponent({ deleteWorkItemMutationHandler: mutationMock });
|
||||
|
||||
findWorkItemDetail().vm.$emit('deleteWorkItem');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('workItemDeleted')).toEqual([[defaultPropsData.workItemId]]);
|
||||
expect(hideModal).toHaveBeenCalled();
|
||||
expect(mutationMock).toHaveBeenCalledWith({ input: { id: defaultPropsData.workItemId } });
|
||||
});
|
||||
|
||||
it.each`
|
||||
errorType | mutationMock | errorMessage
|
||||
${'an error in the mutation response'} | ${jest.fn().mockResolvedValue(deleteWorkItemMutationErrorResponse)} | ${'Error'}
|
||||
${'a network error'} | ${jest.fn().mockRejectedValue(new Error('GraphQL networkError'))} | ${'GraphQL networkError'}
|
||||
`(
|
||||
'shows an error message when there is $errorType',
|
||||
async ({ mutationMock, errorMessage }) => {
|
||||
createComponent({ deleteWorkItemMutationHandler: mutationMock });
|
||||
|
||||
findWorkItemDetail().vm.$emit('deleteWorkItem');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('workItemDeleted')).toBeUndefined();
|
||||
expect(hideModal).not.toHaveBeenCalled();
|
||||
expect(findAlert().text()).toBe(errorMessage);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -289,6 +289,32 @@ export const deleteWorkItemFailureResponse = {
|
|||
],
|
||||
};
|
||||
|
||||
export const deleteWorkItemMutationErrorResponse = {
|
||||
data: {
|
||||
workItemDelete: {
|
||||
errors: ['Error'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const deleteWorkItemFromTaskMutationResponse = {
|
||||
data: {
|
||||
workItemDeleteTask: {
|
||||
workItem: { id: 123, descriptionHtml: 'updated work item desc' },
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const deleteWorkItemFromTaskMutationErrorResponse = {
|
||||
data: {
|
||||
workItemDeleteTask: {
|
||||
workItem: { id: 123, descriptionHtml: 'updated work item desc' },
|
||||
errors: ['Error'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const workItemTitleSubscriptionResponse = {
|
||||
data: {
|
||||
issuableTitleUpdated: {
|
||||
|
|
Loading…
Reference in a new issue