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"
/>
-
-
+
+
{{ expandText }}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d2c2fe7a02714885d07497a5e273863059c7cdf5..5cf3e7e2d385123ff050abc8075a2d7a75864d6b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3915,6 +3915,9 @@ msgstr ""
msgid "Achievements|%{namespace_link} awarded you the %{bold_start}%{achievement_name}%{bold_end} achievement!"
msgstr ""
+msgid "Achievements|%{userCount} awarded users"
+msgstr ""
+
msgid "Achievements|Achievement description cannot be longer than %{length} characters."
msgstr ""
diff --git a/spec/frontend/achievements/achievements_app_spec.js b/spec/frontend/achievements/achievements_app_spec.js
index 24c3d32b183205eed93522bbbe84572249d37131..646931dd0b1b96408fa9cef39573b3f0849e8543 100644
--- a/spec/frontend/achievements/achievements_app_spec.js
+++ b/spec/frontend/achievements/achievements_app_spec.js
@@ -107,10 +107,8 @@ describe('Achievements app', () => {
expect(avatarList.exists()).toBe(true);
expect(avatarList.props('items')).toEqual(
expect.arrayContaining([
- getGroupAchievementsResponse.data.group.achievements.nodes[0].userAchievements.nodes[0]
- .user,
- getGroupAchievementsResponse.data.group.achievements.nodes[0].userAchievements.nodes[1]
- .user,
+ getGroupAchievementsResponse.data.group.achievements.nodes[0].uniqueUsers.nodes[0],
+ getGroupAchievementsResponse.data.group.achievements.nodes[0].uniqueUsers.nodes[1],
]),
);
});