Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
abc0c2c770
commit
a46fed716c
|
|
@ -7,7 +7,9 @@ const twoFactorNode = document.querySelector('.js-two-factor-auth');
|
||||||
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
|
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
|
||||||
|
|
||||||
if (skippable) {
|
if (skippable) {
|
||||||
const button = `<br/><a class="btn gl-button btn-sm btn-confirm gl-mt-3" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
|
const button = `<div class="gl-alert-actions">
|
||||||
|
<a class="btn gl-button btn-md btn-confirm gl-alert-action" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>
|
||||||
|
</div>`;
|
||||||
const flashAlert = document.querySelector('.flash-alert');
|
const flashAlert = document.querySelector('.flash-alert');
|
||||||
if (flashAlert) {
|
if (flashAlert) {
|
||||||
// eslint-disable-next-line no-unsanitized/method
|
// eslint-disable-next-line no-unsanitized/method
|
||||||
|
|
@ -17,7 +19,5 @@ if (skippable) {
|
||||||
|
|
||||||
mount2faRegistration();
|
mount2faRegistration();
|
||||||
initWebAuthnRegistration();
|
initWebAuthnRegistration();
|
||||||
|
|
||||||
initRecoveryCodes();
|
initRecoveryCodes();
|
||||||
|
|
||||||
initManageTwoFactorForm();
|
initManageTwoFactorForm();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
<script>
|
||||||
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
import {
|
||||||
|
sprintfWorkItem,
|
||||||
|
WIDGET_TYPE_ASSIGNEES,
|
||||||
|
WIDGET_TYPE_HEALTH_STATUS,
|
||||||
|
WIDGET_TYPE_ITERATION,
|
||||||
|
WIDGET_TYPE_LABELS,
|
||||||
|
WIDGET_TYPE_MILESTONE,
|
||||||
|
WIDGET_TYPE_PROGRESS,
|
||||||
|
WIDGET_TYPE_START_AND_DUE_DATE,
|
||||||
|
WIDGET_TYPE_WEIGHT,
|
||||||
|
} from '../constants';
|
||||||
|
import WorkItemState from './work_item_state.vue';
|
||||||
|
import WorkItemDueDate from './work_item_due_date.vue';
|
||||||
|
import WorkItemAssignees from './work_item_assignees.vue';
|
||||||
|
import WorkItemLabels from './work_item_labels.vue';
|
||||||
|
import WorkItemMilestone from './work_item_milestone.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WorkItemLabels,
|
||||||
|
WorkItemMilestone,
|
||||||
|
WorkItemAssignees,
|
||||||
|
WorkItemDueDate,
|
||||||
|
WorkItemState,
|
||||||
|
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
|
||||||
|
WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'),
|
||||||
|
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
|
||||||
|
WorkItemHealthStatus: () =>
|
||||||
|
import('ee_component/work_items/components/work_item_health_status.vue'),
|
||||||
|
},
|
||||||
|
mixins: [glFeatureFlagMixin()],
|
||||||
|
inject: ['fullPath'],
|
||||||
|
props: {
|
||||||
|
workItem: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
workItemParentId: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
workItemType() {
|
||||||
|
return this.workItem.workItemType?.name;
|
||||||
|
},
|
||||||
|
canUpdate() {
|
||||||
|
return this.workItem?.userPermissions?.updateWorkItem;
|
||||||
|
},
|
||||||
|
canDelete() {
|
||||||
|
return this.workItem?.userPermissions?.deleteWorkItem;
|
||||||
|
},
|
||||||
|
canSetWorkItemMetadata() {
|
||||||
|
return this.workItem?.userPermissions?.setWorkItemMetadata;
|
||||||
|
},
|
||||||
|
canAssignUnassignUser() {
|
||||||
|
return this.workItemAssignees && this.canSetWorkItemMetadata;
|
||||||
|
},
|
||||||
|
confidentialTooltip() {
|
||||||
|
return sprintfWorkItem(this.$options.i18n.confidentialTooltip, this.workItemType);
|
||||||
|
},
|
||||||
|
workItemAssignees() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
|
||||||
|
},
|
||||||
|
workItemLabels() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_LABELS);
|
||||||
|
},
|
||||||
|
workItemDueDate() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
|
||||||
|
},
|
||||||
|
workItemWeight() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
||||||
|
},
|
||||||
|
workItemProgress() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_PROGRESS);
|
||||||
|
},
|
||||||
|
workItemIteration() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
|
||||||
|
},
|
||||||
|
workItemHealthStatus() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS);
|
||||||
|
},
|
||||||
|
workItemMilestone() {
|
||||||
|
return this.isWidgetPresent(WIDGET_TYPE_MILESTONE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isWidgetPresent(type) {
|
||||||
|
return this.workItem?.widgets?.find((widget) => widget.type === type);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<work-item-state
|
||||||
|
:work-item="workItem"
|
||||||
|
:work-item-parent-id="workItemParentId"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-assignees
|
||||||
|
v-if="workItemAssignees"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:assignees="workItemAssignees.assignees.nodes"
|
||||||
|
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
:can-invite-members="workItemAssignees.canInviteMembers"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-labels
|
||||||
|
v-if="workItemLabels"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-iid="workItem.iid"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-due-date
|
||||||
|
v-if="workItemDueDate"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:due-date="workItemDueDate.dueDate"
|
||||||
|
:start-date="workItemDueDate.startDate"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-milestone
|
||||||
|
v-if="workItemMilestone"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-milestone="workItemMilestone.milestone"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
@error="$emit('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-iid="workItem.iid"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-progress
|
||||||
|
v-if="workItemProgress"
|
||||||
|
class="gl-mb-5"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:progress="workItemProgress.progress"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-iteration
|
||||||
|
v-if="workItemIteration"
|
||||||
|
class="gl-mb-5"
|
||||||
|
:iteration="workItemIteration.iteration"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-iid="workItem.iid"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<work-item-health-status
|
||||||
|
v-if="workItemHealthStatus"
|
||||||
|
class="gl-mb-5"
|
||||||
|
:health-status="workItemHealthStatus.healthStatus"
|
||||||
|
:can-update="canUpdate"
|
||||||
|
:work-item-id="workItem.id"
|
||||||
|
:work-item-iid="workItem.iid"
|
||||||
|
:work-item-type="workItemType"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
@ -22,18 +22,11 @@ import {
|
||||||
sprintfWorkItem,
|
sprintfWorkItem,
|
||||||
i18n,
|
i18n,
|
||||||
WIDGET_TYPE_ASSIGNEES,
|
WIDGET_TYPE_ASSIGNEES,
|
||||||
WIDGET_TYPE_LABELS,
|
|
||||||
WIDGET_TYPE_NOTIFICATIONS,
|
WIDGET_TYPE_NOTIFICATIONS,
|
||||||
WIDGET_TYPE_CURRENT_USER_TODOS,
|
WIDGET_TYPE_CURRENT_USER_TODOS,
|
||||||
WIDGET_TYPE_DESCRIPTION,
|
WIDGET_TYPE_DESCRIPTION,
|
||||||
WIDGET_TYPE_AWARD_EMOJI,
|
WIDGET_TYPE_AWARD_EMOJI,
|
||||||
WIDGET_TYPE_START_AND_DUE_DATE,
|
|
||||||
WIDGET_TYPE_WEIGHT,
|
|
||||||
WIDGET_TYPE_PROGRESS,
|
|
||||||
WIDGET_TYPE_HIERARCHY,
|
WIDGET_TYPE_HIERARCHY,
|
||||||
WIDGET_TYPE_MILESTONE,
|
|
||||||
WIDGET_TYPE_ITERATION,
|
|
||||||
WIDGET_TYPE_HEALTH_STATUS,
|
|
||||||
WORK_ITEM_TYPE_VALUE_ISSUE,
|
WORK_ITEM_TYPE_VALUE_ISSUE,
|
||||||
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
|
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
|
||||||
WIDGET_TYPE_NOTES,
|
WIDGET_TYPE_NOTES,
|
||||||
|
|
@ -48,17 +41,13 @@ import { findHierarchyWidgetChildren } from '../utils';
|
||||||
import WorkItemTree from './work_item_links/work_item_tree.vue';
|
import WorkItemTree from './work_item_links/work_item_tree.vue';
|
||||||
import WorkItemActions from './work_item_actions.vue';
|
import WorkItemActions from './work_item_actions.vue';
|
||||||
import WorkItemTodos from './work_item_todos.vue';
|
import WorkItemTodos from './work_item_todos.vue';
|
||||||
import WorkItemState from './work_item_state.vue';
|
|
||||||
import WorkItemTitle from './work_item_title.vue';
|
import WorkItemTitle from './work_item_title.vue';
|
||||||
|
import WorkItemAttributesWrapper from './work_item_attributes_wrapper.vue';
|
||||||
import WorkItemCreatedUpdated from './work_item_created_updated.vue';
|
import WorkItemCreatedUpdated from './work_item_created_updated.vue';
|
||||||
import WorkItemDescription from './work_item_description.vue';
|
import WorkItemDescription from './work_item_description.vue';
|
||||||
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
|
|
||||||
import WorkItemDueDate from './work_item_due_date.vue';
|
|
||||||
import WorkItemAssignees from './work_item_assignees.vue';
|
|
||||||
import WorkItemLabels from './work_item_labels.vue';
|
|
||||||
import WorkItemMilestone from './work_item_milestone.vue';
|
|
||||||
import WorkItemNotes from './work_item_notes.vue';
|
import WorkItemNotes from './work_item_notes.vue';
|
||||||
import WorkItemDetailModal from './work_item_detail_modal.vue';
|
import WorkItemDetailModal from './work_item_detail_modal.vue';
|
||||||
|
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18n,
|
i18n,
|
||||||
|
|
@ -74,23 +63,14 @@ export default {
|
||||||
GlSkeletonLoader,
|
GlSkeletonLoader,
|
||||||
GlIcon,
|
GlIcon,
|
||||||
GlEmptyState,
|
GlEmptyState,
|
||||||
WorkItemAssignees,
|
|
||||||
WorkItemActions,
|
WorkItemActions,
|
||||||
WorkItemTodos,
|
WorkItemTodos,
|
||||||
WorkItemCreatedUpdated,
|
WorkItemCreatedUpdated,
|
||||||
WorkItemDescription,
|
WorkItemDescription,
|
||||||
WorkItemAwardEmoji,
|
WorkItemAwardEmoji,
|
||||||
WorkItemDueDate,
|
|
||||||
WorkItemLabels,
|
|
||||||
WorkItemTitle,
|
WorkItemTitle,
|
||||||
WorkItemState,
|
WorkItemAttributesWrapper,
|
||||||
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
|
|
||||||
WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'),
|
|
||||||
WorkItemTypeIcon,
|
WorkItemTypeIcon,
|
||||||
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
|
|
||||||
WorkItemHealthStatus: () =>
|
|
||||||
import('ee_component/work_items/components/work_item_health_status.vue'),
|
|
||||||
WorkItemMilestone,
|
|
||||||
WorkItemTree,
|
WorkItemTree,
|
||||||
WorkItemNotes,
|
WorkItemNotes,
|
||||||
WorkItemDetailModal,
|
WorkItemDetailModal,
|
||||||
|
|
@ -256,33 +236,12 @@ export default {
|
||||||
workItemAssignees() {
|
workItemAssignees() {
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
|
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
|
||||||
},
|
},
|
||||||
workItemLabels() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_LABELS);
|
|
||||||
},
|
|
||||||
workItemDueDate() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
|
|
||||||
},
|
|
||||||
workItemWeight() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
|
||||||
},
|
|
||||||
workItemProgress() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_PROGRESS);
|
|
||||||
},
|
|
||||||
workItemAwardEmoji() {
|
workItemAwardEmoji() {
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI);
|
return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI);
|
||||||
},
|
},
|
||||||
workItemHierarchy() {
|
workItemHierarchy() {
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY);
|
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY);
|
||||||
},
|
},
|
||||||
workItemIteration() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
|
|
||||||
},
|
|
||||||
workItemHealthStatus() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS);
|
|
||||||
},
|
|
||||||
workItemMilestone() {
|
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_MILESTONE);
|
|
||||||
},
|
|
||||||
workItemNotes() {
|
workItemNotes() {
|
||||||
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
|
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
|
||||||
},
|
},
|
||||||
|
|
@ -511,83 +470,9 @@ export default {
|
||||||
@error="updateError = $event"
|
@error="updateError = $event"
|
||||||
/>
|
/>
|
||||||
<work-item-created-updated :work-item-iid="workItemIid" />
|
<work-item-created-updated :work-item-iid="workItemIid" />
|
||||||
<work-item-state
|
<work-item-attributes-wrapper
|
||||||
:work-item="workItem"
|
:work-item="workItem"
|
||||||
:work-item-parent-id="workItemParentId"
|
:work-item-parent-id="workItemParentId"
|
||||||
:can-update="canUpdate"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-assignees
|
|
||||||
v-if="workItemAssignees"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:assignees="workItemAssignees.assignees.nodes"
|
|
||||||
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
:can-invite-members="workItemAssignees.canInviteMembers"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-labels
|
|
||||||
v-if="workItemLabels"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-iid="workItem.iid"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-due-date
|
|
||||||
v-if="workItemDueDate"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:due-date="workItemDueDate.dueDate"
|
|
||||||
:start-date="workItemDueDate.startDate"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-milestone
|
|
||||||
v-if="workItemMilestone"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-milestone="workItemMilestone.milestone"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-weight
|
|
||||||
v-if="workItemWeight"
|
|
||||||
class="gl-mb-5"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:weight="workItemWeight.weight"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-iid="workItem.iid"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-progress
|
|
||||||
v-if="workItemProgress"
|
|
||||||
class="gl-mb-5"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:progress="workItemProgress.progress"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-iteration
|
|
||||||
v-if="workItemIteration"
|
|
||||||
class="gl-mb-5"
|
|
||||||
:iteration="workItemIteration.iteration"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-iid="workItem.iid"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
@error="updateError = $event"
|
|
||||||
/>
|
|
||||||
<work-item-health-status
|
|
||||||
v-if="workItemHealthStatus"
|
|
||||||
class="gl-mb-5"
|
|
||||||
:health-status="workItemHealthStatus.healthStatus"
|
|
||||||
:can-update="canUpdate"
|
|
||||||
:work-item-id="workItem.id"
|
|
||||||
:work-item-iid="workItem.iid"
|
|
||||||
:work-item-type="workItemType"
|
|
||||||
@error="updateError = $event"
|
@error="updateError = $event"
|
||||||
/>
|
/>
|
||||||
<work-item-description
|
<work-item-description
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
- else
|
- else
|
||||||
= _("(removed)")
|
= _("(removed)")
|
||||||
|
|
||||||
.todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start.gl-lg-align-items-center
|
.todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start
|
||||||
.todo-avatar.gl-display-none.gl-sm-display-inline-block
|
.todo-avatar.gl-display-none.gl-sm-display-inline-block
|
||||||
= author_avatar(todo, size: 24)
|
= author_avatar(todo, size: 24)
|
||||||
.todo-note
|
.todo-note
|
||||||
|
|
@ -47,6 +47,8 @@
|
||||||
%span.action-description<
|
%span.action-description<
|
||||||
= first_line_in_markdown(todo, :body, 125, is_todo: true, project: todo.project, group: todo.group)
|
= first_line_in_markdown(todo, :body, 125, is_todo: true, project: todo.project, group: todo.group)
|
||||||
|
|
||||||
|
= render_if_exists "dashboard/todos/diff_summary", local_assigns: { todo: todo }
|
||||||
|
|
||||||
.todo-timestamp.gl-white-space-nowrap.gl-sm-ml-3.gl-mt-2.gl-mb-2.gl-sm-my-0.gl-px-2.gl-sm-px-0
|
.todo-timestamp.gl-white-space-nowrap.gl-sm-ml-3.gl-mt-2.gl-mb-2.gl-sm-my-0.gl-px-2.gl-sm-px-0
|
||||||
%span.todo-timestamp.gl-font-sm.gl-text-secondary
|
%span.todo-timestamp.gl-font-sm.gl-text-secondary
|
||||||
= todo_due_date(todo)
|
= todo_due_date(todo)
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,4 @@
|
||||||
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
|
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
|
||||||
%p.form-text.text-muted
|
%p.form-text.text-muted
|
||||||
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
|
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
|
||||||
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration.").html_safe % { link_to_help: link_to_help }
|
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration within seven days.").html_safe % { link_to_help: link_to_help }
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ feature_categories:
|
||||||
description: Define ownership of namespaces, projects, and users by organizations
|
description: Define ownership of namespaces, projects, and users by organizations
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119421
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119421
|
||||||
milestone: '16.0'
|
milestone: '16.0'
|
||||||
gitlab_schema: gitlab_main
|
gitlab_schema: gitlab_main_clusterwide
|
||||||
|
|
|
||||||
|
|
@ -5239,7 +5239,7 @@ msgstr ""
|
||||||
msgid "Analytics"
|
msgid "Analytics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Add to Dashboard"
|
msgid "Analytics|A visualization with that name already exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Add visualizations"
|
msgid "Analytics|Add visualizations"
|
||||||
|
|
@ -5311,9 +5311,15 @@ msgstr ""
|
||||||
msgid "Analytics|Edit"
|
msgid "Analytics|Edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Enter a visualization name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Error while saving dashboard"
|
msgid "Analytics|Error while saving dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Error while saving visualization."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Host"
|
msgid "Analytics|Host"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -5323,7 +5329,7 @@ msgstr ""
|
||||||
msgid "Analytics|Line Chart"
|
msgid "Analytics|Line Chart"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|New Analytics Visualization Title"
|
msgid "Analytics|New analytics visualization name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|New dashboard"
|
msgid "Analytics|New dashboard"
|
||||||
|
|
@ -5362,6 +5368,18 @@ msgstr ""
|
||||||
msgid "Analytics|Save"
|
msgid "Analytics|Save"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Save and add to Dashboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Save new visualization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Select a measurement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Select a visualization type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Single Statistic"
|
msgid "Analytics|Single Statistic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -5374,6 +5392,9 @@ msgstr ""
|
||||||
msgid "Analytics|Updating dashboard %{dashboardId}"
|
msgid "Analytics|Updating dashboard %{dashboardId}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Updating visualization %{visualizationName}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analytics|Users"
|
msgid "Analytics|Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -5392,6 +5413,9 @@ msgstr ""
|
||||||
msgid "Analytics|Visualization Type"
|
msgid "Analytics|Visualization Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Analytics|Visualization was saved successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Analyze your dependencies for known vulnerabilities."
|
msgid "Analyze your dependencies for known vulnerabilities."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -16424,7 +16448,7 @@ msgstr ""
|
||||||
msgid "DomainVerification|The following domains are configured for projects in this group. Users with email addresses that match a verified domain do not need to confirm their account."
|
msgid "DomainVerification|The following domains are configured for projects in this group. Users with email addresses that match a verified domain do not need to confirm their account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DomainVerification|To verify ownership of your domain, add the above key to a TXT record within your DNS configuration. %{link_to_help}"
|
msgid "DomainVerification|To verify ownership of your domain, add the above key to a TXT record within your DNS configuration within seven days. %{link_to_help}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Don't have a group?"
|
msgid "Don't have a group?"
|
||||||
|
|
@ -44775,6 +44799,9 @@ msgstr ""
|
||||||
msgid "Summary comment (optional)"
|
msgid "Summary comment (optional)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Summary generated by AI"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Sun"
|
msgid "Sun"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -47737,7 +47764,7 @@ msgstr ""
|
||||||
msgid "To"
|
msgid "To"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration."
|
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration within seven days."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "To Do"
|
msgid "To Do"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
|
||||||
|
import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
|
||||||
|
import WorkItemState from '~/work_items/components/work_item_state.vue';
|
||||||
|
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
|
||||||
|
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
|
||||||
|
|
||||||
|
import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
|
||||||
|
import { workItemResponseFactory } from '../mock_data';
|
||||||
|
|
||||||
|
describe('WorkItemAttributesWrapper component', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true });
|
||||||
|
|
||||||
|
const findWorkItemState = () => wrapper.findComponent(WorkItemState);
|
||||||
|
const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate);
|
||||||
|
const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees);
|
||||||
|
const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels);
|
||||||
|
const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone);
|
||||||
|
|
||||||
|
const createComponent = ({ workItem = workItemQueryResponse.data.workItem } = {}) => {
|
||||||
|
wrapper = shallowMount(WorkItemAttributesWrapper, {
|
||||||
|
propsData: {
|
||||||
|
workItem,
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
hasIssueWeightsFeature: true,
|
||||||
|
hasIterationsFeature: true,
|
||||||
|
hasOkrsFeature: true,
|
||||||
|
hasIssuableHealthStatusFeature: true,
|
||||||
|
projectNamespace: 'namespace',
|
||||||
|
fullPath: 'group/project',
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
WorkItemWeight: true,
|
||||||
|
WorkItemIteration: true,
|
||||||
|
WorkItemHealthStatus: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('work item state', () => {
|
||||||
|
it('renders the work item state', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findWorkItemState().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('assignees widget', () => {
|
||||||
|
it('renders assignees component when widget is returned from the API', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findWorkItemAssignees().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render assignees component when widget is not returned from the API', () => {
|
||||||
|
createComponent({
|
||||||
|
workItem: workItemResponseFactory({ assigneesWidgetPresent: false }).data.workItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findWorkItemAssignees().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('labels widget', () => {
|
||||||
|
it.each`
|
||||||
|
description | labelsWidgetPresent | exists
|
||||||
|
${'renders when widget is returned from API'} | ${true} | ${true}
|
||||||
|
${'does not render when widget is not returned from API'} | ${false} | ${false}
|
||||||
|
`('$description', ({ labelsWidgetPresent, exists }) => {
|
||||||
|
const response = workItemResponseFactory({ labelsWidgetPresent });
|
||||||
|
createComponent({ workItem: response.data.workItem });
|
||||||
|
|
||||||
|
expect(findWorkItemLabels().exists()).toBe(exists);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dates widget', () => {
|
||||||
|
describe.each`
|
||||||
|
description | datesWidgetPresent | exists
|
||||||
|
${'when widget is returned from API'} | ${true} | ${true}
|
||||||
|
${'when widget is not returned from API'} | ${false} | ${false}
|
||||||
|
`('$description', ({ datesWidgetPresent, exists }) => {
|
||||||
|
it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, () => {
|
||||||
|
const response = workItemResponseFactory({ datesWidgetPresent });
|
||||||
|
createComponent({ workItem: response.data.workItem });
|
||||||
|
|
||||||
|
expect(findWorkItemDueDate().exists()).toBe(exists);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('milestone widget', () => {
|
||||||
|
it.each`
|
||||||
|
description | milestoneWidgetPresent | exists
|
||||||
|
${'renders when widget is returned from API'} | ${true} | ${true}
|
||||||
|
${'does not render when widget is not returned from API'} | ${false} | ${false}
|
||||||
|
`('$description', ({ milestoneWidgetPresent, exists }) => {
|
||||||
|
const response = workItemResponseFactory({ milestoneWidgetPresent });
|
||||||
|
createComponent({ workItem: response.data.workItem });
|
||||||
|
|
||||||
|
expect(findWorkItemMilestone().exists()).toBe(exists);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -18,12 +18,8 @@ import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
|
||||||
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
|
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
|
||||||
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
|
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
|
||||||
import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue';
|
import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue';
|
||||||
import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
|
import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
|
||||||
import WorkItemState from '~/work_items/components/work_item_state.vue';
|
|
||||||
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
|
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
|
||||||
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
|
|
||||||
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
|
|
||||||
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
|
|
||||||
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
|
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
|
||||||
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
|
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
|
||||||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||||
|
|
@ -69,12 +65,8 @@ describe('WorkItemDetail component', () => {
|
||||||
const findWorkItemActions = () => wrapper.findComponent(WorkItemActions);
|
const findWorkItemActions = () => wrapper.findComponent(WorkItemActions);
|
||||||
const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
|
const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
|
||||||
const findCreatedUpdated = () => wrapper.findComponent(WorkItemCreatedUpdated);
|
const findCreatedUpdated = () => wrapper.findComponent(WorkItemCreatedUpdated);
|
||||||
const findWorkItemState = () => wrapper.findComponent(WorkItemState);
|
|
||||||
const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription);
|
const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription);
|
||||||
const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate);
|
const findWorkItemAttributesWrapper = () => wrapper.findComponent(WorkItemAttributesWrapper);
|
||||||
const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees);
|
|
||||||
const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels);
|
|
||||||
const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone);
|
|
||||||
const findParent = () => wrapper.find('[data-testid="work-item-parent"]');
|
const findParent = () => wrapper.find('[data-testid="work-item-parent"]');
|
||||||
const findParentButton = () => findParent().findComponent(GlButton);
|
const findParentButton = () => findParent().findComponent(GlButton);
|
||||||
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
|
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
|
||||||
|
|
@ -168,7 +160,6 @@ describe('WorkItemDetail component', () => {
|
||||||
|
|
||||||
it('renders skeleton loader', () => {
|
it('renders skeleton loader', () => {
|
||||||
expect(findSkeleton().exists()).toBe(true);
|
expect(findSkeleton().exists()).toBe(true);
|
||||||
expect(findWorkItemState().exists()).toBe(false);
|
|
||||||
expect(findWorkItemTitle().exists()).toBe(false);
|
expect(findWorkItemTitle().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -181,7 +172,6 @@ describe('WorkItemDetail component', () => {
|
||||||
|
|
||||||
it('does not render skeleton', () => {
|
it('does not render skeleton', () => {
|
||||||
expect(findSkeleton().exists()).toBe(false);
|
expect(findSkeleton().exists()).toBe(false);
|
||||||
expect(findWorkItemState().exists()).toBe(true);
|
|
||||||
expect(findWorkItemTitle().exists()).toBe(true);
|
expect(findWorkItemTitle().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -480,83 +470,6 @@ describe('WorkItemDetail component', () => {
|
||||||
|
|
||||||
expect(findAlert().text()).toBe(updateError);
|
expect(findAlert().text()).toBe(updateError);
|
||||||
});
|
});
|
||||||
describe('assignees widget', () => {
|
|
||||||
it('renders assignees component when widget is returned from the API', async () => {
|
|
||||||
createComponent();
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findWorkItemAssignees().exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render assignees component when widget is not returned from the API', async () => {
|
|
||||||
createComponent({
|
|
||||||
handler: jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValue(workItemByIidResponseFactory({ assigneesWidgetPresent: false })),
|
|
||||||
});
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findWorkItemAssignees().exists()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('labels widget', () => {
|
|
||||||
it.each`
|
|
||||||
description | labelsWidgetPresent | exists
|
|
||||||
${'renders when widget is returned from API'} | ${true} | ${true}
|
|
||||||
${'does not render when widget is not returned from API'} | ${false} | ${false}
|
|
||||||
`('$description', async ({ labelsWidgetPresent, exists }) => {
|
|
||||||
const response = workItemByIidResponseFactory({ labelsWidgetPresent });
|
|
||||||
const handler = jest.fn().mockResolvedValue(response);
|
|
||||||
createComponent({ handler });
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findWorkItemLabels().exists()).toBe(exists);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('dates widget', () => {
|
|
||||||
describe.each`
|
|
||||||
description | datesWidgetPresent | exists
|
|
||||||
${'when widget is returned from API'} | ${true} | ${true}
|
|
||||||
${'when widget is not returned from API'} | ${false} | ${false}
|
|
||||||
`('$description', ({ datesWidgetPresent, exists }) => {
|
|
||||||
it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => {
|
|
||||||
const response = workItemByIidResponseFactory({ datesWidgetPresent });
|
|
||||||
const handler = jest.fn().mockResolvedValue(response);
|
|
||||||
createComponent({ handler });
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findWorkItemDueDate().exists()).toBe(exists);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows an error message when it emits an `error` event', async () => {
|
|
||||||
createComponent();
|
|
||||||
await waitForPromises();
|
|
||||||
const updateError = 'Failed to update';
|
|
||||||
|
|
||||||
findWorkItemDueDate().vm.$emit('error', updateError);
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findAlert().text()).toBe(updateError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('milestone widget', () => {
|
|
||||||
it.each`
|
|
||||||
description | milestoneWidgetPresent | exists
|
|
||||||
${'renders when widget is returned from API'} | ${true} | ${true}
|
|
||||||
${'does not render when widget is not returned from API'} | ${false} | ${false}
|
|
||||||
`('$description', async ({ milestoneWidgetPresent, exists }) => {
|
|
||||||
const response = workItemByIidResponseFactory({ milestoneWidgetPresent });
|
|
||||||
const handler = jest.fn().mockResolvedValue(response);
|
|
||||||
createComponent({ handler });
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findWorkItemMilestone().exists()).toBe(exists);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls the work item query', async () => {
|
it('calls the work item query', async () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
@ -713,4 +626,24 @@ describe('WorkItemDetail component', () => {
|
||||||
expect(findWorkItemTodos().exists()).toBe(false);
|
expect(findWorkItemTodos().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('work item attributes wrapper', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
createComponent();
|
||||||
|
await waitForPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the work item attributes wrapper', () => {
|
||||||
|
expect(findWorkItemAttributesWrapper().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error message when it emits an `error` event', async () => {
|
||||||
|
const updateError = 'Failed to update';
|
||||||
|
|
||||||
|
findWorkItemAttributesWrapper().vm.$emit('error', updateError);
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(findAlert().text()).toBe(updateError);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue