diff options
Diffstat (limited to 'app/assets/javascripts')
5 files changed, 113 insertions, 5 deletions
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index 3af2b7892be..cd89234a231 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -13,6 +13,7 @@ import { mergeById, isSidebarDirty, getAggregationsUrl, + prepareSearchAggregations, } from './utils'; export const fetchGroups = ({ commit }, search) => { @@ -135,12 +136,12 @@ export const fetchSidebarCount = ({ commit, state }) => { return Promise.all(promises); }; -export const fetchLanguageAggregation = ({ commit }) => { +export const fetchLanguageAggregation = ({ commit, state }) => { commit(types.REQUEST_AGGREGATIONS); return axios .get(getAggregationsUrl()) .then(({ data }) => { - commit(types.RECEIVE_AGGREGATIONS_SUCCESS, data); + commit(types.RECEIVE_AGGREGATIONS_SUCCESS, prepareSearchAggregations(state, data)); }) .catch((e) => { logError(e); diff --git a/app/assets/javascripts/search/store/utils.js b/app/assets/javascripts/search/store/utils.js index 1e6619ca6d5..8e484e69646 100644 --- a/app/assets/javascripts/search/store/utils.js +++ b/app/assets/javascripts/search/store/utils.js @@ -1,7 +1,8 @@ -import { isEqual } from 'lodash'; +import { isEqual, orderBy } from 'lodash'; import AccessorUtilities from '~/lib/utils/accessor'; import { formatNumber } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; +import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; import { MAX_FREQUENT_ITEMS, MAX_FREQUENCY, @@ -9,6 +10,8 @@ import { NUMBER_FORMATING_OPTIONS, } from './constants'; +const LANGUAGE_AGGREGATION_NAME = languageFilterData.filterParam; + function extractKeys(object, keyList) { return Object.fromEntries(keyList.map((key) => [key, object[key]])); } @@ -117,3 +120,27 @@ export const getAggregationsUrl = () => { currentUrl.pathname = joinPaths('/search', 'aggregations'); return currentUrl.toString(); }; + +const sortLanguages = (state, entries) => { + const queriedLanguages = state.query?.[LANGUAGE_AGGREGATION_NAME] || []; + + if (!Array.isArray(queriedLanguages) || !queriedLanguages.length) { + return entries; + } + + const queriedLanguagesSet = new Set(queriedLanguages); + + return orderBy(entries, [({ key }) => queriedLanguagesSet.has(key), 'count'], ['desc', 'desc']); +}; + +export const prepareSearchAggregations = (state, aggregationData) => + aggregationData.map((item) => { + if (item?.name === LANGUAGE_AGGREGATION_NAME) { + return { + ...item, + buckets: sortLanguages(state, item.buckets), + }; + } + + return item; + }); diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue index 3225d3f6e49..1d3319dbc90 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue @@ -87,6 +87,9 @@ export default { hasAdminPermission() { return this.note.userPermissions.adminNote; }, + hasAwardEmojiPermission() { + return this.note.userPermissions.awardEmoji; + }, }, methods: { showReplyForm() { @@ -159,10 +162,13 @@ export default { </note-header> <div class="gl-display-inline-flex"> <note-actions + :show-award-emoji="hasAwardEmojiPermission" :show-reply="showReply" :show-edit="hasAdminPermission" + :note-id="note.id" @startReplying="showReplyForm" @startEditing="startEditing" + @error="($event) => $emit('error', $event)" /> <!-- v-if condition should be moved to "delete" dropdown item as soon as we implement copying the link --> <gl-dropdown diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue index c17e855e527..6bea7953698 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue @@ -1,7 +1,10 @@ <script> -import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; +import { __, s__ } from '~/locale'; import ReplyButton from '~/notes/components/note_actions/reply_button.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import addAwardEmojiMutation from '../../graphql/notes/work_item_note_add_award_emoji.mutation.graphql'; export default { name: 'WorkItemNoteActions', @@ -10,11 +13,14 @@ export default { }, components: { GlButton, + GlIcon, ReplyButton, + EmojiPicker: () => import('~/emoji/components/picker.vue'), }, directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], props: { showReply: { type: Boolean, @@ -24,12 +30,63 @@ export default { type: Boolean, required: true, }, + noteId: { + type: String, + required: true, + }, + showAwardEmoji: { + type: Boolean, + required: false, + default: false, + }, + }, + methods: { + async setAwardEmoji(name) { + try { + const { + data: { + awardEmojiAdd: { errors = [] }, + }, + } = await this.$apollo.mutate({ + mutation: addAwardEmojiMutation, + variables: { + awardableId: this.noteId, + name, + }, + }); + + if (errors.length > 0) { + throw new Error(errors[0].message); + } + } catch (error) { + this.$emit('error', s__('WorkItem|Failed to award emoji')); + Sentry.captureException(error); + } + }, }, }; </script> <template> <div class="note-actions"> + <emoji-picker + v-if="showAwardEmoji && glFeatures.workItemsMvc2" + toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary" + data-testid="note-emoji-button" + @click="setAwardEmoji" + > + <template #button-content> + <gl-icon class="award-control-icon-neutral gl-button-icon gl-icon" name="slight-smile" /> + <gl-icon + class="award-control-icon-positive gl-button-icon gl-icon gl-left-3!" + name="smiley" + /> + <gl-icon + class="award-control-icon-super-positive gl-button-icon gl-icon gl-left-3!" + name="smile" + /> + </template> + </emoji-picker> <reply-button v-if="showReply" ref="replyButton" @startReplying="$emit('startReplying')" /> <gl-button v-if="showEdit" diff --git a/app/assets/javascripts/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql b/app/assets/javascripts/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql new file mode 100644 index 00000000000..dc51c53428b --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql @@ -0,0 +1,17 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + +mutation workItemNoteAddAwardEmoji($awardableId: AwardableID!, $name: String!) { + awardEmojiAdd(input: { awardableId: $awardableId, name: $name }) { + awardEmoji { + name + description + unicode + emoji + unicodeVersion + user { + ...User + } + } + errors + } +} |