diff options
Diffstat (limited to 'app/assets/javascripts/sidebar/components/sidebar_dropdown.vue')
-rw-r--r-- | app/assets/javascripts/sidebar/components/sidebar_dropdown.vue | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue new file mode 100644 index 00000000000..26e2bc96f54 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue @@ -0,0 +1,252 @@ +<script> +import { + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlDropdownText, + GlLoadingIcon, + GlSearchBoxByType, +} from '@gitlab/ui'; +import { kebabCase, snakeCase } from 'lodash'; +import { IssuableType, WorkspaceType } from '~/issues/constants'; +import { __ } from '~/locale'; +import { + defaultEpicSort, + dropdowni18nText, + epicIidPattern, + issuableAttributesQueries, + IssuableAttributeState, + IssuableAttributeType, + IssuableAttributeTypeKeyMap, + LocalizedIssuableAttributeType, + noAttributeId, +} from 'ee_else_ce/sidebar/constants'; +import { createAlert } from '~/flash'; +import { PathIdSeparator } from '~/related_issues/constants'; + +export default { + noAttributeId, + i18n: { + expired: __('(expired)'), + }, + components: { + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlDropdownDivider, + GlSearchBoxByType, + GlLoadingIcon, + }, + inject: { + issuableAttributesQueries: { + default: issuableAttributesQueries, + }, + issuableAttributesState: { + default: IssuableAttributeState, + }, + widgetTitleText: { + default: { + [IssuableAttributeType.Milestone]: __('Milestone'), + expired: __('(expired)'), + none: __('None'), + }, + }, + }, + props: { + attrWorkspacePath: { + required: true, + type: String, + }, + currentAttribute: { + type: Object, + required: false, + default: () => ({}), + }, + issuableAttribute: { + type: String, + required: true, + }, + issuableType: { + type: String, + required: true, + validator(value) { + return [IssuableType.Issue, IssuableType.MergeRequest].includes(value); + }, + }, + workspaceType: { + type: String, + required: false, + default: WorkspaceType.project, + validator(value) { + return [WorkspaceType.group, WorkspaceType.project].includes(value); + }, + }, + }, + data() { + return { + attributesList: [], + searchTerm: '', + skipQuery: true, + }; + }, + apollo: { + attributesList: { + query() { + const { list } = this.issuableAttributeQuery; + const { query } = list[this.issuableType]; + return query[this.workspaceType] || query; + }, + variables() { + if (!this.isEpic) { + return { + fullPath: this.attrWorkspacePath, + title: this.searchTerm, + state: this.issuableAttributesState[this.issuableAttribute], + }; + } + + const variables = { + fullPath: this.attrWorkspacePath, + state: this.issuableAttributesState[this.issuableAttribute], + sort: defaultEpicSort, + }; + + if (epicIidPattern.test(this.searchTerm)) { + const matches = this.searchTerm.match(epicIidPattern); + variables.iidStartsWith = matches.groups.iid; + } else if (this.searchTerm !== '') { + variables.in = 'TITLE'; + variables.title = this.searchTerm; + } + + return variables; + }, + update: (data) => data?.workspace?.attributes?.nodes ?? [], + error(error) { + createAlert({ message: this.i18n.listFetchError, captureError: true, error }); + }, + skip() { + if ( + this.isEpic && + this.searchTerm.startsWith(PathIdSeparator.Epic) && + this.searchTerm.length < 2 + ) { + return true; + } + return this.skipQuery; + }, + debounce: 250, + }, + }, + computed: { + attributeTypeTitle() { + return this.widgetTitleText[this.issuableAttribute]; + }, + dropdownText() { + return this.currentAttribute ? this.currentAttribute?.title : this.attributeTypeTitle; + }, + emptyPropsList() { + return this.attributesList.length === 0; + }, + i18n() { + const localizedAttribute = + LocalizedIssuableAttributeType[IssuableAttributeTypeKeyMap[this.issuableAttribute]]; + return dropdowni18nText(localizedAttribute, this.issuableType); + }, + isEpic() { + // MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311 + return this.issuableAttribute === IssuableType.Epic; + }, + issuableAttributeQuery() { + return this.issuableAttributesQueries[this.issuableAttribute]; + }, + formatIssuableAttribute() { + return { + kebab: kebabCase(this.issuableAttribute), + snake: snakeCase(this.issuableAttribute), + }; + }, + }, + methods: { + isAttributeChecked(attributeId) { + return ( + attributeId === this.currentAttribute?.id || (!this.currentAttribute?.id && !attributeId) + ); + }, + isAttributeOverdue(attribute) { + return this.issuableAttribute === IssuableAttributeType.Milestone + ? attribute?.expired + : false; + }, + handleShow() { + this.skipQuery = false; + }, + setFocus() { + this.$refs.search.focusInput(); + }, + show() { + this.$refs.dropdown.show(); + }, + updateAttribute(attribute) { + this.$emit('change', attribute); + }, + }, +}; +</script> + +<template> + <gl-dropdown + ref="dropdown" + block + :header-text="i18n.assignAttribute" + lazy + :text="dropdownText" + toggle-class="gl-m-0" + @show="handleShow" + @shown="setFocus" + > + <gl-search-box-by-type ref="search" v-model="searchTerm" :placeholder="__('Search')" /> + <gl-dropdown-item + :data-testid="`no-${formatIssuableAttribute.kebab}-item`" + is-check-item + :is-checked="isAttributeChecked($options.noAttributeId)" + @click="$emit('change', { id: $options.noAttributeId })" + > + {{ i18n.noAttribute }} + </gl-dropdown-item> + <gl-dropdown-divider /> + <gl-loading-icon + v-if="$apollo.queries.attributesList.loading" + size="sm" + class="gl-py-4" + data-testid="loading-icon-dropdown" + /> + <template v-else> + <gl-dropdown-text v-if="emptyPropsList"> + {{ i18n.noAttributesFound }} + </gl-dropdown-text> + <slot + v-else + name="list" + :attributes-list="attributesList" + :is-attribute-checked="isAttributeChecked" + :update-attribute="updateAttribute" + > + <gl-dropdown-item + v-for="attrItem in attributesList" + :key="attrItem.id" + is-check-item + :is-checked="isAttributeChecked(attrItem.id)" + :data-testid="`${formatIssuableAttribute.kebab}-items`" + @click="updateAttribute(attrItem)" + > + {{ attrItem.title }} + <template v-if="isAttributeOverdue(attrItem)">{{ $options.i18n.expired }}</template> + </gl-dropdown-item> + </slot> + </template> + <template #footer> + <slot name="footer"></slot> + </template> + </gl-dropdown> +</template> |