summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/sidebar
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/sidebar')
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue1
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue100
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue12
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/help_state.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue1
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue30
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js5
-rw-r--r--app/assets/javascripts/sidebar/queries/update_merge_request_labels.mutation.graphql15
-rw-r--r--app/assets/javascripts/sidebar/utils.js1
18 files changed, 158 insertions, 61 deletions
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
index 052bb3dcb53..00f1339d7f2 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
@@ -22,7 +22,9 @@ export default {
return sprintf(__("%{userName}'s avatar"), { userName: this.user.name });
},
avatarUrl() {
- return this.user.avatar || this.user.avatar_url || gon.default_avatar_url;
+ return (
+ this.user.avatarUrl || this.user.avatar || this.user.avatar_url || gon.default_avatar_url
+ );
},
isMergeRequest() {
return this.issuableType === 'merge_request';
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
index 878b331fb3c..fbbe2e341a7 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -16,10 +16,6 @@ export default {
type: Object,
required: true,
},
- rootPath: {
- type: String,
- required: true,
- },
tooltipPlacement: {
type: String,
default: 'bottom',
@@ -76,7 +72,7 @@ export default {
<!-- use d-flex so that slot can be appropriately styled -->
<span class="d-flex">
<assignee-avatar :user="user" :img-size="32" :issuable-type="issuableType" />
- <slot :user="user"></slot>
+ <slot></slot>
</span>
</gl-link>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index 20dc7cb07e7..5f8ba844218 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -29,7 +29,8 @@ export default {
},
changing: {
type: Boolean,
- required: true,
+ required: false,
+ default: false,
},
},
computed: {
diff --git a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
index 4697d85472b..cf6a0a4a151 100644
--- a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
@@ -26,7 +26,6 @@ export default {
<template>
<div class="gl-display-flex gl-flex-direction-column">
- <label data-testid="assigneeLabel">{{ assigneesText }}</label>
<div v-if="emptyUsers" data-testid="none">
<span>
{{ __('None') }}
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index 95934c0ef2a..31d5d7c0077 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -13,10 +13,6 @@ export default {
type: Array,
required: true,
},
- rootPath: {
- type: String,
- required: true,
- },
issuableType: {
type: String,
required: false,
@@ -66,22 +62,20 @@ export default {
<template>
<assignee-avatar-link
v-if="hasOneUser"
- #default="{ user }"
tooltip-placement="left"
:tooltip-has-name="false"
:user="firstUser"
- :root-path="rootPath"
:issuable-type="issuableType"
>
<div class="ml-2 gl-line-height-normal">
- <div>{{ user.name }}</div>
+ <div>{{ firstUser.name }}</div>
<div>{{ username }}</div>
</div>
</assignee-avatar-link>
<div v-else>
<div class="user-list">
<div v-for="user in uncollapsedUsers" :key="user.id" class="user-item">
- <assignee-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType" />
+ <assignee-avatar-link :user="user" :issuable-type="issuableType" />
</div>
</div>
<div v-if="renderShowMoreSection" class="user-list-more">
diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
index 1af1bc18e3e..1785174e8d7 100644
--- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
+++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
@@ -1,11 +1,26 @@
<script>
import $ from 'jquery';
-import { difference, union } from 'lodash';
-import flash from '~/flash';
-import axios from '~/lib/utils/axios_utils';
+import { camelCase, difference, union } from 'lodash';
+import updateIssueLabelsMutation from '~/boards/queries/issue_set_labels.mutation.graphql';
+import createFlash from '~/flash';
+import { IssuableType } from '~/issue_show/constants';
import { __ } from '~/locale';
+import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_request_labels.mutation.graphql';
+import { toLabelGid } from '~/sidebar/utils';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
+import { getIdFromGraphQLId, MutationOperationMode } from '~/graphql_shared/utils';
+
+const mutationMap = {
+ [IssuableType.Issue]: {
+ mutation: updateIssueLabelsMutation,
+ mutationName: 'updateIssue',
+ },
+ [IssuableType.MergeRequest]: {
+ mutation: updateMergeRequestLabelsMutation,
+ mutationName: 'mergeRequestSetLabels',
+ },
+};
export default {
components: {
@@ -21,7 +36,6 @@ export default {
'issuableType',
'labelsFetchPath',
'labelsManagePath',
- 'labelsUpdatePath',
'projectIssuesPath',
'projectPath',
],
@@ -35,37 +49,79 @@ export default {
handleDropdownClose() {
$(this.$el).trigger('hidden.gl.dropdown');
},
- handleUpdateSelectedLabels(dropdownLabels) {
+ getUpdateVariables(dropdownLabels) {
const currentLabelIds = this.selectedLabels.map(label => label.id);
const userAddedLabelIds = dropdownLabels.filter(label => label.set).map(label => label.id);
const userRemovedLabelIds = dropdownLabels.filter(label => !label.set).map(label => label.id);
const labelIds = difference(union(currentLabelIds, userAddedLabelIds), userRemovedLabelIds);
- this.updateSelectedLabels(labelIds);
+ switch (this.issuableType) {
+ case IssuableType.Issue:
+ return {
+ addLabelIds: userAddedLabelIds,
+ iid: this.iid,
+ projectPath: this.projectPath,
+ removeLabelIds: userRemovedLabelIds,
+ };
+ case IssuableType.MergeRequest:
+ return {
+ iid: this.iid,
+ labelIds: labelIds.map(toLabelGid),
+ operationMode: MutationOperationMode.Replace,
+ projectPath: this.projectPath,
+ };
+ default:
+ return {};
+ }
+ },
+ handleUpdateSelectedLabels(dropdownLabels) {
+ this.updateSelectedLabels(this.getUpdateVariables(dropdownLabels));
+ },
+ getRemoveVariables(labelId) {
+ switch (this.issuableType) {
+ case IssuableType.Issue:
+ return {
+ iid: this.iid,
+ projectPath: this.projectPath,
+ removeLabelIds: [labelId],
+ };
+ case IssuableType.MergeRequest:
+ return {
+ iid: this.iid,
+ labelIds: [toLabelGid(labelId)],
+ operationMode: MutationOperationMode.Remove,
+ projectPath: this.projectPath,
+ };
+ default:
+ return {};
+ }
},
handleLabelRemove(labelId) {
- const currentLabelIds = this.selectedLabels.map(label => label.id);
- const labelIds = difference(currentLabelIds, [labelId]);
-
- this.updateSelectedLabels(labelIds);
+ this.updateSelectedLabels(this.getRemoveVariables(labelId));
},
- updateSelectedLabels(labelIds) {
+ updateSelectedLabels(inputVariables) {
this.isLabelsSelectInProgress = true;
- axios({
- data: {
- [this.issuableType]: {
- label_ids: labelIds,
- },
- },
- method: 'put',
- url: this.labelsUpdatePath,
- })
+ this.$apollo
+ .mutate({
+ mutation: mutationMap[this.issuableType].mutation,
+ variables: { input: inputVariables },
+ })
.then(({ data }) => {
- this.selectedLabels = data.labels;
+ const { mutationName } = mutationMap[this.issuableType];
+
+ if (data[mutationName]?.errors?.length) {
+ throw new Error();
+ }
+
+ const issuableType = camelCase(this.issuableType);
+ this.selectedLabels = data[mutationName]?.[issuableType]?.labels?.nodes?.map(label => ({
+ ...label,
+ id: getIdFromGraphQLId(label.id),
+ }));
})
- .catch(() => flash(__('An error occurred while updating labels.')))
+ .catch(() => createFlash({ message: __('An error occurred while updating labels.') }))
.finally(() => {
this.isLabelsSelectInProgress = false;
});
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 0457aad8795..6e004084077 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,9 +1,8 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
const ICON_ON = 'notifications';
@@ -13,7 +12,7 @@ const LABEL_OFF = __('Notifications off');
export default {
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
@@ -110,12 +109,9 @@ export default {
<div>
<span
ref="tooltip"
- v-tooltip
- class="sidebar-collapsed-icon"
+ v-gl-tooltip.viewport.left
:title="notificationTooltip"
- data-container="body"
- data-placement="left"
- data-boundary="viewport"
+ class="sidebar-collapsed-icon"
@click="onClickCollapsedIcon"
>
<gl-icon
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
index 9d72bf4394e..7b67c34ded6 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -96,7 +96,12 @@ export default {
</script>
<template>
- <div v-gl-tooltip:body.viewport.left :title="tooltipText" class="sidebar-collapsed-icon">
+ <div
+ v-gl-tooltip:body.viewport.left
+ :title="tooltipText"
+ data-testid="collapsedState"
+ class="sidebar-collapsed-icon"
+ >
<gl-icon name="timer" />
<div class="time-tracking-collapsed-summary">
<div :class="divClass">
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index d4cc98e3743..99302993b9a 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -70,14 +70,19 @@ export default {
</script>
<template>
- <div class="time-tracking-comparison-pane">
+ <div data-testid="timeTrackingComparisonPane">
<div
v-gl-tooltip
+ data-testid="compareMeter"
:title="timeRemainingTooltip"
:class="timeRemainingStatusClass"
class="compare-meter"
>
- <gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" />
+ <gl-progress-bar
+ data-testid="timeRemainingProgress"
+ :value="timeRemainingPercent"
+ :variant="progressBarVariant"
+ />
<div class="compare-display-container">
<div class="compare-display float-left">
<span class="compare-label">{{ s__('TimeTracking|Spent') }}</span>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
index 305726d9725..8a80b1bf13f 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
@@ -11,7 +11,8 @@ export default {
</script>
<template>
- <div class="time-tracking-estimate-only-pane">
- <span class="bold"> {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }}
+ <div data-testid="estimateOnlyPane">
+ <span class="gl-font-weight-bold">{{ s__('TimeTracking|Estimated:') }} </span
+ >{{ timeEstimateHumanReadable }}
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
index b45746e789d..8bc828091c0 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
@@ -34,7 +34,7 @@ export default {
</script>
<template>
- <div class="time-tracking-help-state">
+ <div data-testid="helpPane" class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>{{ __('Track time with quick actions') }}</h4>
<p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
index 45552589e50..2d3d0ce8dc5 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
@@ -5,7 +5,7 @@ export default {
</script>
<template>
- <div class="time-tracking-no-tracking-pane">
- <span class="no-value"> {{ __('No estimate or time spent') }} </span>
+ <div data-testid="noTrackingPane">
+ <span class="no-value">{{ __('No estimate or time spent') }}</span>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
index 406677941b7..6bef5ed67a4 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
@@ -57,7 +57,6 @@ export default {
:human-time-estimate="store.humanTimeEstimate"
:human-time-spent="store.humanTotalTimeSpent"
:limit-to-hours="store.timeTrackingLimitToHours"
- :root-path="store.rootPath"
/>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
index b2b3b289c5c..33c6ac6e2ba 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
@@ -15,7 +15,7 @@ export default {
return sprintf(
s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'),
{
- startTag: '<span class="bold">',
+ startTag: '<span class="gl-font-weight-bold">',
endTag: '</span>',
timeSpentHumanReadable: this.timeSpentHumanReadable,
},
@@ -27,5 +27,5 @@ export default {
</script>
<template>
- <div class="time-tracking-spend-only-pane" v-html="timeSpent"></div>
+ <div data-testid="spentOnlyPane" v-html="timeSpent"></div>
</template>
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 a2fb0ebcbc6..3199ed1e615 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -44,6 +44,21 @@ export default {
default: false,
required: false,
},
+ /*
+ In issue list, "time-tracking-collapsed-state" is always rendered even if the sidebar isn't collapsed.
+ The actual hiding is controlled with css classes:
+ Hide "time-tracking-collapsed-state"
+ if .right-sidebar .right-sidebar-collapsed .sidebar-collapsed-icon
+ Show "time-tracking-collapsed-state"
+ if .right-sidebar .right-sidebar-expanded .sidebar-collapsed-icon
+
+ In Swimlanes sidebar, we do not use collapsed state at all.
+ */
+ showCollapsed: {
+ type: Boolean,
+ default: true,
+ required: false,
+ },
},
data() {
return {
@@ -93,8 +108,9 @@ export default {
</script>
<template>
- <div v-cloak class="time_tracker time-tracking-component-wrap">
+ <div v-cloak class="time-tracker time-tracking-component-wrap" data-testid="time-tracker">
<time-tracking-collapsed-state
+ v-if="showCollapsed"
:show-comparison-state="showComparisonState"
:show-no-time-tracking-state="showNoTimeTrackingState"
:show-help-state="showHelpState"
@@ -103,13 +119,19 @@ export default {
:time-spent-human-readable="humanTimeSpent"
:time-estimate-human-readable="humanTimeEstimate"
/>
- <div class="title hide-collapsed">
+ <div class="title hide-collapsed gl-mb-3">
{{ __('Time tracking') }}
- <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)">
+ <div
+ v-if="!showHelpState"
+ data-testid="helpButton"
+ class="help-button float-right"
+ @click="toggleHelpState(true)"
+ >
<gl-icon name="question-o" />
</div>
<div
- v-if="showHelpState"
+ v-else
+ data-testid="closeHelpButton"
class="close-help-button float-right"
@click="toggleHelpState(false)"
>
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 00b4e2de5e5..984cd8a3b1d 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -91,8 +91,13 @@ export function mountSidebarLabels() {
return false;
}
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
return new Vue({
el,
+ apolloProvider,
provide: {
...el.dataset,
allowLabelCreate: parseBoolean(el.dataset.allowLabelCreate),
diff --git a/app/assets/javascripts/sidebar/queries/update_merge_request_labels.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_merge_request_labels.mutation.graphql
new file mode 100644
index 00000000000..3c09daad793
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/update_merge_request_labels.mutation.graphql
@@ -0,0 +1,15 @@
+mutation mergeRequestSetLabels($input: MergeRequestSetLabelsInput!) {
+ mergeRequestSetLabels(input: $input) {
+ errors
+ mergeRequest {
+ labels {
+ nodes {
+ color
+ description
+ id
+ title
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/utils.js b/app/assets/javascripts/sidebar/utils.js
new file mode 100644
index 00000000000..23730508b56
--- /dev/null
+++ b/app/assets/javascripts/sidebar/utils.js
@@ -0,0 +1 @@
+export const toLabelGid = id => `gid://gitlab/Label/${id}`;