237 lines
7.4 KiB
Vue
237 lines
7.4 KiB
Vue
<script>
|
|
import '~/commons/bootstrap';
|
|
import { GlIcon, GlLink, GlTooltip, GlTooltipDirective, GlButton } from '@gitlab/ui';
|
|
import SafeHtml from '~/vue_shared/directives/safe_html';
|
|
import IssueDueDate from '~/boards/components/issue_due_date.vue';
|
|
import { TYPENAME_WORK_ITEM } from '~/graphql_shared/constants';
|
|
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
|
import { isMetaKey } from '~/lib/utils/common_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 {
|
|
components: {
|
|
IssueMilestone,
|
|
IssueAssignees,
|
|
CiIcon,
|
|
GlIcon,
|
|
GlLink,
|
|
GlTooltip,
|
|
IssueWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
|
|
IssueDueDate,
|
|
GlButton,
|
|
WorkItemDetailModal,
|
|
},
|
|
directives: {
|
|
GlTooltip: GlTooltipDirective,
|
|
SafeHtml,
|
|
},
|
|
mixins: [relatedIssuableMixin],
|
|
props: {
|
|
canReorder: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
isLocked: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
lockedMessage: {
|
|
type: String,
|
|
required: false,
|
|
default: '',
|
|
},
|
|
workItemType: {
|
|
type: String,
|
|
required: false,
|
|
default: '',
|
|
},
|
|
},
|
|
computed: {
|
|
stateTitle() {
|
|
return sprintf(
|
|
'<span class="bold">%{state}</span> %{timeInWords}<br/><span class="text-tertiary">%{timestamp}</span>',
|
|
{
|
|
state: this.stateText,
|
|
timeInWords: this.stateTimeInWords,
|
|
timestamp: this.stateTimestamp,
|
|
},
|
|
);
|
|
},
|
|
iconClasses() {
|
|
return `${this.iconClass} ic-${this.iconName}`;
|
|
},
|
|
workItemId() {
|
|
return convertToGraphQLId(TYPENAME_WORK_ITEM, this.idKey);
|
|
},
|
|
},
|
|
methods: {
|
|
handleTitleClick(event) {
|
|
if (this.workItemType === 'TASK') {
|
|
if (isMetaKey(event)) {
|
|
return;
|
|
}
|
|
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>
|
|
|
|
<template>
|
|
<div
|
|
:class="{
|
|
'issuable-info-container': !canReorder,
|
|
'card-body': canReorder,
|
|
}"
|
|
class="item-body gl-display-flex gl-align-items-center gl-gap-3 gl-mx-n2"
|
|
>
|
|
<div
|
|
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap gl-flex-grow-1 gl-gap-2 gl-px-3 gl-py-2 py-xl-0 flex-xl-nowrap gl-min-h-7"
|
|
>
|
|
<!-- Title area: Status icon (XL) and title -->
|
|
<div class="item-title gl-display-flex gl-gap-3 gl-min-w-0">
|
|
<gl-icon
|
|
v-if="hasState"
|
|
ref="iconElementXL"
|
|
:class="iconClasses"
|
|
:name="iconName"
|
|
:title="stateTitle"
|
|
:aria-label="state"
|
|
/>
|
|
<gl-tooltip :target="() => $refs.iconElementXL">
|
|
<span v-safe-html="stateTitle"></span>
|
|
</gl-tooltip>
|
|
<gl-icon
|
|
v-if="confidential"
|
|
v-gl-tooltip
|
|
name="eye-slash"
|
|
:title="__('Confidential')"
|
|
class="confidential-icon"
|
|
:aria-label="__('Confidential')"
|
|
/>
|
|
<gl-link :href="computedPath" class="sortable-link" @click="handleTitleClick">
|
|
{{ title }}
|
|
</gl-link>
|
|
</div>
|
|
|
|
<!-- Info area: meta, path, and assignees -->
|
|
<div
|
|
class="item-info-area gl-display-flex gl-flex-grow-1 gl-flex-shrink-0 gl-gap-3 gl-ml-6 ml-xl-0"
|
|
>
|
|
<!-- Meta area: path and attributes -->
|
|
<!-- If there is no room beside the path, meta attributes are put ABOVE it (flex-wrap-reverse). -->
|
|
<!-- See design: https://gitlab-org.gitlab.io/gitlab-design/hosted/pedro/%2383-issue-mr-rows-cards-spec-previews/#artboard16 -->
|
|
<div
|
|
class="item-meta gl-display-flex gl-md-justify-content-space-between gl-gap-3 gl-flex-wrap-wrap-reverse"
|
|
>
|
|
<!-- Path area: status icon (<XL), path, issue # -->
|
|
<div
|
|
class="item-path-area item-path-id gl-display-flex gl-align-items-center gl-flex-wrap gl-gap-3"
|
|
>
|
|
<gl-tooltip :target="() => $refs.iconElement">
|
|
<span v-safe-html="stateTitle"></span>
|
|
</gl-tooltip>
|
|
<span
|
|
v-if="itemPath"
|
|
v-gl-tooltip
|
|
:title="itemPath"
|
|
class="path-id-text d-inline-block"
|
|
>{{ itemPath }}</span
|
|
>
|
|
<span>{{ pathIdSeparator }}{{ itemId }}</span>
|
|
</div>
|
|
|
|
<!-- Attributes area: CI, epic count, weight, milestone -->
|
|
<!-- They have a different order on large screen sizes -->
|
|
<div
|
|
class="item-attributes-area gl-display-flex gl-align-items-center gl-flex-wrap gl-gap-3"
|
|
>
|
|
<span v-if="hasPipeline" class="mr-ci-status order-md-last">
|
|
<a :href="pipelineStatus.details_path">
|
|
<ci-icon v-gl-tooltip :status="pipelineStatus" :title="pipelineStatusTooltip" />
|
|
</a>
|
|
</span>
|
|
|
|
<issue-milestone
|
|
v-if="hasMilestone"
|
|
:milestone="milestone"
|
|
class="item-milestone gl-font-sm gl-display-flex gl-align-items-center order-md-first"
|
|
/>
|
|
|
|
<!-- Flex order for slots is defined in the parent component: e.g. related_issues_block.vue -->
|
|
<span v-if="weight > 0" class="order-md-1">
|
|
<issue-weight
|
|
:weight="weight"
|
|
class="item-weight gl-display-flex gl-align-items-center"
|
|
tag-name="span"
|
|
/>
|
|
</span>
|
|
|
|
<span v-if="dueDate" class="order-md-1">
|
|
<issue-due-date
|
|
:date="dueDate"
|
|
:closed="Boolean(closedAt)"
|
|
tooltip-placement="top"
|
|
css-class="item-due-date gl-display-flex gl-align-items-center"
|
|
/>
|
|
</span>
|
|
|
|
<issue-assignees
|
|
v-if="hasAssignees"
|
|
:assignees="assignees"
|
|
class="item-assignees gl-display-flex gl-align-items-center gl-align-self-end gl-flex-shrink-0 order-md-2"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<span
|
|
v-if="isLocked"
|
|
v-gl-tooltip
|
|
class="gl-display-inline-block gl-cursor-not-allowed"
|
|
:title="lockedMessage"
|
|
data-testid="lockIcon"
|
|
>
|
|
<gl-icon name="lock" />
|
|
</span>
|
|
<gl-button
|
|
v-else-if="canRemove"
|
|
v-gl-tooltip
|
|
icon="close"
|
|
category="tertiary"
|
|
size="small"
|
|
:disabled="removeDisabled"
|
|
class="js-issue-item-remove-button"
|
|
data-qa-selector="remove_related_issue_button"
|
|
:title="__('Remove')"
|
|
:aria-label="__('Remove')"
|
|
@click="onRemoveRequest"
|
|
/>
|
|
<work-item-detail-modal
|
|
ref="modal"
|
|
:work-item-id="workItemId"
|
|
@close="updateWorkItemIdUrlQuery"
|
|
@workItemDeleted="handleWorkItemDeleted"
|
|
/>
|
|
</div>
|
|
</template>
|