summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/work_items
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
commitb39512ed755239198a9c294b6a45e65c05900235 (patch)
treed234a3efade1de67c46b9e5a38ce813627726aa7 /app/assets/javascripts/work_items
parentd31474cf3b17ece37939d20082b07f6657cc79a9 (diff)
downloadgitlab-ce-b39512ed755239198a9c294b6a45e65c05900235.tar.gz
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/work_items')
-rw-r--r--app/assets/javascripts/work_items/components/item_state.vue10
-rw-r--r--app/assets/javascripts/work_items/components/item_title.vue5
-rw-r--r--app/assets/javascripts/work_items/components/work_item_actions.vue55
-rw-r--r--app/assets/javascripts/work_items/components/work_item_assignees.vue95
-rw-r--r--app/assets/javascripts/work_items/components/work_item_description.vue10
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue150
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail_modal.vue41
-rw-r--r--app/assets/javascripts/work_items/components/work_item_labels.vue3
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/index.js16
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue269
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue133
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_menu.vue79
-rw-r--r--app/assets/javascripts/work_items/components/work_item_state.vue7
-rw-r--r--app/assets/javascripts/work_items/components/work_item_title.vue7
-rw-r--r--app/assets/javascripts/work_items/components/work_item_type_icon.vue44
-rw-r--r--app/assets/javascripts/work_items/components/work_item_weight.vue40
-rw-r--r--app/assets/javascripts/work_items/constants.js44
-rw-r--r--app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql13
-rw-r--r--app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql3
-rw-r--r--app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/provider.js28
-rw-r--r--app/assets/javascripts/work_items/graphql/typedefs.graphql26
-rw-r--r--app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.fragment.graphql10
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.query.graphql6
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_links.query.graphql6
29 files changed, 816 insertions, 294 deletions
diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue
index 2dc8e3a1101..2a0913e380a 100644
--- a/app/assets/javascripts/work_items/components/item_state.vue
+++ b/app/assets/javascripts/work_items/components/item_state.vue
@@ -26,7 +26,7 @@ export default {
type: String,
required: true,
},
- loading: {
+ disabled: {
type: Boolean,
required: false,
default: false,
@@ -61,15 +61,17 @@ export default {
:id="$options.labelId"
:value="state"
:options="$options.states"
- :disabled="loading"
- class="gl-w-auto hide-select-decoration"
+ :disabled="disabled"
+ class="gl-w-auto hide-select-decoration gl-pl-3"
+ :class="{ 'gl-bg-transparent! gl-cursor-text!': disabled }"
@change="setState"
/>
</gl-form-group>
</template>
<style>
-.hide-select-decoration:not(:focus, :hover) {
+.hide-select-decoration:not(:focus, :hover),
+.hide-select-decoration:disabled {
background-image: none;
box-shadow: none;
}
diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue
index 1cdc9c28f05..551ebbadb21 100644
--- a/app/assets/javascripts/work_items/components/item_title.vue
+++ b/app/assets/javascripts/work_items/components/item_title.vue
@@ -36,7 +36,7 @@ export default {
<template>
<h2
class="gl-font-weight-normal gl-sm-font-weight-bold gl-mb-5 gl-mt-0 gl-w-full"
- :class="{ 'gl-cursor-not-allowed': disabled }"
+ :class="{ 'gl-cursor-text': disabled }"
aria-labelledby="item-title"
>
<div
@@ -46,7 +46,8 @@ export default {
:aria-label="__('Title')"
:data-placeholder="placeholder"
:contenteditable="!disabled"
- class="gl-pseudo-placeholder gl-px-4 gl-py-3 gl-ml-n4 gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base"
+ class="gl-px-4 gl-py-3 gl-ml-n4 gl-border gl-border-white gl-rounded-base"
+ :class="{ 'gl-hover-border-gray-200 gl-pseudo-placeholder': !disabled }"
@blur="handleBlur"
@keyup="handleInput"
@keydown.enter.exact="handleSubmit"
diff --git a/app/assets/javascripts/work_items/components/work_item_actions.vue b/app/assets/javascripts/work_items/components/work_item_actions.vue
index 77002eeaf55..2753c3fa388 100644
--- a/app/assets/javascripts/work_items/components/work_item_actions.vue
+++ b/app/assets/javascripts/work_items/components/work_item_actions.vue
@@ -1,15 +1,24 @@
<script>
-import { GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownDivider,
+ GlModal,
+ GlModalDirective,
+} from '@gitlab/ui';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
export default {
i18n: {
deleteTask: s__('WorkItem|Delete task'),
+ enableTaskConfidentiality: s__('WorkItem|Turn on confidentiality'),
+ disableTaskConfidentiality: s__('WorkItem|Turn off confidentiality'),
},
components: {
GlDropdown,
GlDropdownItem,
+ GlDropdownDivider,
GlModal,
},
directives: {
@@ -22,14 +31,33 @@ export default {
required: false,
default: null,
},
+ canUpdate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
canDelete: {
type: Boolean,
required: false,
default: false,
},
+ isConfidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isParentConfidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
- emits: ['deleteWorkItem'],
+ emits: ['deleteWorkItem', 'toggleWorkItemConfidentiality'],
methods: {
+ handleToggleWorkItemConfidentiality() {
+ this.track('click_toggle_work_item_confidentiality');
+ this.$emit('toggleWorkItemConfidentiality', !this.isConfidential);
+ },
handleDeleteWorkItem() {
this.track('click_delete_work_item');
this.$emit('deleteWorkItem');
@@ -44,7 +72,7 @@ export default {
</script>
<template>
- <div v-if="canDelete">
+ <div>
<gl-dropdown
icon="ellipsis_v"
text-sr-only
@@ -53,9 +81,24 @@ export default {
no-caret
right
>
- <gl-dropdown-item v-gl-modal="'work-item-confirm-delete'">{{
- $options.i18n.deleteTask
- }}</gl-dropdown-item>
+ <template v-if="canUpdate && !isParentConfidential">
+ <gl-dropdown-item
+ data-testid="confidentiality-toggle-action"
+ @click="handleToggleWorkItemConfidentiality"
+ >{{
+ isConfidential
+ ? $options.i18n.disableTaskConfidentiality
+ : $options.i18n.enableTaskConfidentiality
+ }}</gl-dropdown-item
+ >
+ <gl-dropdown-divider v-if="canDelete" />
+ </template>
+ <gl-dropdown-item
+ v-if="canDelete"
+ v-gl-modal="'work-item-confirm-delete'"
+ data-testid="delete-action"
+ >{{ $options.i18n.deleteTask }}</gl-dropdown-item
+ >
</gl-dropdown>
<gl-modal
modal-id="work-item-confirm-delete"
diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue
index 9ff424aa20f..7342f215b5e 100644
--- a/app/assets/javascripts/work_items/components/work_item_assignees.vue
+++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue
@@ -18,11 +18,17 @@ import { n__, s__ } from '~/locale';
import Tracking from '~/tracking';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import { i18n, TRACKING_CATEGORY_SHOW } from '../constants';
function isTokenSelectorElement(el) {
- return el?.classList.contains('gl-token-close') || el?.classList.contains('dropdown-item');
+ return (
+ el?.classList.contains('gl-token-close') ||
+ el?.classList.contains('dropdown-item') ||
+ // TODO: replace this logic when we have a class added to clear-all button in GitLab UI
+ (el?.classList.contains('gl-button') &&
+ el?.closest('.form-control')?.classList.contains('gl-token-selector'))
+ );
}
function addClass(el) {
@@ -69,6 +75,11 @@ export default {
required: false,
default: false,
},
+ canInviteMembers: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -130,7 +141,7 @@ export default {
if (this.searchUsers.some((user) => user.username === this.currentUser.username)) {
return this.moveCurrentUserToStart(this.searchUsers);
}
- return [this.currentUser, ...this.searchUsers];
+ return [addClass(this.currentUser), ...this.searchUsers];
}
return this.searchUsers;
},
@@ -138,16 +149,25 @@ export default {
return this.searchKey.length === 0;
},
addAssigneesText() {
+ if (!this.canUpdate) {
+ return s__('WorkItem|None');
+ }
return this.allowsMultipleAssignees
? s__('WorkItem|Add assignees')
: s__('WorkItem|Add assignee');
},
+ assigneeIds() {
+ return this.localAssignees.map(({ id }) => id);
+ },
},
watch: {
- assignees(newVal) {
- if (!this.isEditing) {
- this.localAssignees = newVal.map(addClass);
- }
+ assignees: {
+ handler(newVal) {
+ if (!this.isEditing) {
+ this.localAssignees = newVal.map(addClass);
+ }
+ },
+ deep: true,
},
},
created() {
@@ -169,19 +189,33 @@ export default {
handleBlur(e) {
if (isTokenSelectorElement(e.relatedTarget) || !this.isEditing) return;
this.isEditing = false;
- this.setAssignees(this.localAssignees);
+ this.setAssignees(this.assigneeIds);
},
- setAssignees(assignees) {
- this.$apollo.mutate({
- mutation: localUpdateWorkItemMutation,
- variables: {
- input: {
- id: this.workItemId,
- assignees,
+ async setAssignees(assigneeIds) {
+ try {
+ const {
+ data: {
+ workItemUpdate: { errors },
},
- },
- });
- this.track('updated_assignees');
+ } = await this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ assigneesWidget: {
+ assigneeIds,
+ },
+ },
+ },
+ });
+ if (errors.length > 0) {
+ this.throwUpdateError();
+ return;
+ }
+ this.track('updated_assignees');
+ } catch {
+ this.throwUpdateError();
+ }
},
handleFocus() {
this.isEditing = true;
@@ -205,13 +239,25 @@ export default {
},
moveCurrentUserToStart(users = []) {
if (this.currentUser) {
- return [this.currentUser, ...users.filter((user) => user.id !== this.currentUser.id)];
+ return [
+ addClass(this.currentUser),
+ ...users.filter((user) => user.id !== this.currentUser.id),
+ ];
}
return users;
},
closeDropdown() {
this.$refs.tokenSelector.closeDropdown();
},
+ assignToCurrentUser() {
+ this.setAssignees([this.currentUser.id]);
+ this.localAssignees = [addClass(this.currentUser)];
+ },
+ throwUpdateError() {
+ this.$emit('error', i18n.updateError);
+ // If mutation is rejected, we're rolling back to initial state
+ this.localAssignees = this.assignees.map(addClass);
+ },
},
};
</script>
@@ -227,11 +273,12 @@ export default {
ref="tokenSelector"
:selected-tokens="localAssignees"
:container-class="containerClass"
- class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0!"
:class="{ 'gl-hover-border-gray-200': canUpdate }"
:dropdown-items="dropdownItems"
:loading="isLoadingUsers"
:view-only="!canUpdate"
+ :allow-clear-all="isEditing"
+ class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0! gl-mx-2"
@input="handleAssigneesInput"
@text-input="debouncedSearchKeyUpdate"
@focus="handleFocus"
@@ -241,7 +288,7 @@ export default {
>
<template #empty-placeholder>
<div
- class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2"
+ class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-pl-2 gl-top-2"
data-testid="empty-state"
>
<gl-icon name="profile" />
@@ -251,7 +298,7 @@ export default {
size="small"
class="assign-myself"
data-testid="assign-self"
- @click.stop="setAssignees([currentUser])"
+ @click.stop="assignToCurrentUser"
>{{ __('Assign myself') }}</gl-button
>
</div>
@@ -262,7 +309,7 @@ export default {
:title="token.name"
:data-user-id="getUserId(token.id)"
data-placement="top"
- class="gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link"
+ class="gl-ml-n2 gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link"
>
<gl-avatar :size="24" :src="token.avatarUrl" />
<span class="gl-pl-2">{{ token.name }}</span>
@@ -279,7 +326,7 @@ export default {
<rect width="280" height="20" x="10" y="130" rx="4" />
</gl-skeleton-loader>
</template>
- <template #dropdown-footer>
+ <template v-if="canInviteMembers" #dropdown-footer>
<gl-dropdown-divider />
<gl-dropdown-item @click="closeDropdown">
<invite-members-trigger
diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue
index 90e3cd45cb4..cf59789ce2d 100644
--- a/app/assets/javascripts/work_items/components/work_item_description.vue
+++ b/app/assets/javascripts/work_items/components/work_item_description.vue
@@ -172,7 +172,7 @@ export default {
<template>
<gl-form-group
v-if="isEditing"
- class="gl-my-5"
+ class="gl-my-5 gl-border-t gl-pt-6"
:label="__('Description')"
label-for="work-item-description"
>
@@ -182,7 +182,7 @@ export default {
:is-submitting="isSubmitting"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
- class="gl-p-3 bordered-box"
+ class="gl-p-3 bordered-box gl-mt-5"
>
<template #textarea>
<textarea
@@ -217,9 +217,9 @@ export default {
}}</gl-button>
</div>
</gl-form-group>
- <div v-else class="gl-mb-5">
- <div class="gl-display-flex gl-align-items-center gl-mb-5">
- <h3 class="gl-font-base gl-my-0">{{ __('Description') }}</h3>
+ <div v-else class="gl-mb-5 gl-border-t">
+ <div class="gl-display-inline-flex gl-align-items-center gl-mb-5">
+ <label class="d-block col-form-label gl-mr-5">{{ __('Description') }}</label>
<gl-button
v-if="canEdit"
class="gl-ml-auto"
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index ad90fe88947..a5580c14a7a 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -1,7 +1,16 @@
<script>
-import { GlAlert, GlSkeletonLoader, GlIcon, GlButton } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlSkeletonLoader,
+ GlLoadingIcon,
+ GlIcon,
+ GlBadge,
+ GlButton,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import {
i18n,
WIDGET_TYPE_ASSIGNEES,
@@ -11,8 +20,12 @@ import {
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_VIEWED_STORAGE_KEY,
} from '../constants';
+
import workItemQuery from '../graphql/work_item.query.graphql';
import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
+import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
+
import WorkItemActions from './work_item_actions.vue';
import WorkItemState from './work_item_state.vue';
import WorkItemTitle from './work_item_title.vue';
@@ -24,9 +37,14 @@ import WorkItemInformation from './work_item_information.vue';
export default {
i18n,
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
components: {
GlAlert,
+ GlBadge,
GlButton,
+ GlLoadingIcon,
GlSkeletonLoader,
GlIcon,
WorkItemAssignees,
@@ -38,6 +56,7 @@ export default {
WorkItemWeight,
WorkItemInformation,
LocalStorageSync,
+ WorkItemTypeIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -62,6 +81,7 @@ export default {
error: undefined,
workItem: {},
showInfoBanner: true,
+ updateInProgress: false,
};
},
apollo: {
@@ -114,7 +134,7 @@ export default {
return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_LABELS);
},
workItemWeight() {
- return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT);
+ return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT);
},
workItemHierarchy() {
return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY);
@@ -122,9 +142,15 @@ export default {
parentWorkItem() {
return this.workItemHierarchy?.parent;
},
+ parentWorkItemConfidentiality() {
+ return this.parentWorkItem?.confidential;
+ },
parentUrl() {
return `../../issues/${this.parentWorkItem?.iid}`;
},
+ workItemIconName() {
+ return this.workItem?.workItemType?.iconName;
+ },
},
beforeDestroy() {
/** make sure that if the user has not even dismissed the alert ,
@@ -135,6 +161,54 @@ export default {
dismissBanner() {
this.showInfoBanner = false;
},
+ toggleConfidentiality(confidentialStatus) {
+ this.updateInProgress = true;
+ let updateMutation = updateWorkItemMutation;
+ let inputVariables = {
+ id: this.workItemId,
+ confidential: confidentialStatus,
+ };
+
+ if (this.parentWorkItem) {
+ updateMutation = updateWorkItemTaskMutation;
+ inputVariables = {
+ id: this.parentWorkItem.id,
+ taskData: {
+ id: this.workItemId,
+ confidential: confidentialStatus,
+ },
+ };
+ }
+
+ this.$apollo
+ .mutate({
+ mutation: updateMutation,
+ variables: {
+ input: inputVariables,
+ },
+ })
+ .then(
+ ({
+ data: {
+ workItemUpdate: { errors, workItem, task },
+ },
+ }) => {
+ if (errors?.length) {
+ throw new Error(errors[0]);
+ }
+
+ this.$emit('workItemUpdated', {
+ confidential: workItem?.confidential || task?.confidential,
+ });
+ },
+ )
+ .catch((error) => {
+ this.error = error.message;
+ })
+ .finally(() => {
+ this.updateInProgress = false;
+ });
+ },
},
WORK_ITEM_VIEWED_STORAGE_KEY,
};
@@ -142,7 +216,7 @@ export default {
<template>
<section class="gl-pt-5">
- <gl-alert v-if="error" variant="danger" @dismiss="error = undefined">
+ <gl-alert v-if="error" class="gl-mb-3" variant="danger" @dismiss="error = undefined">
{{ error }}
</gl-alert>
@@ -153,33 +227,61 @@ export default {
</gl-skeleton-loader>
</div>
<template v-else>
- <div class="gl-display-flex gl-align-items-center">
+ <div class="gl-display-flex gl-align-items-center" data-testid="work-item-body">
<ul
v-if="parentWorkItem"
- class="list-unstyled gl-display-flex gl-mr-auto"
+ class="list-unstyled gl-display-flex gl-mr-auto gl-max-w-26 gl-md-max-w-50p gl-min-w-0 gl-mb-0"
data-testid="work-item-parent"
>
- <li class="gl-ml-n4">
- <gl-button icon="issues" category="tertiary" :href="parentUrl">{{
- parentWorkItem.title
- }}</gl-button>
- <gl-icon name="chevron-right" :size="16" />
+ <li class="gl-ml-n4 gl-display-flex gl-align-items-center gl-overflow-hidden">
+ <gl-button
+ v-gl-tooltip.hover
+ class="gl-text-truncate gl-max-w-full"
+ icon="issues"
+ category="tertiary"
+ :href="parentUrl"
+ :title="parentWorkItem.title"
+ >{{ parentWorkItem.title }}</gl-button
+ >
+ <gl-icon name="chevron-right" :size="16" class="gl-flex-shrink-0" />
</li>
- <li class="gl-px-4 gl-py-3 gl-line-height-0">
- <gl-icon name="task-done" />
+ <li
+ class="gl-px-4 gl-py-3 gl-line-height-0 gl-display-flex gl-align-items-center gl-overflow-hidden gl-flex-shrink-0"
+ >
+ <work-item-type-icon
+ :work-item-icon-name="workItemIconName"
+ :work-item-type="workItemType && workItemType.toUpperCase()"
+ />
{{ workItemType }}
</li>
</ul>
- <span
+ <work-item-type-icon
v-else
+ :work-item-icon-name="workItemIconName"
+ :work-item-type="workItemType && workItemType.toUpperCase()"
+ show-text
class="gl-font-weight-bold gl-text-secondary gl-mr-auto"
data-testid="work-item-type"
- >{{ workItemType }}</span
+ />
+ <gl-loading-icon v-if="updateInProgress" :inline="true" class="gl-mr-3" />
+ <gl-badge
+ v-if="workItem.confidential"
+ v-gl-tooltip.bottom
+ :title="$options.i18n.confidentialTooltip"
+ variant="warning"
+ icon="eye-slash"
+ class="gl-mr-3 gl-cursor-help"
+ >{{ __('Confidential') }}</gl-badge
>
<work-item-actions
+ v-if="canUpdate || canDelete"
:work-item-id="workItem.id"
:can-delete="canDelete"
+ :can-update="canUpdate"
+ :is-confidential="workItem.confidential"
+ :is-parent-confidential="parentWorkItemConfidentiality"
@deleteWorkItem="$emit('deleteWorkItem')"
+ @toggleWorkItemConfidentiality="toggleConfidentiality"
@error="error = $event"
/>
<gl-button
@@ -206,11 +308,13 @@ export default {
:work-item-title="workItem.title"
:work-item-type="workItemType"
:work-item-parent-id="workItemParentId"
+ :can-update="canUpdate"
@error="error = $event"
/>
<work-item-state
:work-item="workItem"
:work-item-parent-id="workItemParentId"
+ :can-update="canUpdate"
@error="error = $event"
/>
<template v-if="workItemsMvc2Enabled">
@@ -221,6 +325,7 @@ export default {
:assignees="workItemAssignees.assignees.nodes"
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
:work-item-type="workItemType"
+ :can-invite-members="workItemAssignees.canInviteMembers"
@error="error = $event"
/>
<work-item-labels
@@ -229,15 +334,16 @@ export default {
:can-update="canUpdate"
@error="error = $event"
/>
- <work-item-weight
- v-if="workItemWeight"
- class="gl-mb-5"
- :can-update="canUpdate"
- :weight="workItemWeight.weight"
- :work-item-id="workItem.id"
- :work-item-type="workItemType"
- />
</template>
+ <work-item-weight
+ v-if="workItemWeight"
+ class="gl-mb-5"
+ :can-update="canUpdate"
+ :weight="workItemWeight.weight"
+ :work-item-id="workItem.id"
+ :work-item-type="workItemType"
+ @error="error = $event"
+ />
<work-item-description
v-if="hasDescriptionWidget"
:work-item-id="workItem.id"
diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
index df7c6cab7ef..39a662a6c54 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
@@ -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();
diff --git a/app/assets/javascripts/work_items/components/work_item_labels.vue b/app/assets/javascripts/work_items/components/work_item_labels.vue
index 78ed67998d7..e73488bbd70 100644
--- a/app/assets/javascripts/work_items/components/work_item_labels.vue
+++ b/app/assets/javascripts/work_items/components/work_item_labels.vue
@@ -202,7 +202,8 @@ export default {
:dropdown-items="searchLabels"
:loading="isLoading"
:view-only="!canUpdate"
- class="gl-flex-grow-1 gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base col-9 gl-align-self-start gl-px-0! gl-mx-2!"
+ class="gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0! gl-mx-2!"
+ :class="{ 'gl-hover-border-gray-200': canUpdate }"
@input="focusTokenSelector"
@text-input="debouncedSearchKeyUpdate"
@focus="handleFocus"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/index.js b/app/assets/javascripts/work_items/components/work_item_links/index.js
index 176f84f6c1a..86f03583ea3 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/index.js
+++ b/app/assets/javascripts/work_items/components/work_item_links/index.js
@@ -1,16 +1,10 @@
import Vue from 'vue';
-import VueApollo from 'vue-apollo';
import { GlToast } from '@gitlab/ui';
-import createDefaultClient from '~/lib/graphql';
+import { createApolloProvider } from '../../graphql/provider';
import WorkItemLinks from './work_item_links.vue';
-Vue.use(VueApollo);
Vue.use(GlToast);
-const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
-});
-
export default function initWorkItemLinks() {
if (!window.gon.features.workItemsHierarchy) {
return;
@@ -22,16 +16,20 @@ export default function initWorkItemLinks() {
return;
}
+ const { projectPath, wiHasIssueWeightsFeature } = workItemLinksRoot.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el: workItemLinksRoot,
name: 'WorkItemLinksRoot',
- apolloProvider,
+ apolloProvider: createApolloProvider(),
components: {
workItemLinks: WorkItemLinks,
},
provide: {
- projectPath: workItemLinksRoot.dataset.projectPath,
+ projectPath,
+ fullPath: projectPath,
+ hasIssueWeightsFeature: wiHasIssueWeightsFeature,
},
render: (createElement) =>
createElement('work-item-links', {
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 89f086cfca5..534ebabee08 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
@@ -1,8 +1,14 @@
<script>
-import { GlButton, GlBadge, GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlIcon, GlAlert, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { produce } from 'immer';
import { s__ } from '~/locale';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
+import { isMetaKey } from '~/lib/utils/common_utils';
+import { setUrlParams, updateHistory } from '~/lib/utils/url_utility';
+import SidebarEventHub from '~/sidebar/event_hub';
+
import {
STATE_OPEN,
WIDGET_ICONS,
@@ -10,18 +16,26 @@ import {
WIDGET_TYPE_HIERARCHY,
} from '../../constants';
import getWorkItemLinksQuery from '../../graphql/work_item_links.query.graphql';
+import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
+import workItemQuery from '../../graphql/work_item.query.graphql';
+import WorkItemDetailModal from '../work_item_detail_modal.vue';
import WorkItemLinksForm from './work_item_links_form.vue';
import WorkItemLinksMenu from './work_item_links_menu.vue';
export default {
components: {
GlButton,
- GlBadge,
GlIcon,
+ GlAlert,
GlLoadingIcon,
WorkItemLinksForm,
WorkItemLinksMenu,
+ WorkItemDetailModal,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
+ inject: ['projectPath'],
props: {
workItemId: {
type: String,
@@ -35,32 +49,44 @@ export default {
},
},
apollo: {
- children: {
+ workItem: {
query: getWorkItemLinksQuery,
variables() {
return {
id: this.issuableGid,
};
},
- update(data) {
- return (
- data.workItem.widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY)?.children
- .nodes ?? []
- );
- },
skip() {
return !this.issuableId;
},
+ error(e) {
+ this.error = e.message || this.$options.i18n.fetchError;
+ },
},
},
data() {
return {
isShownAddForm: false,
isOpen: true,
- children: [],
+ activeChildId: null,
+ activeToast: null,
+ prefetchedWorkItem: null,
+ error: undefined,
};
},
computed: {
+ children() {
+ return (
+ this.workItem?.widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY)?.children
+ .nodes ?? []
+ );
+ },
+ canUpdate() {
+ return this.workItem?.userPermissions.updateWorkItem || false;
+ },
+ confidential() {
+ return this.workItem?.confidential || false;
+ },
// Only used for children for now but should be extended later to support parents and siblings
isChildrenEmpty() {
return this.children?.length === 0;
@@ -77,28 +103,149 @@ export default {
return this.issuableId ? convertToGraphQLId(TYPE_WORK_ITEM, this.issuableId) : null;
},
isLoading() {
- return this.$apollo.queries.children.loading;
+ return this.$apollo.queries.workItem.loading;
},
childrenIds() {
return this.children.map((c) => c.id);
},
+ childrenCountLabel() {
+ return this.isLoading && this.children.length === 0 ? '...' : this.children.length;
+ },
+ },
+ mounted() {
+ SidebarEventHub.$on('confidentialityUpdated', this.refetchWorkItems);
+ },
+ destroyed() {
+ SidebarEventHub.$off('confidentialityUpdated', this.refetchWorkItems);
},
methods: {
- badgeVariant(state) {
- return state === STATE_OPEN ? 'success' : 'info';
+ refetchWorkItems() {
+ this.$apollo.queries.workItem.refetch();
+ },
+ iconClass(state) {
+ return state === STATE_OPEN ? 'gl-text-green-500' : 'gl-text-blue-500';
+ },
+ iconName(state) {
+ return state === STATE_OPEN ? 'issue-open-m' : 'issue-close';
},
toggle() {
this.isOpen = !this.isOpen;
},
- toggleAddForm() {
- this.isShownAddForm = !this.isShownAddForm;
+ showAddForm() {
+ this.isOpen = true;
+ this.isShownAddForm = true;
+ this.$nextTick(() => {
+ this.$refs.wiLinksForm.$refs.wiTitleInput?.$el.focus();
+ });
+ },
+ hideAddForm() {
+ this.isShownAddForm = false;
},
addChild(child) {
- this.children = [child, ...this.children];
+ const { defaultClient: client } = this.$apollo.provider.clients;
+ this.toggleChildFromCache(child, child.id, client);
+ },
+ openChild(childItemId, e) {
+ if (isMetaKey(e)) {
+ return;
+ }
+ e.preventDefault();
+ this.activeChildId = childItemId;
+ this.$refs.modal.show();
+ this.updateWorkItemIdUrlQuery(childItemId);
+ },
+ closeModal() {
+ this.activeChildId = null;
+ this.updateWorkItemIdUrlQuery(undefined);
+ },
+ handleWorkItemDeleted(childId) {
+ const { defaultClient: client } = this.$apollo.provider.clients;
+ this.toggleChildFromCache(null, childId, client);
+ this.activeToast = this.$toast.show(s__('WorkItem|Task deleted'));
+ },
+ updateWorkItemIdUrlQuery(childItemId) {
+ updateHistory({
+ url: setUrlParams({ work_item_id: getIdFromGraphQLId(childItemId) }),
+ replace: true,
+ });
+ },
+ childPath(childItemId) {
+ return `/${this.projectPath}/-/work_items/${getIdFromGraphQLId(childItemId)}`;
+ },
+ toggleChildFromCache(workItem, childId, store) {
+ const sourceData = store.readQuery({
+ query: getWorkItemLinksQuery,
+ variables: { id: this.issuableGid },
+ });
+
+ const newData = produce(sourceData, (draftState) => {
+ const widgetHierarchy = draftState.workItem.widgets.find(
+ (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+ );
+
+ const index = widgetHierarchy.children.nodes.findIndex((child) => child.id === childId);
+
+ if (index >= 0) {
+ widgetHierarchy.children.nodes.splice(index, 1);
+ } else {
+ widgetHierarchy.children.nodes.push(workItem);
+ }
+ });
+
+ store.writeQuery({
+ query: getWorkItemLinksQuery,
+ variables: { id: this.issuableGid },
+ data: newData,
+ });
+ },
+ async updateWorkItem(workItem, childId, parentId) {
+ return this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: { input: { id: childId, hierarchyWidget: { parentId } } },
+ update: (store) => this.toggleChildFromCache(workItem, childId, store),
+ });
+ },
+ async undoChildRemoval(workItem, childId) {
+ const { data } = await this.updateWorkItem(workItem, childId, this.issuableGid);
+
+ if (data.workItemUpdate.errors.length === 0) {
+ this.activeToast?.hide();
+ }
+ },
+ async removeChild(childId) {
+ const { data } = await this.updateWorkItem(null, childId, null);
+
+ if (data.workItemUpdate.errors.length === 0) {
+ this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
+ action: {
+ text: s__('WorkItem|Undo'),
+ onClick: this.undoChildRemoval.bind(this, data.workItemUpdate.workItem, childId),
+ },
+ });
+ }
+ },
+ prefetchWorkItem(id) {
+ this.prefetch = setTimeout(
+ () =>
+ this.$apollo.addSmartQuery('prefetchedWorkItem', {
+ query: workItemQuery,
+ variables: {
+ id,
+ },
+ update: (data) => data.workItem,
+ }),
+ DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
+ );
+ },
+ clearPrefetching() {
+ clearTimeout(this.prefetch);
},
},
i18n: {
title: s__('WorkItem|Child items'),
+ fetchError: s__(
+ 'WorkItem|Something went wrong when fetching the items list. Please refresh this page.',
+ ),
emptyStateMessage: s__(
'WorkItem|No child items are currently assigned. Use child items to prioritize tasks that your team should complete in order to accomplish your goals!',
),
@@ -112,21 +259,32 @@ export default {
<template>
<div class="gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-bg-gray-10">
<div
- class="gl-p-4 gl-display-flex gl-justify-content-space-between"
+ class="gl-px-5 gl-py-3 gl-display-flex gl-justify-content-space-between"
:class="{ 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100': isOpen }"
>
- <h5 class="gl-m-0 gl-line-height-32 gl-flex-grow-1">{{ $options.i18n.title }}</h5>
+ <div class="gl-display-flex gl-flex-grow-1">
+ <h5 class="gl-m-0 gl-line-height-24">{{ $options.i18n.title }}</h5>
+ <span
+ class="gl-display-inline-flex gl-align-items-center gl-line-height-24 gl-ml-3"
+ data-testid="children-count"
+ >
+ <gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-2 gl-text-gray-500" />
+ {{ childrenCountLabel }}
+ </span>
+ </div>
<gl-button
- v-if="!isShownAddForm"
+ v-if="canUpdate"
category="secondary"
+ size="small"
data-testid="toggle-add-form"
- @click="toggleAddForm"
+ @click="showAddForm"
>
{{ $options.i18n.addChildButtonLabel }}
</gl-button>
- <div class="gl-border-l-1 gl-border-l-solid gl-border-l-gray-50 gl-pl-4 gl-ml-3">
+ <div class="gl-border-l-1 gl-border-l-solid gl-border-l-gray-100 gl-pl-3 gl-ml-3">
<gl-button
category="tertiary"
+ size="small"
:icon="toggleIcon"
:aria-label="toggleLabel"
data-testid="toggle-links"
@@ -134,48 +292,81 @@ export default {
/>
</div>
</div>
+ <gl-alert v-if="error && !isLoading" variant="danger" @dismiss="error = undefined">
+ {{ error }}
+ </gl-alert>
<div
v-if="isOpen"
- class="gl-bg-gray-10 gl-p-4 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
+ class="gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
+ :class="{ 'gl-p-5 gl-pb-3': !error }"
data-testid="links-body"
>
<gl-loading-icon v-if="isLoading" color="dark" class="gl-my-3" />
<template v-else>
- <div v-if="isChildrenEmpty && !isShownAddForm" data-testid="links-empty">
- <p class="gl-my-3">
+ <div v-if="isChildrenEmpty && !isShownAddForm && !error" data-testid="links-empty">
+ <p class="gl-mt-3 gl-mb-4">
{{ $options.i18n.emptyStateMessage }}
</p>
</div>
<work-item-links-form
v-if="isShownAddForm"
+ ref="wiLinksForm"
data-testid="add-links-form"
:issuable-gid="issuableGid"
:children-ids="childrenIds"
- @cancel="toggleAddForm"
+ :parent-confidential="confidential"
+ @cancel="hideAddForm"
@addWorkItemChild="addChild"
/>
<div
v-for="child in children"
:key="child.id"
- class="gl-relative gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row gl-overflow-break-word gl-min-w-0 gl-bg-white gl-mb-3 gl-py-3 gl-px-4 gl-border gl-border-gray-100 gl-rounded-base gl-line-height-32"
+ class="gl-relative gl-display-flex gl-overflow-break-word gl-min-w-0 gl-bg-white gl-mb-3 gl-py-3 gl-px-4 gl-border gl-border-gray-100 gl-rounded-base gl-line-height-32"
data-testid="links-child"
>
- <div>
- <gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-3 gl-text-gray-700" />
- <span class="gl-word-break-all">{{ child.title }}</span>
+ <div class="gl-overflow-hidden gl-display-flex gl-align-items-center gl-flex-grow-1">
+ <gl-icon
+ :name="iconName(child.state)"
+ class="gl-mr-3"
+ :class="iconClass(child.state)"
+ />
+ <gl-icon
+ v-if="child.confidential"
+ v-gl-tooltip.top
+ name="eye-slash"
+ class="gl-mr-2 gl-text-orange-500"
+ data-testid="confidential-icon"
+ :title="__('Confidential')"
+ />
+ <gl-button
+ :href="childPath(child.id)"
+ category="tertiary"
+ variant="link"
+ class="gl-text-truncate gl-max-w-80 gl-text-black-normal!"
+ @click="openChild(child.id, $event)"
+ @mouseover="prefetchWorkItem(child.id)"
+ @mouseout="clearPrefetching"
+ >
+ {{ child.title }}
+ </gl-button>
</div>
- <div
- class="gl-ml-0 gl-sm-ml-auto! gl-mt-3 gl-sm-mt-0 gl-display-inline-flex gl-align-items-center"
- >
- <gl-badge :variant="badgeVariant(child.state)">
- <span class="gl-sm-display-block">{{
- $options.WORK_ITEM_STATUS_TEXT[child.state]
- }}</span>
- </gl-badge>
- <work-item-links-menu :work-item-id="child.id" :parent-work-item-id="issuableGid" />
+ <div class="gl-ml-0 gl-sm-ml-auto! gl-display-inline-flex gl-align-items-center">
+ <work-item-links-menu
+ v-if="canUpdate"
+ :work-item-id="child.id"
+ :parent-work-item-id="issuableGid"
+ data-testid="links-menu"
+ @removeChild="removeChild(child.id)"
+ />
</div>
</div>
+ <work-item-detail-modal
+ ref="modal"
+ :work-item-id="activeChildId"
+ @close="closeModal"
+ @workItemDeleted="handleWorkItemDeleted(activeChildId)"
+ />
</template>
</div>
</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..8b848995d44 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 projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
+import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
+import { TASK_TYPE_NAME } from '../../constants';
export default {
components: {
@@ -11,6 +13,8 @@ export default {
GlForm,
GlFormCombobox,
GlButton,
+ GlFormGroup,
+ GlFormInput,
},
inject: ['projectPath'],
props: {
@@ -24,24 +28,22 @@ export default {
required: false,
default: () => [],
},
+ parentConfidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
apollo: {
- availableWorkItems: {
- query: projectWorkItemsQuery,
+ workItemTypes: {
+ query: projectWorkItemTypesQuery,
variables() {
return {
- projectPath: this.projectPath,
- searchTerm: this.search?.title || this.search,
- types: ['TASK'],
+ fullPath: this.projectPath,
};
},
- skip() {
- return this.search.length === 0;
- },
update(data) {
- return data.workspace.workItems.edges
- .filter((wi) => !this.childrenIds.includes(wi.node.id))
- .map((wi) => wi.node);
+ return data.workspace?.workItemTypes?.nodes;
},
},
},
@@ -50,8 +52,32 @@ 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;
+ },
+ taskWorkItemType() {
+ return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
+ },
+ },
methods: {
getIdFromGraphQLId,
unsetError() {
@@ -79,35 +105,78 @@ 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: this.taskWorkItemType,
+ hierarchyWidget: {
+ parentId: this.issuableGid,
+ },
+ confidential: this.parentConfidential,
+ },
+ },
+ })
+ .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 +186,35 @@ 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"
+ class="gl-mr-2"
+ >
+ {{ $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/components/work_item_links/work_item_links_menu.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_menu.vue
index 6deb87c5dca..1aa4a433a58 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_menu.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_menu.vue
@@ -1,10 +1,5 @@
<script>
import { GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { produce } from 'immer';
-import { s__ } from '~/locale';
-import changeWorkItemParentMutation from '../../graphql/change_work_item_parent_link.mutation.graphql';
-import getWorkItemLinksQuery from '../../graphql/work_item_links.query.graphql';
-import { WIDGET_TYPE_HIERARCHY } from '../../constants';
export default {
components: {
@@ -12,78 +7,6 @@ export default {
GlDropdown,
GlIcon,
},
- props: {
- workItemId: {
- type: String,
- required: true,
- },
- parentWorkItemId: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- activeToast: null,
- };
- },
- methods: {
- toggleChildFromCache(data, store) {
- const sourceData = store.readQuery({
- query: getWorkItemLinksQuery,
- variables: { id: this.parentWorkItemId },
- });
-
- const newData = produce(sourceData, (draftState) => {
- const widgetHierarchy = draftState.workItem.widgets.find(
- (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
- );
-
- const index = widgetHierarchy.children.nodes.findIndex(
- (child) => child.id === this.workItemId,
- );
-
- if (index >= 0) {
- widgetHierarchy.children.nodes.splice(index, 1);
- } else {
- widgetHierarchy.children.nodes.push(data.workItemUpdate.workItem);
- }
- });
-
- store.writeQuery({
- query: getWorkItemLinksQuery,
- variables: { id: this.parentWorkItemId },
- data: newData,
- });
- },
- async addChild(data) {
- const { data: resp } = await this.$apollo.mutate({
- mutation: changeWorkItemParentMutation,
- variables: { id: this.workItemId, parentId: this.parentWorkItemId },
- update: this.toggleChildFromCache.bind(this, data),
- });
-
- if (resp.workItemUpdate.errors.length === 0) {
- this.activeToast?.hide();
- }
- },
- async removeChild() {
- const { data } = await this.$apollo.mutate({
- mutation: changeWorkItemParentMutation,
- variables: { id: this.workItemId, parentId: null },
- update: this.toggleChildFromCache.bind(this, null),
- });
-
- if (data.workItemUpdate.errors.length === 0) {
- this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
- action: {
- text: s__('WorkItem|Undo'),
- onClick: this.addChild.bind(this, data),
- },
- });
- }
- },
- },
};
</script>
@@ -93,7 +16,7 @@ export default {
<template #button-content>
<gl-icon name="ellipsis_v" :size="14" />
</template>
- <gl-dropdown-item @click="removeChild">
+ <gl-dropdown-item @click="$emit('removeChild')">
{{ s__('WorkItem|Remove') }}
</gl-dropdown-item>
</gl-dropdown>
diff --git a/app/assets/javascripts/work_items/components/work_item_state.vue b/app/assets/javascripts/work_items/components/work_item_state.vue
index 87f4a8822b1..080d4025cc3 100644
--- a/app/assets/javascripts/work_items/components/work_item_state.vue
+++ b/app/assets/javascripts/work_items/components/work_item_state.vue
@@ -27,6 +27,11 @@ export default {
required: false,
default: null,
},
+ canUpdate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -102,7 +107,7 @@ export default {
<item-state
v-if="workItem.state"
:state="workItem.state"
- :loading="updateInProgress"
+ :disabled="updateInProgress || !canUpdate"
@changed="updateWorkItemState"
/>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_title.vue b/app/assets/javascripts/work_items/components/work_item_title.vue
index b4c13037038..cd5cc3894f6 100644
--- a/app/assets/javascripts/work_items/components/work_item_title.vue
+++ b/app/assets/javascripts/work_items/components/work_item_title.vue
@@ -31,6 +31,11 @@ export default {
required: false,
default: null,
},
+ canUpdate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
tracking() {
@@ -84,5 +89,5 @@ export default {
</script>
<template>
- <item-title :title="workItemTitle" @title-changed="updateTitle" />
+ <item-title :title="workItemTitle" :disabled="!canUpdate" @title-changed="updateTitle" />
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_type_icon.vue b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
new file mode 100644
index 00000000000..fd914fa350b
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
@@ -0,0 +1,44 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+import { WORK_ITEMS_TYPE_MAP } from '../constants';
+
+export default {
+ components: {
+ GlIcon,
+ },
+ props: {
+ workItemType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showText: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ workItemIconName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ iconName() {
+ return (
+ this.workItemIconName || WORK_ITEMS_TYPE_MAP[this.workItemType]?.icon || 'issue-type-issue'
+ );
+ },
+ workItemTypeName() {
+ return WORK_ITEMS_TYPE_MAP[this.workItemType]?.name;
+ },
+ },
+};
+</script>
+
+<template>
+ <span>
+ <gl-icon :name="iconName" class="gl-mr-2" />
+ <span v-if="workItemTypeName" :class="{ 'gl-sr-only': !showText }">{{ workItemTypeName }}</span>
+ </span>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_weight.vue b/app/assets/javascripts/work_items/components/work_item_weight.vue
index 30e2c1e56b8..b0ad7c97bb1 100644
--- a/app/assets/javascripts/work_items/components/work_item_weight.vue
+++ b/app/assets/javascripts/work_items/components/work_item_weight.vue
@@ -1,9 +1,10 @@
<script>
import { GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-import { TRACKING_CATEGORY_SHOW } from '../constants';
-import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+import { i18n, TRACKING_CATEGORY_SHOW } from '../constants';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
/* eslint-disable @gitlab/require-i18n-strings */
const allowedKeys = [
@@ -97,17 +98,36 @@ export default {
}
},
updateWeight(event) {
+ if (!this.canUpdate) return;
this.isEditing = false;
+
+ const weight = Number(event.target.value);
+ if (this.weight === weight) {
+ return;
+ }
+
this.track('updated_weight');
- this.$apollo.mutate({
- mutation: localUpdateWorkItemMutation,
- variables: {
- input: {
- id: this.workItemId,
- weight: event.target.value === '' ? null : Number(event.target.value),
+ this.$apollo
+ .mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ weightWidget: {
+ weight: event.target.value === '' ? null : weight,
+ },
+ },
},
- },
- });
+ })
+ .then(({ data }) => {
+ if (data.workItemUpdate.errors.length) {
+ throw new Error(data.workItemUpdate.errors.join('\n'));
+ }
+ })
+ .catch((error) => {
+ this.$emit('error', i18n.updateError);
+ Sentry.captureException(error);
+ });
},
},
};
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 2140b418e6d..a2aea3cd327 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -8,11 +8,6 @@ export const STATE_EVENT_CLOSE = 'CLOSE';
export const TRACKING_CATEGORY_SHOW = 'workItems:show';
-export const i18n = {
- fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
- updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
-};
-
export const TASK_TYPE_NAME = 'Task';
export const WIDGET_TYPE_ASSIGNEES = 'ASSIGNEES';
@@ -22,13 +17,48 @@ export const WIDGET_TYPE_WEIGHT = 'WEIGHT';
export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
-export const WIDGET_TYPE_TASK_ICON = 'task-done';
+export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
+export const WORK_ITEM_TYPE_ENUM_ISSUE = 'ISSUE';
+export const WORK_ITEM_TYPE_ENUM_TASK = 'TASK';
+export const WORK_ITEM_TYPE_ENUM_TEST_CASE = 'TEST_CASE';
+export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS';
+
+export const i18n = {
+ fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
+ updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
+ confidentialTooltip: s__(
+ 'WorkItem|Only project members with at least the Reporter role, the author, and assignees can view or be notified about this task.',
+ ),
+};
export const WIDGET_ICONS = {
- TASK: 'task-done',
+ TASK: 'issue-type-task',
};
export const WORK_ITEM_STATUS_TEXT = {
CLOSED: s__('WorkItem|Closed'),
OPEN: s__('WorkItem|Open'),
};
+
+export const WORK_ITEMS_TYPE_MAP = {
+ [WORK_ITEM_TYPE_ENUM_INCIDENT]: {
+ icon: `issue-type-incident`,
+ name: s__('WorkItem|Incident'),
+ },
+ [WORK_ITEM_TYPE_ENUM_ISSUE]: {
+ icon: `issue-type-issue`,
+ name: s__('WorkItem|Issue'),
+ },
+ [WORK_ITEM_TYPE_ENUM_TASK]: {
+ icon: `issue-type-task`,
+ name: s__('WorkItem|Task'),
+ },
+ [WORK_ITEM_TYPE_ENUM_TEST_CASE]: {
+ icon: `issue-type-test-case`,
+ name: s__('WorkItem|Test case'),
+ },
+ [WORK_ITEM_TYPE_ENUM_REQUIREMENTS]: {
+ icon: `issue-type-requirements`,
+ name: s__('WorkItem|Requirements'),
+ },
+};
diff --git a/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql b/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql
deleted file mode 100644
index dc5286174d8..00000000000
--- a/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql
+++ /dev/null
@@ -1,13 +0,0 @@
-mutation changeWorkItemParentLink($id: WorkItemID!, $parentId: WorkItemID) {
- workItemUpdate(input: { id: $id, hierarchyWidget: { parentId: $parentId } }) {
- workItem {
- id
- workItemType {
- id
- }
- title
- state
- }
- errors
- }
-}
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 7f9aaf43068..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
@@ -1,9 +1,10 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation createWorkItem($input: WorkItemCreateInput!) {
workItemCreate(input: $input) {
workItem {
...WorkItem
}
+ errors
}
}
diff --git a/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql
index ccfe62cc585..1f98cd4fa2b 100644
--- a/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) {
workItemCreateFromTask(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql
index 43c92cf89ec..790b8e60b6a 100644
--- a/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation localUpdateWorkItem($input: LocalUpdateWorkItemInput) {
localUpdateWorkItem(input: $input) @client {
diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js
index 8788ad21e7b..b70c06fddea 100644
--- a/app/assets/javascripts/work_items/graphql/provider.js
+++ b/app/assets/javascripts/work_items/graphql/provider.js
@@ -2,7 +2,7 @@ import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import { WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS, WIDGET_TYPE_WEIGHT } from '../constants';
+import { WIDGET_TYPE_LABELS } from '../constants';
import typeDefs from './typedefs.graphql';
import workItemQuery from './work_item.query.graphql';
@@ -10,7 +10,7 @@ export const temporaryConfig = {
typeDefs,
cacheConfig: {
possibleTypes: {
- LocalWorkItemWidget: ['LocalWorkItemLabels', 'LocalWorkItemWeight'],
+ LocalWorkItemWidget: ['LocalWorkItemLabels'],
},
typePolicies: {
WorkItem: {
@@ -25,15 +25,15 @@ export const temporaryConfig = {
allowScopedLabels: true,
nodes: [],
},
- {
- __typename: 'LocalWorkItemWeight',
- type: 'WEIGHT',
- weight: null,
- },
]
);
},
},
+ widgets: {
+ merge(_, incoming) {
+ return incoming;
+ },
+ },
},
},
},
@@ -49,20 +49,6 @@ export const resolvers = {
});
const data = produce(sourceData, (draftData) => {
- if (input.assignees) {
- const assigneesWidget = draftData.workItem.widgets.find(
- (widget) => widget.type === WIDGET_TYPE_ASSIGNEES,
- );
- assigneesWidget.assignees.nodes = [...input.assignees];
- }
-
- if (input.weight != null) {
- const weightWidget = draftData.workItem.mockWidgets.find(
- (widget) => widget.type === WIDGET_TYPE_WEIGHT,
- );
- weightWidget.weight = input.weight;
- }
-
if (input.labels) {
const labelsWidget = draftData.workItem.mockWidgets.find(
(widget) => widget.type === WIDGET_TYPE_LABELS,
diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql
index 48228b15a53..36ffba8a540 100644
--- a/app/assets/javascripts/work_items/graphql/typedefs.graphql
+++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql
@@ -1,7 +1,6 @@
enum LocalWidgetType {
ASSIGNEES
LABELS
- WEIGHT
}
interface LocalWorkItemWidget {
@@ -19,20 +18,29 @@ type LocalWorkItemLabels implements LocalWorkItemWidget {
nodes: [Label!]
}
-type LocalWorkItemWeight implements LocalWorkItemWidget {
- type: LocalWidgetType!
- weight: Int
-}
-
extend type WorkItem {
mockWidgets: [LocalWorkItemWidget]
}
+input LocalUserInput {
+ id: ID!
+ name: String
+ username: String
+ webUrl: String
+ avatarUrl: String
+}
+
+input LocalLabelInput {
+ id: ID!
+ title: String!
+ color: String
+ description: String
+}
+
input LocalUpdateWorkItemInput {
id: WorkItemID!
- assignees: [UserCore!]
- labels: [Label]
- weight: Int
+ assignees: [LocalUserInput!]
+ labels: [LocalLabelInput]
}
type LocalWorkItemPayload {
diff --git a/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql
index 25eb8099251..0a887fcfc00 100644
--- a/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdate($input: WorkItemUpdateInput!) {
workItemUpdate(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql
index ad861a60d15..fad5a9fa5bc 100644
--- a/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdateTask($input: WorkItemUpdateTaskInput!) {
workItemUpdate: workItemUpdateTask(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql
index 148b340b439..6a94c96b347 100644
--- a/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdateWidgets($input: WorkItemUpdateWidgetsInput!) {
workItemUpdateWidgets(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
index 5f64eda96aa..e8ef27ec778 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
@@ -5,9 +5,11 @@ fragment WorkItem on WorkItem {
title
state
description
+ confidential
workItemType {
id
name
+ iconName
}
userPermissions {
deleteWorkItem
@@ -22,6 +24,7 @@ fragment WorkItem on WorkItem {
... on WorkItemWidgetAssignees {
type
allowsMultipleAssignees
+ canInviteMembers
assignees {
nodes {
...User
@@ -34,12 +37,11 @@ fragment WorkItem on WorkItem {
id
iid
title
+ confidential
}
children {
- edges {
- node {
- id
- }
+ nodes {
+ id
}
}
}
diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
index 61cb8802187..a9f7b714551 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
@@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
-#import "./work_item.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
query workItem($id: WorkItemID!) {
workItem(id: $id) {
@@ -12,10 +12,6 @@ query workItem($id: WorkItemID!) {
...Label
}
}
- ... on LocalWorkItemWeight {
- type
- weight
- }
}
}
}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
index c2496f53cc8..df62ca1c143 100644
--- a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
@@ -5,6 +5,11 @@ query workItemQuery($id: WorkItemID!) {
id
}
title
+ userPermissions {
+ deleteWorkItem
+ updateWorkItem
+ }
+ confidential
widgets {
type
... on WorkItemWidgetHierarchy {
@@ -15,6 +20,7 @@ query workItemQuery($id: WorkItemID!) {
children {
nodes {
id
+ confidential
workItemType {
id
}