diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 09:55:51 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 09:55:51 +0000 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /app/assets/javascripts/vue_shared/components/sidebar | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (diff) | |
download | gitlab-ce-e8d2c2579383897a1dd7f9debd359abe8ae8373d.tar.gz |
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/sidebar')
18 files changed, 208 insertions, 42 deletions
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue index bbc7e6e7a6e..5c3a6852219 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { s__, __, sprintf } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -10,8 +10,9 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; export default { name: 'CopyableField', components: { - GlLoadingIcon, ClipboardButton, + GlLoadingIcon, + GlSprintf, }, props: { value: { @@ -48,12 +49,6 @@ export default { loadingIconLabel() { return sprintf(this.$options.i18n.loadingIconLabel, { name: this.name }); }, - templateText() { - return sprintf(this.$options.i18n.templateText, { - name: this.name, - value: this.value, - }); - }, }, i18n: { loadingIconLabel: __('Loading %{name}'), @@ -78,10 +73,13 @@ export default { class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap" :title="value" > - {{ templateText }} + <gl-sprintf :message="$options.i18n.templateText"> + <template #name>{{ name }}</template> + <template #value>{{ value }}</template> + </gl-sprintf> </span> - <gl-loading-icon v-if="isLoading" inline :label="loadingIconLabel" /> + <gl-loading-icon v-if="isLoading" size="sm" inline :label="loadingIconLabel" /> <clipboard-button v-else size="small" v-bind="clipboardProps" /> </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 075681de320..4531fafbf72 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -104,7 +104,7 @@ export default { <collapsed-calendar-icon :text="collapsedText" class="sidebar-collapsed-icon" /> <div class="title"> {{ label }} - <gl-loading-icon v-if="isLoading" :inline="true" /> + <gl-loading-icon v-if="isLoading" size="sm" :inline="true" /> <div class="float-right"> <button v-if="editable && !editing" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue index 320e2048f1c..12daaea8758 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue @@ -148,7 +148,7 @@ export default { @hide="handleDropdownHide" > <template #button-content - ><gl-loading-icon v-if="moveInProgress" class="gl-mr-3" />{{ + ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{ dropdownButtonTitle }}</template > diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue index f8cc981ba3d..3ec33a653b8 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue @@ -108,7 +108,7 @@ export default { class="float-left d-flex align-items-center" @click="handleCreateClick" > - <gl-loading-icon v-show="labelCreateInProgress" :inline="true" class="mr-1" /> + <gl-loading-icon v-show="labelCreateInProgress" size="sm" :inline="true" class="mr-1" /> {{ __('Create') }} </gl-button> <gl-button class="float-right js-btn-cancel-create" @click="toggleDropdownContentsCreateView"> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue index 86788a84260..9914bfc6026 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue @@ -48,6 +48,12 @@ export default { } return this.labels; }, + showDropdownFooter() { + return ( + (this.isDropdownVariantSidebar || this.isDropdownVariantEmbedded) && + (this.allowLabelCreate || this.labelsManagePath) + ); + }, showNoMatchingResultsMessage() { return Boolean(this.searchKey) && this.visibleLabels.length === 0; }, @@ -192,11 +198,7 @@ export default { </li> </ul> </div> - <div - v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" - class="dropdown-footer" - data-testid="dropdown-footer" - > + <div v-if="showDropdownFooter" class="dropdown-footer" data-testid="dropdown-footer"> <ul class="list-unstyled"> <li v-if="allowLabelCreate"> <gl-link @@ -206,7 +208,7 @@ export default { {{ footerCreateLabelTitle }} </gl-link> </li> - <li> + <li v-if="labelsManagePath"> <gl-link :href="labelsManagePath" class="gl-display-flex flex-row text-break-word label-item" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue index 813de528c0b..aad754e15b0 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue @@ -26,7 +26,7 @@ export default { <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900"> {{ __('Labels') }} <template v-if="allowLabelEdit"> - <gl-loading-icon v-show="labelsSelectInProgress" inline /> + <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline /> <gl-button variant="link" class="float-right gl-text-gray-900! gl-hover-text-blue-800! js-sidebar-dropdown-toggle" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js index 89f96ab916b..178be0f6da0 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js @@ -1,4 +1,4 @@ -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import * as types from './mutation_types'; @@ -16,7 +16,9 @@ export const receiveLabelsSuccess = ({ commit }, labels) => commit(types.RECEIVE_SET_LABELS_SUCCESS, labels); export const receiveLabelsFailure = ({ commit }) => { commit(types.RECEIVE_SET_LABELS_FAILURE); - flash(__('Error fetching labels.')); + createFlash({ + message: __('Error fetching labels.'), + }); }; export const fetchLabels = ({ state, dispatch }) => { dispatch('requestLabels'); @@ -32,7 +34,9 @@ export const requestCreateLabel = ({ commit }) => commit(types.REQUEST_CREATE_LA export const receiveCreateLabelSuccess = ({ commit }) => commit(types.RECEIVE_CREATE_LABEL_SUCCESS); export const receiveCreateLabelFailure = ({ commit }) => { commit(types.RECEIVE_CREATE_LABEL_FAILURE); - flash(__('Error creating label.')); + createFlash({ + message: __('Error creating label.'), + }); }; export const createLabel = ({ state, dispatch }, label) => { dispatch('requestCreateLabel'); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js index 55716e1105e..2e0a57f15dd 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js @@ -1,3 +1,4 @@ +import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils'; import { DropdownVariant } from '../constants'; import * as types from './mutation_types'; @@ -66,5 +67,16 @@ export default { candidateLabel.touched = true; candidateLabel.set = !candidateLabel.set; } + + if (isScopedLabel(candidateLabel)) { + const scopedBase = scopedLabelKey(candidateLabel); + const currentActiveScopedLabel = state.labels.find(({ title }) => { + return title.startsWith(scopedBase) && title !== '' && title !== candidateLabel.title; + }); + + if (currentActiveScopedLabel) { + currentActiveScopedLabel.set = false; + } + } }, }; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue index a7f20fbe851..4651e7a1576 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue @@ -117,7 +117,7 @@ export default { data-testid="create-button" @click="createLabel" > - <gl-loading-icon v-if="labelCreateInProgress" :inline="true" class="mr-1" /> + <gl-loading-icon v-if="labelCreateInProgress" size="sm" :inline="true" class="mr-1" /> {{ __('Create') }} </gl-button> <gl-button diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue index 5d1663bc1fd..b6d14965cfa 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue @@ -26,7 +26,7 @@ export default { <div class="title hide-collapsed gl-mb-3"> {{ __('Labels') }} <template v-if="allowLabelEdit"> - <gl-loading-icon v-show="labelsSelectInProgress" inline /> + <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline /> <gl-button variant="link" class="float-right js-sidebar-dropdown-toggle" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue index 46ccb9470e5..58a940bca3b 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue @@ -1,7 +1,6 @@ <script> import { GlLabel } from '@gitlab/ui'; -import { mapState } from 'vuex'; - +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { isScopedLabel } from '~/lib/utils/common_utils'; export default { @@ -14,15 +13,26 @@ export default { required: false, default: false, }, - }, - computed: { - ...mapState([ - 'selectedLabels', - 'allowLabelRemove', - 'allowScopedLabels', - 'labelsFilterBasePath', - 'labelsFilterParam', - ]), + selectedLabels: { + type: Array, + required: true, + }, + allowLabelRemove: { + type: Boolean, + required: true, + }, + allowScopedLabels: { + type: Boolean, + required: true, + }, + labelsFilterBasePath: { + type: String, + required: true, + }, + labelsFilterParam: { + type: String, + required: true, + }, }, methods: { labelFilterUrl(label) { @@ -33,6 +43,9 @@ export default { scopedLabel(label) { return this.allowScopedLabels && isScopedLabel(label); }, + removeLabel(labelId) { + this.$emit('onLabelRemove', getIdFromGraphQLId(labelId)); + }, }, }; </script> @@ -43,12 +56,14 @@ export default { 'has-labels': selectedLabels.length, }" class="hide-collapsed value issuable-show-labels js-value" + data-testid="value-wrapper" > - <span v-if="!selectedLabels.length" class="text-secondary"> + <span v-if="!selectedLabels.length" class="text-secondary" data-testid="empty-placeholder"> <slot></slot> </span> - <template v-for="label in selectedLabels" v-else> + <template v-else> <gl-label + v-for="label in selectedLabels" :key="label.id" data-qa-selector="selected_label_content" :data-qa-label-name="label.title" @@ -60,7 +75,7 @@ export default { :show-close-button="allowLabelRemove" :disabled="disableLabels" tooltip-placement="top" - @close="$emit('onLabelRemove', label.id)" + @close="removeLabel(label.id)" /> </template> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql new file mode 100644 index 00000000000..1c2fd3bb7c0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql @@ -0,0 +1,15 @@ +query issueLabels($fullPath: ID!, $iid: String) { + workspace: project(fullPath: $fullPath) { + issuable: issue(iid: $iid) { + id + labels { + nodes { + id + title + color + description + } + } + } + } +} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue index 7728c758e18..87f36a5bb72 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue @@ -11,6 +11,7 @@ import DropdownContents from './dropdown_contents.vue'; import DropdownTitle from './dropdown_title.vue'; import DropdownValue from './dropdown_value.vue'; import DropdownValueCollapsed from './dropdown_value_collapsed.vue'; +import issueLabelsQuery from './graphql/issue_labels.query.graphql'; import labelsSelectModule from './store'; Vue.use(Vuex); @@ -24,6 +25,7 @@ export default { DropdownContents, DropdownValueCollapsed, }, + inject: ['iid', 'projectPath'], props: { allowLabelRemove: { type: Boolean, @@ -119,8 +121,23 @@ export default { data() { return { contentIsOnViewport: true, + issueLabels: [], }; }, + apollo: { + issueLabels: { + query: issueLabelsQuery, + variables() { + return { + iid: this.iid, + fullPath: this.projectPath, + }; + }, + update(data) { + return data.workspace?.issuable?.labels.nodes || []; + }, + }, + }, computed: { ...mapState(['showDropdownButton', 'showDropdownContents']), ...mapGetters([ @@ -293,7 +310,7 @@ export default { <template v-if="isDropdownVariantSidebar"> <dropdown-value-collapsed ref="dropdownButtonCollapsed" - :labels="selectedLabels" + :labels="issueLabels" @onValueClick="handleCollapsedValueClick" /> <dropdown-title @@ -302,6 +319,11 @@ export default { /> <dropdown-value :disable-labels="labelsSelectInProgress" + :selected-labels="issueLabels" + :allow-label-remove="allowLabelRemove" + :allow-scoped-labels="allowScopedLabels" + :labels-filter-base-path="labelsFilterBasePath" + :labels-filter-param="labelsFilterParam" @onLabelRemove="$emit('onLabelRemove', $event)" > <slot></slot> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js index 2b96b159ca3..935f020f559 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js @@ -1,4 +1,4 @@ -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import * as types from './mutation_types'; @@ -16,7 +16,9 @@ export const receiveLabelsSuccess = ({ commit }, labels) => commit(types.RECEIVE_SET_LABELS_SUCCESS, labels); export const receiveLabelsFailure = ({ commit }) => { commit(types.RECEIVE_SET_LABELS_FAILURE); - flash(__('Error fetching labels.')); + createFlash({ + message: __('Error fetching labels.'), + }); }; export const fetchLabels = ({ state, dispatch }) => { dispatch('requestLabels'); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js index 131c6e6fb57..1c03d95f37b 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js @@ -1,3 +1,4 @@ +import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils'; import { DropdownVariant } from '../constants'; import * as types from './mutation_types'; @@ -55,5 +56,16 @@ export default { candidateLabel.touched = true; candidateLabel.set = !candidateLabel.set; } + + if (isScopedLabel(candidateLabel)) { + const scopedBase = scopedLabelKey(candidateLabel); + const currentActiveScopedLabel = state.labels.find( + ({ title }) => title.indexOf(scopedBase) === 0 && title !== candidateLabel.title, + ); + + if (currentActiveScopedLabel) { + currentActiveScopedLabel.set = false; + } + } }, }; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js new file mode 100644 index 00000000000..d2afc02233e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js @@ -0,0 +1,23 @@ +/* eslint-disable @gitlab/require-i18n-strings */ + +import TodoButton from './todo_button.vue'; + +export default { + component: TodoButton, + title: 'vue_shared/components/todo_toggle/todo_button', +}; + +const Template = (args, { argTypes }) => ({ + components: { TodoButton }, + props: Object.keys(argTypes), + template: '<todo-button v-bind="$props" v-on="$props" />', +}); + +export const Default = Template.bind({}); +Default.argTypes = { + isTodo: { + description: 'True if to-do is unresolved (i.e. not "done")', + control: { type: 'boolean' }, + }, + click: { action: 'clicked' }, +}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue new file mode 100644 index 00000000000..e6229cf0a93 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue @@ -0,0 +1,56 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import { todoLabel } from './utils'; + +export default { + components: { + GlButton, + }, + props: { + isTodo: { + type: Boolean, + required: false, + default: true, + }, + }, + computed: { + buttonLabel() { + return todoLabel(this.isTodo); + }, + }, + methods: { + updateGlobalTodoCount(additionalTodoCount) { + const countContainer = document.querySelector('.js-todos-count'); + if (countContainer === null) return; + const currentCount = parseInt(countContainer.innerText, 10); + const todoToggleEvent = new CustomEvent('todo:toggle', { + detail: { + count: Math.max(currentCount + additionalTodoCount, 0), + }, + }); + + document.dispatchEvent(todoToggleEvent); + }, + incrementGlobalTodoCount() { + this.updateGlobalTodoCount(1); + }, + decrementGlobalTodoCount() { + this.updateGlobalTodoCount(-1); + }, + onToggle(event) { + if (this.isTodo) { + this.decrementGlobalTodoCount(); + } else { + this.incrementGlobalTodoCount(); + } + this.$emit('click', event); + }, + }, +}; +</script> + +<template> + <gl-button v-bind="$attrs" :aria-label="buttonLabel" @click="onToggle($event)"> + {{ buttonLabel }} + </gl-button> +</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js new file mode 100644 index 00000000000..59e72a2ffe3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js @@ -0,0 +1,5 @@ +import { __ } from '~/locale'; + +export const todoLabel = (hasTodo) => { + return hasTodo ? __('Mark as done') : __('Add a to do'); +}; |