diff --git a/app/assets/javascripts/achievements/components/achievements_app.vue b/app/assets/javascripts/achievements/components/achievements_app.vue index aa0c67521ef85fcf2cf94d98e335c0fb3746f472..5c32eca3354fe21c90906c449d4d4c36301a3aab 100644 --- a/app/assets/javascripts/achievements/components/achievements_app.vue +++ b/app/assets/javascripts/achievements/components/achievements_app.vue @@ -6,13 +6,13 @@ import { GlKeysetPagination, GlLoadingIcon, } from '@gitlab/ui'; -import { uniqBy } from 'lodash'; -import { s__ } from '~/locale'; +import { s__, sprintf } from '~/locale'; import PageHeading from '~/vue_shared/components/page_heading.vue'; import CrudComponent from '~/vue_shared/components/crud_component.vue'; import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; import { NEW_ROUTE_NAME } from '../constants'; import getGroupAchievements from './graphql/get_group_achievements.query.graphql'; +import getMoreUniqueUsers from './graphql/get_more_unique_users.query.graphql'; import AwardButton from './award_button.vue'; const ENTRIES_PER_PAGE = 20; @@ -57,6 +57,7 @@ export default { before: null, }, pageInfo: {}, + loadingUsers: {}, }; }, apollo: { @@ -110,8 +111,57 @@ export default { before: item, }; }, - uniqueRecipients(userAchievements) { - return uniqBy(userAchievements, 'user.id').map(({ user }) => user); + awardedUsers(userCount) { + return sprintf( + this.$options.i18n.users, + { + userCount, + }, + false, + ); + }, + async loadMoreUsers(achievementId, after) { + this.setLoadingState(achievementId, true); + try { + const fetchedData = await this.fetchMoreUniqueUsers(achievementId, after); + if (fetchedData) { + this.mergeUniqueUsers(achievementId, fetchedData); + } + } finally { + this.setLoadingState(achievementId, false); + } + }, + async fetchMoreUniqueUsers(achievementId, after) { + const { data } = await this.$apollo.query({ + query: getMoreUniqueUsers, + variables: { + groupFullPath: this.groupFullPath, + achievementId, + after, + }, + }); + return data?.group?.achievements?.nodes?.[0]?.uniqueUsers; + }, + mergeUniqueUsers(achievementId, fetchedData) { + this.achievements = this.achievements.map((achievement) => { + if (achievement.id === achievementId) { + return { + ...achievement, + uniqueUsers: { + nodes: [...achievement.uniqueUsers.nodes, ...fetchedData.nodes], + pageInfo: fetchedData.pageInfo, + count: fetchedData.count, + }, + }; + } + return achievement; + }); + }, + setLoadingState(achievementId, isLoading) { + this.loadingUsers = { + ...this.loadingUsers, + [achievementId]: isLoading, + }; }, }, i18n: { @@ -119,6 +169,7 @@ export default { emptyStateTitle: s__('Achievements|There are currently no achievements.'), newAchievement: s__('Achievements|New achievement'), notYetAwarded: s__('Achievements|Not yet awarded.'), + users: s__('Achievements|%{userCount} awarded users'), }, NEW_ROUTE_NAME, }; @@ -167,10 +218,16 @@ export default { +
+ {{ awardedUsers(achievement.uniqueUsers.count) }} +
{{ $options.i18n.notYetAwarded }} diff --git a/app/assets/javascripts/achievements/components/graphql/achievement_fields.fragment.graphql b/app/assets/javascripts/achievements/components/graphql/achievement_fields.fragment.graphql index df356d7fc798fc69f5608d8d99efffaf2275f9e6..418ac51c7cb6a26a936ce3c370541d399acdffe5 100644 --- a/app/assets/javascripts/achievements/components/graphql/achievement_fields.fragment.graphql +++ b/app/assets/javascripts/achievements/components/graphql/achievement_fields.fragment.graphql @@ -1,18 +1,11 @@ +#import "./unique_users_fields.fragment.graphql" + fragment AchievementFragment on Achievement { id name description avatarUrl - userAchievements { - nodes { - id - user { - id - username - name - avatarUrl - webUrl - } - } + uniqueUsers(first: 100) { + ...UniqueUsersFragment } } diff --git a/app/assets/javascripts/achievements/components/graphql/get_more_unique_users.query.graphql b/app/assets/javascripts/achievements/components/graphql/get_more_unique_users.query.graphql new file mode 100644 index 0000000000000000000000000000000000000000..52be4f32f0fc31e66633462273a83020e24423d3 --- /dev/null +++ b/app/assets/javascripts/achievements/components/graphql/get_more_unique_users.query.graphql @@ -0,0 +1,19 @@ +#import "./unique_users_fields.fragment.graphql" + +query getMoreUniqueUsers( + $groupFullPath: ID! + $achievementId: AchievementsAchievementID! + $after: String +) { + group(fullPath: $groupFullPath) { + id + achievements(ids: [$achievementId]) { + nodes { + id + uniqueUsers(first: 100, after: $after) { + ...UniqueUsersFragment + } + } + } + } +} diff --git a/app/assets/javascripts/achievements/components/graphql/unique_users_fields.fragment.graphql b/app/assets/javascripts/achievements/components/graphql/unique_users_fields.fragment.graphql new file mode 100644 index 0000000000000000000000000000000000000000..e88532edb4beb42008bf1339cc1b337379e94a36 --- /dev/null +++ b/app/assets/javascripts/achievements/components/graphql/unique_users_fields.fragment.graphql @@ -0,0 +1,12 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" +#import "~/graphql_shared/fragments/user.fragment.graphql" + +fragment UniqueUsersFragment on UserCoreConnection { + nodes { + ...User + } + pageInfo { + ...PageInfo + } + count +} diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue index 258e8b1a6c55dc692722288b6b3368dd58c4a06d..3d578d0fe51447048d1d61c8641064cf9dbd1863 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue @@ -27,6 +27,16 @@ export default { required: false, default: __('None'), }, + hasMore: { + type: Boolean, + required: false, + default: false, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -48,19 +58,26 @@ export default { return this.breakpoint > 0 && this.items.length > this.breakpoint; }, expandText() { - if (!this.hasHiddenItems) { + if (!this.hasHiddenItems && !this.hasMore) { return ''; } - const count = this.items.length - this.breakpoint; + if (this.hasMore) { + return __('Load more'); + } + const count = this.items.length - this.breakpoint; return sprintf(__('%{count} more'), { count }); }, }, methods: { expand() { - this.isExpanded = true; - this.$emit('expanded'); + if (this.hasMore && !this.hasHiddenItems) { + this.$emit('load-more'); + } else { + this.isExpanded = true; + this.$emit('expanded'); + } }, collapse() { this.isExpanded = false; @@ -85,8 +102,13 @@ export default { :popover-username="item.username" img-css-classes="gl-mr-3" /> -