Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/sidebar/components')
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue22
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/user_name_with_status.vue23
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue22
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue44
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_editable_item.vue8
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/report.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue195
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/todo.vue2
18 files changed, 321 insertions, 46 deletions
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index adb573db652..4b3b22f6db3 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -47,7 +47,7 @@ export default {
<template>
<div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
{{ assigneeTitle }}
- <gl-loading-icon v-if="loading" inline class="align-bottom" />
+ <gl-loading-icon v-if="loading" size="sm" inline class="align-bottom" />
<a
v-if="editable"
class="js-sidebar-dropdown-toggle edit-link float-right"
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index 9840aa4ed66..c6877226b7d 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -1,6 +1,6 @@
<script>
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import createFlash from '~/flash';
import { __ } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
@@ -113,7 +113,9 @@ export default {
})
.catch(() => {
this.loading = false;
- return new Flash(__('Error occurred when saving assignees'));
+ return createFlash({
+ message: __('Error occurred when saving assignees'),
+ });
});
},
exposeAvailabilityStatus(users) {
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
index d9a974202a3..1dd05d3886e 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
@@ -3,6 +3,7 @@ import { GlDropdownItem } from '@gitlab/ui';
import { cloneDeep } from 'lodash';
import Vue from 'vue';
import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants';
import { __, n__ } from '~/locale';
import SidebarAssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
@@ -80,6 +81,8 @@ export default {
selected: [],
isSettingAssignees: false,
isDirty: false,
+ oldIid: null,
+ oldSelected: null,
};
},
apollo: {
@@ -142,6 +145,14 @@ export default {
return this.currentUser.username !== undefined;
},
},
+ watch: {
+ iid(_, oldIid) {
+ if (this.isDirty) {
+ this.oldIid = oldIid;
+ this.oldSelected = this.selected;
+ }
+ },
+ },
created() {
assigneesWidget.updateAssignees = this.updateAssignees;
},
@@ -157,10 +168,14 @@ export default {
variables: {
...this.queryVariables,
assigneeUsernames,
+ iid: this.oldIid || this.iid,
},
})
.then(({ data }) => {
- this.$emit('assignees-updated', data.issuableSetAssignees.issuable.assignees.nodes);
+ this.$emit('assignees-updated', {
+ id: getIdFromGraphQLId(data.issuableSetAssignees.issuable.id),
+ assignees: data.issuableSetAssignees.issuable.assignees.nodes,
+ });
return data;
})
.catch(() => {
@@ -176,7 +191,10 @@ export default {
saveAssignees() {
if (this.isDirty) {
this.isDirty = false;
- this.updateAssignees(this.selected.map(({ username }) => username));
+ const usernames = this.oldSelected || this.selected;
+ this.updateAssignees(usernames.map(({ username }) => username));
+ this.oldIid = null;
+ this.oldSelected = null;
}
this.$el.dispatchEvent(hideDropdownEvent);
},
diff --git a/app/assets/javascripts/sidebar/components/assignees/user_name_with_status.vue b/app/assets/javascripts/sidebar/components/assignees/user_name_with_status.vue
index 41b3b6c9a45..bed84dc5706 100644
--- a/app/assets/javascripts/sidebar/components/assignees/user_name_with_status.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/user_name_with_status.vue
@@ -22,8 +22,16 @@ export default {
required: false,
default: '',
},
+ pronouns: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
+ hasPronouns() {
+ return this.pronouns !== null && this.pronouns.trim() !== '';
+ },
isBusy() {
return isUserBusy(this.availability);
},
@@ -32,9 +40,18 @@ export default {
</script>
<template>
<span :class="containerClasses">
- <gl-sprintf v-if="isBusy" :message="s__('UserAvailability|%{author} (Busy)')">
- <template #author>{{ name }}</template>
+ <gl-sprintf :message="s__('UserAvailability|%{author} %{spanStart}(Busy)%{spanEnd}')">
+ <template #author
+ >{{ name }}
+ <span v-if="hasPronouns" class="gl-text-gray-500 gl-font-sm gl-font-weight-normal"
+ >({{ pronouns }})</span
+ ></template
+ >
+ <template #span="{ content }"
+ ><span v-if="isBusy" class="gl-text-gray-500 gl-font-sm gl-font-weight-normal">{{
+ content
+ }}</span>
+ </template>
</gl-sprintf>
- <template v-else>{{ name }}</template>
</span>
</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue
index 372368707af..dc0f2b54a7b 100644
--- a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue
@@ -4,7 +4,7 @@ import Vue from 'vue';
import createFlash from '~/flash';
import { __, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
-import { confidentialityQueries } from '~/sidebar/constants';
+import { confidentialityQueries, Tracking } from '~/sidebar/constants';
import SidebarConfidentialityContent from './sidebar_confidentiality_content.vue';
import SidebarConfidentialityForm from './sidebar_confidentiality_form.vue';
@@ -18,8 +18,8 @@ const hideDropdownEvent = new CustomEvent('hiddenGlDropdown', {
export default {
tracking: {
- event: 'click_edit_button',
- label: 'right_sidebar',
+ event: Tracking.editEvent,
+ label: Tracking.rightSidebarLabel,
property: 'confidentiality',
},
components: {
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
index c3dfa5f8b14..1ff24dec884 100644
--- a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
@@ -5,7 +5,13 @@ import { IssuableType } from '~/issue_show/constants';
import { dateInWords, formatDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { __, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
-import { dateFields, dateTypes, dueDateQueries, startDateQueries } from '~/sidebar/constants';
+import {
+ dateFields,
+ dateTypes,
+ dueDateQueries,
+ startDateQueries,
+ Tracking,
+} from '~/sidebar/constants';
import SidebarFormattedDate from './sidebar_formatted_date.vue';
import SidebarInheritDate from './sidebar_inherit_date.vue';
@@ -15,8 +21,8 @@ const hideDropdownEvent = new CustomEvent('hiddenGlDropdown', {
export default {
tracking: {
- event: 'click_edit_button',
- label: 'right_sidebar',
+ event: Tracking.editEvent,
+ label: Tracking.rightSidebarLabel,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -149,6 +155,9 @@ export default {
},
},
methods: {
+ epicDatePopoverEl() {
+ return this.$refs?.epicDatePopover?.$el;
+ },
closeForm() {
this.$refs.editable.collapse();
this.$el.dispatchEvent(hideDropdownEvent);
@@ -249,12 +258,7 @@ export default {
:aria-label="$options.i18n.help"
data-testid="inherit-date-popover"
/>
- <gl-popover
- :target="() => $refs.epicDatePopover.$el"
- triggers="focus"
- placement="left"
- boundary="viewport"
- >
+ <gl-popover :target="epicDatePopoverEl" triggers="focus" placement="left" boundary="viewport">
<p>{{ $options.i18n.dateHelpValidMessage }}</p>
<gl-link :href="$options.dateHelpUrl" target="_blank">{{
$options.i18n.learnMore
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
index c3f31a3d220..42d2e456a07 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -2,7 +2,7 @@
import { GlButton } from '@gitlab/ui';
import $ from 'jquery';
import { mapActions } from 'vuex';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import createFlash from '~/flash';
import { __, sprintf } from '../../../locale';
import eventHub from '../../event_hub';
@@ -52,7 +52,9 @@ export default {
const flashMessage = __(
'Something went wrong trying to change the locked state of this %{issuableDisplayName}',
);
- Flash(sprintf(flashMessage, { issuableDisplayName: this.issuableDisplayName }));
+ createFlash({
+ message: sprintf(flashMessage, { issuableDisplayName: this.issuableDisplayName }),
+ });
})
.finally(() => {
this.closeForm();
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index e85e416881c..650aa603f18 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -92,11 +92,11 @@ export default {
@click="onClickCollapsedIcon"
>
<gl-icon name="users" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<span v-else data-testid="collapsed-count"> {{ participantCount }} </span>
</div>
<div v-if="showParticipantLabel" class="title hide-collapsed gl-mb-2">
- <gl-loading-icon v-if="loading" :inline="true" />
+ <gl-loading-icon v-if="loading" size="sm" :inline="true" />
{{ participantLabel }}
</div>
<div class="participants-list hide-collapsed">
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
index 88c0b18ccc7..295027186cc 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
@@ -35,7 +35,7 @@ export default {
<template>
<div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
{{ reviewerTitle }}
- <gl-loading-icon v-if="loading" inline class="align-bottom" />
+ <gl-loading-icon v-if="loading" size="sm" inline class="align-bottom" />
<a
v-if="editable"
class="js-sidebar-dropdown-toggle edit-link float-right"
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index c0bd54c60da..e414aaf719b 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -2,7 +2,7 @@
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import createFlash from '~/flash';
import { __ } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
@@ -80,7 +80,9 @@ export default {
})
.catch(() => {
this.loading = false;
- return new Flash(__('Error occurred when saving reviewers'));
+ return createFlash({
+ message: __('Error occurred when saving reviewers'),
+ });
});
},
requestReview(data) {
diff --git a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
index 592cfea5e32..fdf63c23552 100644
--- a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
+++ b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
@@ -181,7 +181,7 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
- <gl-loading-icon v-if="isUpdating" :inline="true" />
+ <gl-loading-icon v-if="isUpdating" size="sm" :inline="true" />
<severity-token v-else-if="!isDropdownShowing" :severity="selectedItem" />
</div>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index c80ccc928b3..2e00a23de7c 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -16,11 +16,13 @@ import { IssuableType } from '~/issue_show/constants';
import { __, s__, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import {
+ Tracking,
IssuableAttributeState,
IssuableAttributeType,
issuableAttributesQueries,
noAttributeId,
-} from '../constants';
+ defaultEpicSort,
+} from '~/sidebar/constants';
export default {
noAttributeId,
@@ -28,6 +30,7 @@ export default {
issuableAttributesQueries,
i18n: {
[IssuableAttributeType.Milestone]: __('Milestone'),
+ expired: __('(expired)'),
none: __('None'),
},
directives: {
@@ -73,9 +76,14 @@ export default {
type: String,
required: true,
validator(value) {
- return value === IssuableType.Issue;
+ return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
},
},
+ icon: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
apollo: {
currentAttribute: {
@@ -117,7 +125,9 @@ export default {
return {
fullPath: this.attrWorkspacePath,
title: this.searchTerm,
+ in: this.searchTerm && this.issuableAttribute === IssuableType.Epic ? 'TITLE' : undefined,
state: this.$options.IssuableAttributeState[this.issuableAttribute],
+ sort: this.issuableAttribute === IssuableType.Epic ? defaultEpicSort : null,
};
},
update(data) {
@@ -140,8 +150,8 @@ export default {
currentAttribute: null,
attributesList: [],
tracking: {
- label: 'right_sidebar',
- event: 'click_edit_button',
+ event: Tracking.editEvent,
+ label: Tracking.rightSidebarLabel,
property: this.issuableAttribute,
},
};
@@ -170,6 +180,9 @@ export default {
attributeTypeTitle() {
return this.$options.i18n[this.issuableAttribute];
},
+ attributeTypeIcon() {
+ return this.icon || this.issuableAttribute;
+ },
i18n() {
return {
noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), {
@@ -222,7 +235,8 @@ export default {
variables: {
fullPath: this.workspacePath,
attributeId:
- this.issuableAttribute === IssuableAttributeType.Milestone
+ this.issuableAttribute === IssuableAttributeType.Milestone &&
+ this.issuableType === IssuableType.Issue
? getIdFromGraphQLId(attributeId)
: attributeId,
iid: this.iid,
@@ -253,6 +267,11 @@ export default {
attributeId === this.currentAttribute?.id || (!this.currentAttribute?.id && !attributeId)
);
},
+ isAttributeOverdue(attribute) {
+ return this.issuableAttribute === IssuableAttributeType.Milestone
+ ? attribute?.expired
+ : false;
+ },
showDropdown() {
this.$refs.newDropdown.show();
},
@@ -282,8 +301,10 @@ export default {
>
<template #collapsed>
<div v-if="isClassicSidebar" v-gl-tooltip class="sidebar-collapsed-icon">
- <gl-icon :size="16" :aria-label="attributeTypeTitle" :name="issuableAttribute" />
- <span class="collapse-truncated-title">{{ attributeTitle }}</span>
+ <gl-icon :size="16" :aria-label="attributeTypeTitle" :name="attributeTypeIcon" />
+ <span class="collapse-truncated-title">
+ {{ attributeTitle }}
+ </span>
</div>
<div
:data-testid="`select-${issuableAttribute}`"
@@ -300,8 +321,13 @@ export default {
:attributeUrl="attributeUrl"
:currentAttribute="currentAttribute"
>
- <gl-link class="gl-text-gray-900! gl-font-weight-bold" :href="attributeUrl">
+ <gl-link
+ class="gl-text-gray-900! gl-font-weight-bold"
+ :href="attributeUrl"
+ :data-qa-selector="`${issuableAttribute}_link`"
+ >
{{ attributeTitle }}
+ <span v-if="isAttributeOverdue(currentAttribute)">{{ $options.i18n.expired }}</span>
</gl-link>
</slot>
</div>
@@ -328,6 +354,7 @@ export default {
<gl-dropdown-divider />
<gl-loading-icon
v-if="$apollo.queries.attributesList.loading"
+ size="sm"
class="gl-py-4"
data-testid="loading-icon-dropdown"
/>
@@ -351,6 +378,7 @@ export default {
@click="updateAttribute(attrItem.id)"
>
{{ attrItem.title }}
+ <span v-if="isAttributeOverdue(attrItem)">{{ $options.i18n.expired }}</span>
</gl-dropdown-item>
</slot>
</template>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
index 825d7ff5841..7c496cc422a 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
@@ -117,9 +117,15 @@ export default {
{{ title }}
</span>
<slot name="title-extra"></slot>
- <gl-loading-icon v-if="loading || initialLoading" inline class="gl-ml-2 hide-collapsed" />
+ <gl-loading-icon
+ v-if="loading || initialLoading"
+ size="sm"
+ inline
+ class="gl-ml-2 hide-collapsed"
+ />
<gl-loading-icon
v-if="loading && isClassicSidebar"
+ size="sm"
inline
class="gl-mx-auto gl-my-0 hide-expanded"
/>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
index e97742a1339..bc7e377a966 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
@@ -2,17 +2,18 @@
import { GlIcon, GlLoadingIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
+import { isLoggedIn } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
-import { subscribedQueries } from '~/sidebar/constants';
+import { subscribedQueries, Tracking } from '~/sidebar/constants';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
export default {
tracking: {
- event: 'click_edit_button',
- label: 'right_sidebar',
+ event: Tracking.editEvent,
+ label: Tracking.rightSidebarLabel,
property: 'subscriptions',
},
directives: {
@@ -102,7 +103,7 @@ export default {
});
},
isLoggedIn() {
- return Boolean(gon.current_user_id);
+ return isLoggedIn();
},
canSubscribe() {
return this.emailsDisabled || !this.isLoggedIn;
@@ -195,7 +196,7 @@ export default {
class="sidebar-collapsed-icon"
@click="toggleSubscribed"
>
- <gl-loading-icon v-if="isLoading" class="sidebar-item-icon is-active" />
+ <gl-loading-icon v-if="isLoading" size="sm" class="sidebar-item-icon is-active" />
<gl-icon v-else :name="notificationIcon" :size="16" class="sidebar-item-icon is-active" />
</span>
<div v-show="emailsDisabled" class="gl-mt-3 hide-collapsed gl-text-gray-500">
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
index f91a78b7f1d..8a14998910b 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlTable } from '@gitlab/ui';
import createFlash from '~/flash';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
@@ -52,8 +53,7 @@ export default {
return this.issuableType === 'issue';
},
getGraphQLEntityType() {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- return this.isIssue() ? 'Issue' : 'MergeRequest';
+ return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST;
},
extractTimelogs(data) {
const timelogs = data?.issuable?.timelogs?.nodes || [];
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 87ddbbf256a..9a9d03353dc 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -200,7 +200,7 @@ export default {
/>
<div class="hide-collapsed gl-line-height-20 gl-text-gray-900">
{{ __('Time tracking') }}
- <gl-loading-icon v-if="isTimeTrackingInfoLoading" inline />
+ <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" inline />
<div
v-if="!showHelpState"
data-testid="helpButton"
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
new file mode 100644
index 00000000000..a9c4203af22
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
@@ -0,0 +1,195 @@
+<script>
+import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { produce } from 'immer';
+import createFlash from '~/flash';
+import { __, sprintf } from '~/locale';
+import { todoQueries, TodoMutationTypes, todoMutations } from '~/sidebar/constants';
+import { todoLabel } from '~/vue_shared/components/sidebar/todo_toggle//utils';
+import TodoButton from '~/vue_shared/components/sidebar/todo_toggle/todo_button.vue';
+
+export default {
+ components: {
+ GlButton,
+ GlIcon,
+ TodoButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: {
+ isClassicSidebar: {
+ default: false,
+ },
+ },
+ props: {
+ issuableId: {
+ type: String,
+ required: true,
+ },
+ issuableIid: {
+ type: String,
+ required: true,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ issuableType: {
+ required: true,
+ type: String,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ };
+ },
+ apollo: {
+ todoId: {
+ query() {
+ return todoQueries[this.issuableType].query;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ iid: String(this.issuableIid),
+ };
+ },
+ update(data) {
+ return data.workspace?.issuable?.currentUserTodos.nodes[0]?.id;
+ },
+ result({ data }) {
+ const currentUserTodos = data.workspace?.issuable?.currentUserTodos?.nodes ?? [];
+ this.todoId = currentUserTodos[0]?.id;
+ this.$emit('todoUpdated', currentUserTodos.length > 0);
+ },
+ error() {
+ createFlash({
+ message: sprintf(__('Something went wrong while setting %{issuableType} to-do item.'), {
+ issuableType: this.issuableType,
+ }),
+ });
+ },
+ },
+ },
+ computed: {
+ todoIdQuery() {
+ return todoQueries[this.issuableType].query;
+ },
+ todoIdQueryVariables() {
+ return {
+ fullPath: this.fullPath,
+ iid: String(this.issuableIid),
+ };
+ },
+ isLoading() {
+ return this.$apollo.queries?.todoId?.loading || this.loading;
+ },
+ hasTodo() {
+ return Boolean(this.todoId);
+ },
+ todoMutationType() {
+ if (this.hasTodo) {
+ return TodoMutationTypes.MarkDone;
+ }
+ return TodoMutationTypes.Create;
+ },
+ collapsedButtonIcon() {
+ return this.hasTodo ? 'todo-done' : 'todo-add';
+ },
+ tootltipTitle() {
+ return todoLabel(this.hasTodo);
+ },
+ },
+ methods: {
+ toggleTodo() {
+ this.loading = true;
+ this.$apollo
+ .mutate({
+ mutation: todoMutations[this.todoMutationType],
+ variables: {
+ input: {
+ targetId: !this.hasTodo ? this.issuableId : undefined,
+ id: this.hasTodo ? this.todoId : undefined,
+ },
+ },
+ update: (
+ store,
+ {
+ data: {
+ todoMutation: { todo },
+ },
+ },
+ ) => {
+ const queryProps = {
+ query: this.todoIdQuery,
+ variables: this.todoIdQueryVariables,
+ };
+
+ const sourceData = store.readQuery(queryProps);
+ const data = produce(sourceData, (draftState) => {
+ draftState.workspace.issuable.currentUserTodos.nodes = this.hasTodo ? [] : [todo];
+ });
+ store.writeQuery({
+ data,
+ ...queryProps,
+ });
+ },
+ })
+ .then(
+ ({
+ data: {
+ todoMutation: { errors },
+ },
+ }) => {
+ if (errors.length) {
+ createFlash({
+ message: errors[0],
+ });
+ }
+ },
+ )
+ .catch(() => {
+ createFlash({
+ message: sprintf(__('Something went wrong while setting %{issuableType} to-do item.'), {
+ issuableType: this.issuableType,
+ }),
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div data-testid="sidebar-todo">
+ <todo-button
+ :issuable-type="issuableType"
+ :issuable-id="issuableId"
+ :is-todo="hasTodo"
+ :loading="isLoading"
+ size="small"
+ class="hide-collapsed"
+ @click.stop.prevent="toggleTodo"
+ />
+ <gl-button
+ v-if="isClassicSidebar"
+ category="tertiary"
+ type="reset"
+ class="sidebar-collapsed-icon sidebar-collapsed-container gl-rounded-0! gl-shadow-none!"
+ @click.stop.prevent="toggleTodo"
+ >
+ <gl-icon
+ v-gl-tooltip.left.viewport
+ :title="tootltipTitle"
+ :size="16"
+ :class="{ 'todo-undone': hasTodo }"
+ :name="collapsedButtonIcon"
+ :aria-label="collapsedButtonIcon"
+ />
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
index f589e7555b3..f7e76cc2b7f 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
@@ -85,6 +85,6 @@ export default {
:name="collapsedButtonIcon"
/>
<span v-show="!collapsed" class="issuable-todo-inner">{{ buttonLabel }}</span>
- <gl-loading-icon v-show="isActionActive" :inline="true" />
+ <gl-loading-icon v-show="isActionActive" size="sm" :inline="true" />
</button>
</template>