Skip to content

Commit 95f101c

Browse files
authored
[ENG-9807] show contributors for moderation cards for registry and collections (#812)
- Ticket: https://openscience.atlassian.net/browse/ENG-9807 - Feature flag: n/a ## Purpose Right now, moderation cards across the platform only show one author, even when a submission has many. This is the case for preprints, registrations, and collections/projects. Moderators have no quick way to tell whether something is single-author or multi-author without opening the full submission. This slows down triage and makes it harder to spot common patterns in spam, out-of-scope content, or AI-generated submissions, where author lists are often a giveaway. We need to surface more authors directly on the card so moderators can make faster, more informed decisions. ## Summary of Changes show contributors for moderation cards for registry and collections
1 parent 55581df commit 95f101c

16 files changed

+484
-110
lines changed

src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.html

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,81 @@
22
@let attributes = currentSubmissionAttributes();
33

44
@if (action && attributes) {
5-
<div class="p-3">
6-
<div class="flex align-items-center gap-2">
7-
<osf-icon
8-
[class]="reviewStatusIcon[submission().reviewsState].value"
9-
[iconClass]="reviewStatusIcon[submission().reviewsState].icon"
10-
></osf-icon>
5+
<div class="resource">
6+
<p-accordion (onOpen)="handleOpen()">
7+
<p-accordion-panel value="0">
8+
<p-accordion-header class="flex flex-row align-items-start gap-3 p-3">
9+
<div osfStopPropagation class="flex align-items-baseline gap-2 font-normal">
10+
<div class="p-3">
11+
<div class="flex align-items-center gap-2">
12+
<osf-icon
13+
[class]="reviewStatusIcon[submission().reviewsState].value"
14+
[iconClass]="reviewStatusIcon[submission().reviewsState].icon"
15+
></osf-icon>
1116

12-
<p-button class="link-btn-no-padding" link (onClick)="handleNavigation()" [label]="submission().title" />
13-
</div>
17+
<p-button
18+
class="link-btn-no-padding"
19+
link
20+
(onClick)="handleNavigation()"
21+
[label]="submission().title"
22+
/>
23+
</div>
1424

15-
<p class="flex flex-wrap gap-1 mt-2">
16-
@switch (action.toState) {
17-
@case (SubmissionReviewStatus.Pending) {
18-
<span class="capitalize">{{ 'moderation.submissionReview.submitted' | translate }}</span>
19-
}
20-
@case (SubmissionReviewStatus.Accepted) {
21-
@if (!collectionProvider()?.reviewsWorkflow) {
22-
<span class="capitalize">{{ 'moderation.submissionReview.submitted' | translate }}</span>
23-
} @else {
24-
<span class="capitalize">{{ 'moderation.submissionReview.accepted' | translate }}</span>
25-
}
26-
}
27-
@case (SubmissionReviewStatus.Rejected) {
28-
<span class="capitalize">{{ 'moderation.submissionReview.rejected' | translate }}</span>
29-
}
30-
@case (SubmissionReviewStatus.Removed) {
31-
<span class="capitalize">{{ 'moderation.submissionReview.withdrawn' | translate }}</span>
32-
}
33-
}
34-
<span>{{ action.dateCreated | dateAgo }}</span>
35-
<span>{{ 'moderation.submissionReview.by' | translate }}</span>
36-
<span>{{ action.createdBy }}</span>
37-
</p>
25+
<p class="flex flex-wrap gap-1 mt-2">
26+
@switch (action.toState) {
27+
@case (SubmissionReviewStatus.Pending) {
28+
<span class="capitalize">{{ 'moderation.submissionReview.submitted' | translate }}</span>
29+
}
30+
@case (SubmissionReviewStatus.Accepted) {
31+
@if (!collectionProvider()?.reviewsWorkflow) {
32+
<span class="capitalize">{{ 'moderation.submissionReview.submitted' | translate }}</span>
33+
} @else {
34+
<span class="capitalize">{{ 'moderation.submissionReview.accepted' | translate }}</span>
35+
}
36+
}
37+
@case (SubmissionReviewStatus.Rejected) {
38+
<span class="capitalize">{{ 'moderation.submissionReview.rejected' | translate }}</span>
39+
}
40+
@case (SubmissionReviewStatus.Removed) {
41+
<span class="capitalize">{{ 'moderation.submissionReview.withdrawn' | translate }}</span>
42+
}
43+
}
44+
<span>{{ action.dateCreated | dateAgo }}</span>
45+
<span>{{ 'moderation.submissionReview.by' | translate }}</span>
46+
<span>{{ action.createdBy }}</span>
47+
</p>
3848

39-
<div class="flex mt-2 flex-wrap">
40-
@for (attribute of attributes; track attribute.key) {
41-
@if (!$first) {
42-
<span class="mx-1 flex align-items-start">|</span>
43-
}
44-
<p class="font-normal">
45-
<span class="font-bold">{{ attribute.label }}:</span> {{ attribute.value }}
46-
</p>
47-
}
48-
</div>
49+
<div class="flex mt-2 flex-wrap">
50+
@for (attribute of attributes; track attribute.key) {
51+
@if (!$first) {
52+
<span class="mx-1 flex align-items-start">|</span>
53+
}
54+
<p class="font-normal">
55+
<span class="font-bold">{{ attribute.label }}:</span> {{ attribute.value }}
56+
</p>
57+
}
58+
</div>
4959

50-
@if (action.comment) {
51-
<osf-truncated-text class="block font-italic mt-2" [text]="action.comment" />
52-
}
60+
@if (action.comment) {
61+
<osf-truncated-text class="block font-italic mt-2" [text]="action.comment" />
62+
}
63+
</div>
64+
</div>
65+
</p-accordion-header>
66+
67+
<p-accordion-content>
68+
<div class="flex align-content-start gap-1 p-3 pt-0 ml-3">
69+
<p>{{ 'common.labels.contributors' | translate }}:</p>
70+
<osf-contributors-list
71+
[contributors]="submission().contributors!"
72+
[isLoading]="!!submission().contributorsLoading"
73+
[hasLoadMore]="hasMoreContributors()"
74+
(loadMoreContributors)="loadMoreContributors.emit()"
75+
>
76+
</osf-contributors-list>
77+
</div>
78+
</p-accordion-content>
79+
</p-accordion-panel>
80+
</p-accordion>
5381
</div>
5482
}

src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { select } from '@ngxs/store';
22

33
import { TranslatePipe } from '@ngx-translate/core';
44

5+
import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion';
56
import { Button } from 'primeng/button';
67

7-
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
8+
import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core';
89
import { ActivatedRoute, Router } from '@angular/router';
910

1011
import { collectionFilterNames } from '@osf/features/collections/constants';
12+
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
1113
import { IconComponent } from '@osf/shared/components/icon/icon.component';
1214
import { TruncatedTextComponent } from '@osf/shared/components/truncated-text/truncated-text.component';
1315
import { CollectionSubmissionWithGuid } from '@osf/shared/models/collections/collections.models';
@@ -19,7 +21,18 @@ import { SubmissionReviewStatus } from '../../enums';
1921

2022
@Component({
2123
selector: 'osf-submission-item',
22-
imports: [TranslatePipe, IconComponent, DateAgoPipe, Button, TruncatedTextComponent],
24+
imports: [
25+
TranslatePipe,
26+
IconComponent,
27+
DateAgoPipe,
28+
Button,
29+
TruncatedTextComponent,
30+
Accordion,
31+
AccordionPanel,
32+
AccordionHeader,
33+
AccordionContent,
34+
ContributorsListComponent,
35+
],
2336
templateUrl: './collection-submission-item.component.html',
2437
styleUrl: './collection-submission-item.component.scss',
2538
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -29,7 +42,8 @@ export class CollectionSubmissionItemComponent {
2942
private activatedRoute = inject(ActivatedRoute);
3043

3144
submission = input.required<CollectionSubmissionWithGuid>();
32-
45+
loadContributors = output<void>();
46+
loadMoreContributors = output<void>();
3347
collectionProvider = select(CollectionsSelectors.getCollectionProvider);
3448

3549
readonly reviewStatusIcon = ReviewStatusIcon;
@@ -55,6 +69,15 @@ export class CollectionSubmissionItemComponent {
5569
.filter((attribute) => attribute.value);
5670
});
5771

72+
hasMoreContributors = computed(() => {
73+
const submission = this.submission();
74+
if (submission.contributors && submission.totalContributors) {
75+
return submission.contributors.length < submission.totalContributors;
76+
}
77+
78+
return false;
79+
});
80+
5881
handleNavigation() {
5982
const currentStatus = this.activatedRoute.snapshot.queryParams['status'];
6083
const queryParams = currentStatus ? { status: currentStatus, mode: 'moderation' } : {};
@@ -68,4 +91,8 @@ export class CollectionSubmissionItemComponent {
6891

6992
window.open(url, '_blank');
7093
}
94+
95+
handleOpen() {
96+
this.loadContributors.emit();
97+
}
7198
}

src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
<div class="submission-container mt-5">
33
@for (item of submissions(); track $index) {
44
<div class="submission-item">
5-
<osf-submission-item [submission]="item"></osf-submission-item>
5+
<osf-submission-item
6+
[submission]="item"
7+
(loadContributors)="loadContributors(item)"
8+
(loadMoreContributors)="loadMoreContributors(item)"
9+
>
10+
</osf-submission-item>
611
</div>
712
}
813
</div>

src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import { select } from '@ngxs/store';
1+
import { createDispatchMap, select } from '@ngxs/store';
22

33
import { TranslatePipe } from '@ngx-translate/core';
44

55
import { ChangeDetectionStrategy, Component } from '@angular/core';
66

7+
import {
8+
GetCollectionSubmissionContributors,
9+
LoadMoreCollectionSubmissionContributors,
10+
} from '@osf/features/moderation/store/collections-moderation';
11+
import { CollectionSubmissionWithGuid } from '@shared/models/collections/collections.models';
12+
713
import { CollectionsModerationSelectors } from '../../store/collections-moderation';
814
import { CollectionSubmissionItemComponent } from '../collection-submission-item/collection-submission-item.component';
915

@@ -16,4 +22,17 @@ import { CollectionSubmissionItemComponent } from '../collection-submission-item
1622
})
1723
export class CollectionSubmissionsListComponent {
1824
submissions = select(CollectionsModerationSelectors.getCollectionSubmissions);
25+
26+
readonly actions = createDispatchMap({
27+
getCollectionSubmissionContributors: GetCollectionSubmissionContributors,
28+
loadMoreCollectionSubmissionContributors: LoadMoreCollectionSubmissionContributors,
29+
});
30+
31+
loadContributors(item: CollectionSubmissionWithGuid) {
32+
this.actions.getCollectionSubmissionContributors(item.id, 1);
33+
}
34+
35+
loadMoreContributors(item: CollectionSubmissionWithGuid) {
36+
this.actions.loadMoreCollectionSubmissionContributors(item.id);
37+
}
1938
}

src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
[submission]="item"
4040
[status]="selectedReviewOption()"
4141
(selected)="navigateToRegistration(item)"
42+
(loadContributors)="loadContributors(item)"
43+
(loadMoreContributors)="loadMoreContributors(item)"
4244
></osf-registry-submission-item>
4345
</div>
4446
}

