Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,13 @@ <h4 class="line-height-3">{{ 'preprints.details.decision.decline.label' | transl

<section class="w-full mt-4 flex flex-column gap-2">
<div class="flex flex-row align-items-start gap-2">
<p-radio-button inputId="accept" class="pt-1" [value]="ReviewsState.Accepted" [(ngModel)]="decision" />
<p-radio-button
inputId="accept"
class="pt-1"
[value]="ReviewsState.Accepted"
[(ngModel)]="decision"
(change)="decisionToggled()"
/>

<label for="accept" class="cursor-pointer">
<h4 class="line-height-3">{{ 'preprints.details.decision.accept.label' | translate }}</h4>
Expand All @@ -106,13 +112,32 @@ <h4 class="line-height-3">{{ 'preprints.details.decision.accept.label' | transla
</div>

<div class="flex flex-row align-items-start gap-2">
<p-radio-button inputId="reject" class="pt-1" [value]="rejectRadioButtonValue()" [(ngModel)]="decision" />
<p-radio-button
inputId="reject"
class="pt-1"
[value]="rejectRadioButtonValue()"
[(ngModel)]="decision"
(change)="decisionToggled()"
/>

<label for="reject" class="cursor-pointer">
<h4 class="line-height-3">{{ rejectOptionLabel() | translate }}</h4>
<p class="line-height-3">{{ rejectOptionExplanation() | translate }}</p>
</label>
</div>

<div class="flex flex-row align-items-start gap-2">
<p-radio-button
inputId="reportSpam"
class="pt-1"
[value]="ReviewActionTrigger.ReportSpam"
[(ngModel)]="decision"
(change)="decisionToggled()"
/>
<label for="reportSpam" class="cursor-pointer">
<h4 class="line-height-3">{{ 'preprints.details.decision.reportSpam.label' | translate }}</h4>
</label>
</div>
</section>

<section class="w-full mt-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SubmitRequestsDecision,
SubmitReviewsDecision,
} from '@osf/features/preprints/store/preprint';
import { ToastService } from '@osf/shared/services/toast.service';

import { PREPRINT_MOCK } from '@testing/mocks/preprint.mock';
import { PREPRINT_PROVIDER_DETAILS_MOCK } from '@testing/mocks/preprint-provider-details';
Expand All @@ -21,6 +22,7 @@ import { REVIEW_ACTION_MOCK } from '@testing/mocks/review-action.mock';
import { provideOSFCore } from '@testing/osf.testing.provider';
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock } from '@testing/providers/toast-provider.mock';

import { PreprintMakeDecisionComponent } from './preprint-make-decision.component';

Expand All @@ -44,6 +46,7 @@ describe('PreprintMakeDecisionComponent', () => {
providers: [
provideOSFCore(),
MockProvider(Router, routerMock),
MockProvider(ToastService, ToastServiceMock.simple()),
provideMockStore({
signals: [{ selector: PreprintSelectors.getPreprint, value: mockPreprint }],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import {
SubmitReviewsDecision,
} from '@osf/features/preprints/store/preprint';
import { InputLimits } from '@osf/shared/constants/input-limits.const';
import { ReviewActionTrigger } from '@osf/shared/enums/trigger-action.enum';
import { StringOrNull } from '@osf/shared/helpers/types.helper';
import { ToastService } from '@osf/shared/services/toast.service';

@Component({
selector: 'osf-preprint-make-decision',
Expand All @@ -38,6 +40,7 @@ import { StringOrNull } from '@osf/shared/helpers/types.helper';
export class PreprintMakeDecisionComponent {
private readonly translateService = inject(TranslateService);
private readonly router = inject(Router);
private readonly toastService = inject(ToastService);

private readonly actions = createDispatchMap({
submitReviewsDecision: SubmitReviewsDecision,
Expand All @@ -53,14 +56,15 @@ export class PreprintMakeDecisionComponent {

dialogVisible = false;
didValidate = signal<boolean>(false);
decision = signal<ReviewsState>(ReviewsState.Accepted);
decision = signal<ReviewsState | ReviewActionTrigger.ReportSpam>(ReviewsState.Accepted);
initialReviewerComment = signal<StringOrNull>(null);
reviewerComment = signal<StringOrNull>(null);
requestDecisionJustification = signal<StringOrNull>(null);
saving = signal<boolean>(false);

readonly decisionCommentLimit = InputLimits.decisionComment.maxLength;
readonly ReviewsState = ReviewsState;
readonly ReviewActionTrigger = ReviewActionTrigger;

labelDecisionButton = computed(() => {
const preprint = this.preprint();
Expand Down Expand Up @@ -99,7 +103,11 @@ export class PreprintMakeDecisionComponent {
const preprint = this.preprint();
const reviewsState = preprint?.reviewsState;

if (this.isPendingWithdrawal() || reviewsState === ReviewsState.Pending) {
if (
this.decision() === ReviewActionTrigger.ReportSpam ||
this.isPendingWithdrawal() ||
reviewsState === ReviewsState.Pending
) {
return 'preprints.details.decision.submitButton.submitDecision';
}

Expand Down Expand Up @@ -231,6 +239,11 @@ export class PreprintMakeDecisionComponent {
const preprint = this.preprint();
if (!preprint) return;

if (this.decision() === ReviewActionTrigger.ReportSpam) {
this.reportSpam();
return;
}

let trigger;
if (preprint.reviewsState !== ReviewsState.Pending && this.commentEdited() && !this.decisionChanged()) {
// If the submission is not pending,
Expand Down Expand Up @@ -306,6 +319,32 @@ export class PreprintMakeDecisionComponent {
}
}

decisionToggled() {
if (this.decision() === ReviewActionTrigger.ReportSpam) {
this.reviewerComment.set(null);
} else {
this.reviewerComment.set(this.initialReviewerComment());
}
}

private reportSpam() {
const preprint = this.preprint();
if (!preprint) return;

this.saving.set(true);
this.actions
.submitReviewsDecision(ReviewActionTrigger.ReportSpam, this.reviewerComment()?.trim() || null)
.pipe(finalize(() => this.saving.set(false)))
.subscribe({
next: () => {
this.toastService.showSuccess('preprints.details.decision.reportSpam.success');
this.dialogVisible = false;
this.decision.set(preprint.reviewsState);
this.reviewerComment.set(this.initialReviewerComment());
},
});
}

requestDecisionToggled() {
if (!this.isPendingWithdrawal()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { PrerenderReadyService } from '@core/services/prerender-ready.service';
import { ClearCurrentProvider } from '@core/store/provider';
import { UserSelectors } from '@core/store/user';
import { ReviewPermissions } from '@osf/shared/enums/review-permissions.enum';
import { ReviewActionTrigger } from '@osf/shared/enums/trigger-action.enum';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
import { DataciteService } from '@osf/shared/services/datacite/datacite.service';
import { LoaderService } from '@osf/shared/services/loader.service';
Expand Down Expand Up @@ -147,7 +148,8 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
});

latestAction = computed(() => {
const actions = this.reviewActions();
// banners and the decision dialog describe the last decision, and a spam report is not one
const actions = this.reviewActions().filter((action) => action.trigger !== ReviewActionTrigger.ReportSpam);
return actions.length > 0 ? actions[0] : null;
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="flex flex-column gap-4">
@for (action of reviewActions(); track $index) {
@for (action of decisionActions(); track $index) {
<p class="flex flex-wrap gap-1">
@switch (action.toState) {
@case (SubmissionReviewStatus.Pending) {
Expand Down Expand Up @@ -63,6 +63,17 @@
}
</label>
</div>

<div class="flex gap-2">
<p-radioButton
[formControlName]="ModerationDecisionFormControls.Action"
[inputId]="ReviewActionTrigger.ReportSpam"
[value]="ReviewActionTrigger.ReportSpam"
></p-radioButton>
<label [for]="ReviewActionTrigger.ReportSpam" class="m-0 flex gap-2 flex-column">
<p class="font-bold">{{ 'moderation.makeDecision.reportSpam' | translate }}</p>
</label>
</div>
</div>

<div class="flex flex-column gap-1">
Expand Down Expand Up @@ -92,6 +103,17 @@
<p>{{ 'moderation.makeDecision.forceWithdrawMessage' | translate }}</p>
</label>
</div>

<div class="flex gap-2">
<p-radioButton
[formControlName]="ModerationDecisionFormControls.Action"
[inputId]="ReviewActionTrigger.ReportSpam"
[value]="ReviewActionTrigger.ReportSpam"
></p-radioButton>
<label [for]="ReviewActionTrigger.ReportSpam" class="m-0 flex gap-2 flex-column">
<p class="font-bold">{{ 'moderation.makeDecision.reportSpam' | translate }}</p>
</label>
</div>
</div>

<div class="flex flex-column gap-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { RegistrationReviewStates } from '@osf/shared/enums/registration-review-
import { RevisionReviewStates } from '@osf/shared/enums/revision-review-states.enum';
import { ReviewActionTrigger, SchemaResponseActionTrigger } from '@osf/shared/enums/trigger-action.enum';
import { DateAgoPipe } from '@osf/shared/pipes/date-ago.pipe';
import { ToastService } from '@osf/shared/services/toast.service';

import { MOCK_REGISTRATION_OVERVIEW_MODEL } from '@testing/mocks/registration-overview-model.mock';
import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock } from '@testing/providers/toast-provider.mock';

import { RegistrySelectors } from '../../store/registry';

Expand All @@ -43,6 +45,7 @@ function setup(overrides: SetupOverrides = {}) {
providers: [
provideOSFCore(),
provideDynamicDialogRefMock(),
MockProvider(ToastService, ToastServiceMock.simple()),
MockProvider(DynamicDialogConfig, { data: { registry, revisionId } }),
provideMockStore({
signals: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { RadioButton } from 'primeng/radiobutton';
import { Textarea } from 'primeng/textarea';

import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

Expand Down Expand Up @@ -45,6 +45,9 @@ export class RegistryMakeDecisionComponent {
readonly decisionCommentLimit = InputLimits.decisionComment.maxLength;

readonly reviewActions = select(RegistrySelectors.getReviewActions);
readonly decisionActions = computed(() =>
this.reviewActions().filter((action) => action.trigger !== ReviewActionTrigger.ReportSpam)
);
readonly isSubmitting = select(RegistrySelectors.isReviewActionSubmitting);

private readonly actions = createDispatchMap({ submitDecision: SubmitDecision });
Expand Down Expand Up @@ -94,12 +97,14 @@ export class RegistryMakeDecisionComponent {
}

handleSubmission(): void {
const revisionId = this.config.data.revisionId;
const action = this.requestForm.value[ModerationDecisionFormControls.Action] ?? '';
// a spam report is related to the registration and its submitter, so a revision is never its target
const revisionId = action === ReviewActionTrigger.ReportSpam ? null : this.config.data.revisionId;
this.actions
.submitDecision(
{
targetId: revisionId ? revisionId : this.registry.id,
action: this.requestForm.value[ModerationDecisionFormControls.Action] ?? '',
action,
comment: this.requestForm.value[ModerationDecisionFormControls.Comment] ?? '',
},
!!revisionId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ViewOnlyLinkMessageComponent } from '@osf/shared/components/view-only-l
import { RegistrationReviewStates } from '@osf/shared/enums/registration-review-states.enum';
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { RevisionReviewStates } from '@osf/shared/enums/revision-review-states.enum';
import { ReviewActionTrigger } from '@osf/shared/enums/trigger-action.enum';
import { toCamelCase } from '@osf/shared/helpers/camel-case';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
import { LoaderService } from '@osf/shared/services/loader.service';
Expand Down Expand Up @@ -230,6 +231,12 @@ export class RegistryOverviewComponent implements OnInit, OnDestroy {
this.toastService.showSuccess(`moderation.makeDecision.${action}Success`);
}

// a spam report leaves the registration untouched, and the reload below drops the
// moderator query param, which would hide the 'Make decision' button
if (data.action === ReviewActionTrigger.ReportSpam) {
return;
}

const currentUrl = this.router.url.split('?')[0];
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
this.router.navigateByUrl(currentUrl);
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/enums/trigger-action.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum ReviewActionTrigger {
AcceptWithdrawal = 'accept_withdrawal',
RejectWithdrawal = 'reject_withdrawal',
RequestEmbargoTermination = 'request_embargo_termination',
ReportSpam = 'report_spam',
}

export enum SchemaResponseActionTrigger {
Expand Down
6 changes: 6 additions & 0 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,8 @@
"rejectWithdrawalMessage": "Submission will remain fully public",
"remarksPlaceholder": "Add remarks to registration admins",
"removeSuccess": "Submission has been withdrawn successfully",
"reportSpam": "Report spam",
"reportSpamSuccess": "Submission has been reported as spam",
"submitDecision": "Submit decision",
"withdrawRequest": "Withdraw Request",
"withdrawRequestMessage": "Submission will be withdrawn from the collection and will no longer appear in search results."
Expand Down Expand Up @@ -1332,6 +1334,10 @@
"post": "Submission will be removed from search results and made private.",
"pre": "Submission will not appear in search results and will remain private."
},
"reportSpam": {
"label": "Report spam",
"success": "Submission has been reported as spam"
},
"settings": {
"comments": {
"private": "Comments are not visible to contributors",
Expand Down
Loading