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/sidebar_dropdown.vue')
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown.vue252
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>