diff options
Diffstat (limited to 'app/assets/javascripts/admin/topics/components')
-rw-r--r-- | app/assets/javascripts/admin/topics/components/merge_topics.vue | 141 | ||||
-rw-r--r-- | app/assets/javascripts/admin/topics/components/topic_select.vue | 106 |
2 files changed, 247 insertions, 0 deletions
diff --git a/app/assets/javascripts/admin/topics/components/merge_topics.vue b/app/assets/javascripts/admin/topics/components/merge_topics.vue new file mode 100644 index 00000000000..921b762bbef --- /dev/null +++ b/app/assets/javascripts/admin/topics/components/merge_topics.vue @@ -0,0 +1,141 @@ +<script> +import { GlAlert, GlButton, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import csrf from '~/lib/utils/csrf'; +import TopicSelect from './topic_select.vue'; + +export default { + components: { + GlAlert, + GlButton, + GlModal, + GlSprintf, + TopicSelect, + }, + directives: { + GlModal: GlModalDirective, + }, + inject: ['path'], + data() { + return { + sourceTopic: {}, + targetTopic: {}, + }; + }, + computed: { + sourceTopicId() { + return getIdFromGraphQLId(this.sourceTopic?.id); + }, + targetTopicId() { + return getIdFromGraphQLId(this.targetTopic?.id); + }, + validSelectedTopics() { + return ( + Object.keys(this.sourceTopic).length && + Object.keys(this.targetTopic).length && + this.sourceTopic !== this.targetTopic + ); + }, + actionPrimary() { + return { + text: __('Merge'), + attributes: { + variant: 'danger', + disabled: !this.validSelectedTopics, + }, + }; + }, + }, + methods: { + selectSourceTopic(topic) { + this.sourceTopic = topic; + }, + selectTargetTopic(topic) { + this.targetTopic = topic; + }, + mergeTopics() { + this.$refs.mergeForm.submit(); + }, + }, + i18n: { + title: s__('MergeTopics|Merge topics'), + body: s__( + 'MergeTopics|Move all assigned projects from the source topic to the target topic and remove the source topic.', + ), + sourceTopic: s__('MergeTopics|Source topic'), + targetTopic: s__('MergeTopics|Target topic'), + warningTitle: s__('MergeTopics|Merging topics will cause the following:'), + warningBody: s__('MergeTopics|This action cannot be undone.'), + warningRemoveTopic: s__('MergeTopics|%{sourceTopic} will be removed'), + warningMoveProjects: s__('MergeTopics|All assigned projects will be moved to %{targetTopic}'), + }, + modal: { + id: 'merge-topics', + actionSecondary: { + text: __('Cancel'), + attributes: { + variant: 'default', + }, + }, + }, + csrf, +}; +</script> +<template> + <div class="gl-mr-3"> + <gl-button v-gl-modal="$options.modal.id" category="secondary">{{ + $options.i18n.title + }}</gl-button> + <gl-modal + :title="$options.i18n.title" + :action-primary="actionPrimary" + :action-secondary="$options.modal.actionSecondary" + :modal-id="$options.modal.id" + size="sm" + @primary="mergeTopics" + > + <p>{{ $options.i18n.body }}</p> + <topic-select + :selected-topic="sourceTopic" + :label-text="$options.i18n.sourceTopic" + @click="selectSourceTopic" + /> + <topic-select + :selected-topic="targetTopic" + :label-text="$options.i18n.targetTopic" + @click="selectTargetTopic" + /> + <gl-alert + v-if="validSelectedTopics" + :title="$options.i18n.warningTitle" + :dismissible="false" + variant="danger" + > + <ul> + <li> + <gl-sprintf :message="$options.i18n.warningRemoveTopic"> + <template #sourceTopic> + <strong>{{ sourceTopic.name }}</strong> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.warningMoveProjects"> + <template #targetTopic> + <strong>{{ targetTopic.name }}</strong> + </template> + </gl-sprintf> + </li> + </ul> + {{ $options.i18n.warningBody }} + </gl-alert> + <form ref="mergeForm" method="post" :action="path"> + <input type="hidden" name="_method" value="post" /> + <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> + <input type="hidden" name="source_topic_id" :value="sourceTopicId" /> + <input type="hidden" name="target_topic_id" :value="targetTopicId" /> + </form> + </gl-modal> + </div> +</template> diff --git a/app/assets/javascripts/admin/topics/components/topic_select.vue b/app/assets/javascripts/admin/topics/components/topic_select.vue new file mode 100644 index 00000000000..8bf5be1afd1 --- /dev/null +++ b/app/assets/javascripts/admin/topics/components/topic_select.vue @@ -0,0 +1,106 @@ +<script> +import { + GlAvatarLabeled, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, +} from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; +import searchProjectTopics from '~/graphql_shared/queries/project_topics_search.query.graphql'; + +export default { + components: { + GlAvatarLabeled, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + }, + props: { + selectedTopic: { + type: Object, + required: false, + default: () => ({}), + }, + labelText: { + type: String, + required: false, + default: null, + }, + }, + apollo: { + topics: { + query: searchProjectTopics, + variables() { + return { + search: this.search, + }; + }, + update(data) { + return data.topics?.nodes || []; + }, + debounce: 250, + }, + }, + data() { + return { + topics: [], + search: '', + }; + }, + computed: { + loading() { + return this.$apollo.queries.topics.loading; + }, + isResultEmpty() { + return this.topics.length === 0; + }, + dropdownText() { + if (Object.keys(this.selectedTopic).length) { + return this.selectedTopic.name; + } + + return this.$options.i18n.dropdownText; + }, + }, + methods: { + selectTopic(topic) { + this.$emit('click', topic); + }, + }, + i18n: { + dropdownText: s__('TopicSelect|Select a topic'), + searchPlaceholder: s__('TopicSelect|Search topics'), + emptySearchResult: s__('TopicSelect|No matching results'), + }, + AVATAR_SHAPE_OPTION_RECT, +}; +</script> + +<template> + <div> + <label v-if="labelText">{{ labelText }}</label> + <gl-dropdown block :text="dropdownText"> + <gl-search-box-by-type + v-model="search" + :is-loading="loading" + :placeholder="$options.i18n.searchPlaceholder" + /> + <gl-dropdown-item v-for="topic in topics" :key="topic.id" @click="selectTopic(topic)"> + <gl-avatar-labeled + :label="topic.title" + :sub-label="topic.name" + :src="topic.avatarUrl" + :entity-name="topic.name" + :size="32" + :shape="$options.AVATAR_SHAPE_OPTION_RECT" + /> + </gl-dropdown-item> + <gl-dropdown-text v-if="isResultEmpty && !loading"> + <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span> + </gl-dropdown-text> + </gl-dropdown> + </div> +</template> |