diff options
Diffstat (limited to 'app/assets/javascripts/sidebar')
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}`; |