src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import { Primitive } from '@osf/shared/helpers/types.helper';
2424
import { PENDING_SUBMISSION_REVIEW_OPTIONS, REGISTRY_SORT_OPTIONS } from '../../constants';
2525
import { RegistrySort, SubmissionReviewStatus } from '../../enums';
2626
import { RegistryModeration } from '../../models';
27-
import { GetRegistrySubmissions, RegistryModerationSelectors } from '../../store/registry-moderation';
27+
import {
28+
GetRegistrySubmissionContributors,
29+
GetRegistrySubmissions,
30+
LoadMoreRegistrySubmissionContributors,
31+
RegistryModerationSelectors,
32+
} from '../../store/registry-moderation';
2833
import { RegistrySubmissionItemComponent } from '../registry-submission-item/registry-submission-item.component';
2934

3035
@Component({
@@ -54,7 +59,11 @@ export class RegistryPendingSubmissionsComponent implements OnInit {
5459
this.route.parent?.params.pipe(map((params) => params['providerId'])) ?? of(undefined)
5560
);
5661

57-
readonly actions = createDispatchMap({ getRegistrySubmissions: GetRegistrySubmissions });
62+
readonly actions = createDispatchMap({
63+
getRegistrySubmissions: GetRegistrySubmissions,
64+
getRegistrySubmissionContributors: GetRegistrySubmissionContributors,
65+
loadMoreRegistrySubmissionContributors: LoadMoreRegistrySubmissionContributors,
66+
});
5867

5968
readonly submissions = select(RegistryModerationSelectors.getRegistrySubmissions);
6069
readonly isLoading = select(RegistryModerationSelectors.areRegistrySubmissionLoading);
@@ -112,6 +121,14 @@ export class RegistryPendingSubmissionsComponent implements OnInit {
112121
window.open(url, '_blank');
113122
}
114123

124+
loadContributors(item: RegistryModeration) {
125+
this.actions.getRegistrySubmissionContributors(item.id);
126+
}
127+
128+
loadMoreContributors(item: RegistryModeration) {
129+
this.actions.loadMoreRegistrySubmissionContributors(item.id);
130+
}
131+
115132
private getStatusFromQueryParams() {
116133
const queryParams = this.route.snapshot.queryParams;
117134
const statusValues = Object.values(SubmissionReviewStatus);

0 commit comments

Comments
 (0)