Skip to content

Commit 6d47cc7

Browse files
committed
feat(preprint-withdrawal): Implement withdrawal functionality
1 parent 666b6c9 commit 6d47cc7

24 files changed

Lines changed: 455 additions & 61 deletions

src/app/features/moderation/models/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export * from './preprint-review-action.model';
1111
export * from './preprint-review-action-json-api.model';
1212
export * from './preprint-submission.model';
1313
export * from './preprint-submission-json-api.model';
14-
export * from './preprint-withdrawal-action.model';
1514
export * from './preprint-withdrawal-submission.model';
1615
export * from './preprint-withdrawal-submission-json-api.model';
1716
export * from './registry-json-api.model';

src/app/features/moderation/models/preprint-withdrawal-action.model.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/app/features/preprints/components/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ export { PreprintsCreatorsFilterComponent } from './filters/preprints-creators-f
44
export { PreprintsDateCreatedFilterComponent } from './filters/preprints-date-created-filter/preprints-date-created-filter.component';
55
export { PreprintsInstitutionFilterComponent } from './filters/preprints-institution-filter/preprints-institution-filter.component';
66
export { PreprintsLicenseFilterComponent } from './filters/preprints-license-filter/preprints-license-filter.component';
7+
export { AdditionalInfoComponent } from './preprint-details/additional-info/additional-info.component';
8+
export { GeneralInformationComponent } from './preprint-details/general-information/general-information.component';
9+
export { PreprintFileSectionComponent } from './preprint-details/preprint-file-section/preprint-file-section.component';
10+
export { ShareAndDownloadComponent } from './preprint-details/share-and-downlaod/share-and-download.component';
11+
export { StatusBannerComponent } from './preprint-details/status-banner/status-banner.component';
712
export { PreprintProviderFooterComponent } from './preprint-provider-footer/preprint-provider-footer.component';
813
export { PreprintProviderHeroComponent } from './preprint-provider-hero/preprint-provider-hero.component';
914
export { PreprintServicesComponent } from './preprint-services/preprint-services.component';
@@ -14,6 +19,7 @@ export { PreprintsFilterChipsComponent } from '@osf/features/preprints/component
1419
export { PreprintsResourcesComponent } from '@osf/features/preprints/components/filters/preprints-resources/preprints-resources.component';
1520
export { PreprintsResourcesFiltersComponent } from '@osf/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component';
1621
export { PreprintsSubjectFilterComponent } from '@osf/features/preprints/components/filters/preprints-subject-filter/preprints-subject-filter.component';
22+
export { WithdrawDialogComponent } from '@osf/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component';
1723
export { FileStepComponent } from '@osf/features/preprints/components/stepper/file-step/file-step.component';
1824
export { MetadataStepComponent } from '@osf/features/preprints/components/stepper/metadata-step/metadata-step.component';
1925
export { ReviewStepComponent } from '@osf/features/preprints/components/stepper/review-step/review-step.component';

src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,6 @@ SEVERITIES[ReviewsState.PendingWithdrawal] = 'error';
5252
SEVERITIES[ReviewsState.WithdrawalRejected] = 'error';
5353
SEVERITIES[ReviewsState.Withdrawn] = 'warn';
5454

55-
//1. pending status for pre- and post-moderation providers | works
56-
//2. accepted status for pre- and post-moderation providers | works
57-
//3. rejected status for pre-moderation | works
58-
//4. withdrawn status for post-moderation | works
59-
60-
//[RNi] TODO: check pending withdrawal and withdrawal rejected status for pre- and post-moderation providers
61-
6255
@Component({
6356
selector: 'osf-preprint-status-banner',
6457
imports: [TranslatePipe, TitleCasePipe, Message, Dialog, Tag, Button],
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<div>
2+
<p>
3+
You are about to withdraw this version of your &#123;singularPreprintWord&#125;. Withdrawing a version will remove
4+
it from public view but will not affect other versions of this &#123;singularPreprintWord&#125;, if available.
5+
</p>
6+
<p [innerHTML]="modalExplanation()"></p>
7+
</div>
8+
9+
<div class="flex flex-column m-t-24">
10+
<label for="withdrawalJustification">{{ 'Reason for withdrawal' | translate | titlecase }}</label>
11+
<textarea
12+
pTextarea
13+
id="withdrawalJustification"
14+
[rows]="4"
15+
placeholder="Comment"
16+
[formControl]="withdrawalJustificationFormControl"
17+
></textarea>
18+
@let control = withdrawalJustificationFormControl;
19+
@if (control.errors?.['required'] && (control.touched || control.dirty)) {
20+
<p-message class="simple-variant flex mt-1" severity="error" variant="simple" size="small">
21+
{{ INPUT_VALIDATION_MESSAGES.required | translate }}
22+
</p-message>
23+
}
24+
@if (control.errors?.['minlength'] && (control.touched || control.dirty)) {
25+
<p-message class="simple-variant flex mt-1" severity="error" variant="simple" size="small">
26+
{{ 'Comment must be at least 25 characters.' | translate: { length: inputLimits.abstract.minLength } }}
27+
</p-message>
28+
}
29+
</div>
30+
31+
<div class="flex justify-content-end gap-2 m-t-48">
32+
<p-button
33+
class="w-12rem btn-full-width"
34+
[label]="'common.buttons.cancel' | translate"
35+
severity="info"
36+
(click)="dialogRef.close()"
37+
/>
38+
<p-button
39+
class="w-12rem btn-full-width"
40+
[label]="'common.buttons.withdraw' | translate"
41+
(click)="withdraw()"
42+
[loading]="withdrawRequestInProgress()"
43+
[disabled]="withdrawalJustificationFormControl.invalid"
44+
severity="danger"
45+
/>
46+
</div>

src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.scss

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { WithdrawDialogComponent } from './withdraw-dialog.component';
4+
5+
describe.skip('WithdrawDialogComponent', () => {
6+
let component: WithdrawDialogComponent;
7+
let fixture: ComponentFixture<WithdrawDialogComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [WithdrawDialogComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(WithdrawDialogComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { createDispatchMap } from '@ngxs/store';
2+
3+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
4+
5+
import { Button } from 'primeng/button';
6+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
7+
import { Message } from 'primeng/message';
8+
import { Textarea } from 'primeng/textarea';
9+
10+
import { TitleCasePipe } from '@angular/common';
11+
import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
12+
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
13+
14+
import { formInputLimits } from '@osf/features/preprints/constants';
15+
import { ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums';
16+
import { getPreprintDocumentType } from '@osf/features/preprints/helpers';
17+
import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models';
18+
import { WithdrawPreprint } from '@osf/features/preprints/store/preprint';
19+
import { INPUT_VALIDATION_MESSAGES } from '@shared/constants';
20+
import { CustomValidators } from '@shared/utils';
21+
22+
@Component({
23+
selector: 'osf-withdraw-dialog',
24+
imports: [Textarea, ReactiveFormsModule, Message, TranslatePipe, Button, TitleCasePipe],
25+
templateUrl: './withdraw-dialog.component.html',
26+
styleUrl: './withdraw-dialog.component.scss',
27+
changeDetection: ChangeDetectionStrategy.OnPush,
28+
})
29+
export class WithdrawDialogComponent implements OnInit {
30+
private readonly config = inject(DynamicDialogConfig);
31+
private readonly translateService = inject(TranslateService);
32+
readonly dialogRef = inject(DynamicDialogRef);
33+
34+
private provider!: PreprintProviderDetails;
35+
private preprint!: Preprint;
36+
37+
private actions = createDispatchMap({
38+
withdrawPreprint: WithdrawPreprint,
39+
});
40+
41+
protected inputLimits = formInputLimits;
42+
protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES;
43+
44+
withdrawalJustificationFormControl = new FormControl('', {
45+
nonNullable: true,
46+
validators: [
47+
CustomValidators.requiredTrimmed(),
48+
Validators.minLength(this.inputLimits.withdrawalJustification.minLength),
49+
],
50+
});
51+
modalExplanation = signal<string>('');
52+
withdrawRequestInProgress = signal<boolean>(false);
53+
54+
public ngOnInit() {
55+
this.provider = this.config.data.provider;
56+
this.preprint = this.config.data.preprint;
57+
58+
this.modalExplanation.set(this.calculateModalExplanation());
59+
}
60+
61+
withdraw() {
62+
if (this.withdrawalJustificationFormControl.invalid) {
63+
return;
64+
}
65+
66+
const withdrawalJustification = this.withdrawalJustificationFormControl.value;
67+
this.withdrawRequestInProgress.set(true);
68+
this.actions.withdrawPreprint(this.preprint.id, withdrawalJustification).subscribe({
69+
complete: () => {
70+
this.withdrawRequestInProgress.set(false);
71+
this.dialogRef.close(true);
72+
},
73+
error: () => {
74+
this.withdrawRequestInProgress.set(false);
75+
},
76+
});
77+
}
78+
79+
private calculateModalExplanation() {
80+
const providerReviewWorkflow = this.provider.reviewsWorkflow;
81+
const documentType = getPreprintDocumentType(this.provider, this.translateService);
82+
//[RNi] TODO: maybe extract to env, also see static pages
83+
const supportEmail = 'support@osf.io';
84+
85+
switch (providerReviewWorkflow) {
86+
case ProviderReviewsWorkflow.PreModeration: {
87+
if (this.preprint.reviewsState === ReviewsState.Pending) {
88+
return this.translateService.instant(
89+
'Since this version is still pending approval and private, it can be withdrawn immediately. ' +
90+
'The reason of withdrawal will be visible to service moderators. Once withdrawn, the {{singularPreprintWord}} ' +
91+
'will remain private and never be made public.',
92+
{
93+
singularPreprintWord: documentType.singular,
94+
}
95+
);
96+
} else
97+
return this.translateService.instant(
98+
'<strong>{{pluralCapitalizedPreprintWord}} are a permanent part of the scholarly record.' +
99+
' Withdrawal requests are subject to this service’s policy on {{singularPreprintWord}} version' +
100+
' removal and at the discretion of the moderators.</strong><br>This service uses pre-moderation. ' +
101+
'This request will be submitted to service moderators for review. If the request is approved, this ' +
102+
'{singularPreprintWord} version will be replaced by a tombstone page with metadata and the reason ' +
103+
'for withdrawal. This {singularPreprintWord} version will still be searchable by other users after removal.',
104+
{
105+
singularPreprintWord: documentType.singular,
106+
pluralCapitalizedPreprintWord: documentType.pluralCapitalized,
107+
}
108+
);
109+
}
110+
case ProviderReviewsWorkflow.PostModeration: {
111+
return this.translateService.instant(
112+
'<strong>{pluralCapitalizedPreprintWord} are a permanent part of the scholarly record. ' +
113+
'Withdrawal requests are subject to this service’s policy on {singularPreprintWord} version ' +
114+
'removal and at the discretion of the moderators.</strong><br>This service uses post-moderation.' +
115+
' This request will be submitted to service moderators for review. If the request is approved, this ' +
116+
'{singularPreprintWord} version will be replaced by a tombstone page with metadata and the reason for' +
117+
' withdrawal. This {singularPreprintWord} version will still be searchable by other users after removal.',
118+
{
119+
singularPreprintWord: documentType.singular,
120+
pluralCapitalizedPreprintWord: documentType.pluralCapitalized,
121+
}
122+
);
123+
}
124+
default: {
125+
return this.translateService.instant(
126+
'<strong>{pluralCapitalizedPreprintWord} are a permanent part of the scholarly record. ' +
127+
'Withdrawal requests are subject to this service’s policy on {singularPreprintWord} version removal' +
128+
' and at the discretion of the moderators.</strong><br>This request will be submitted to' +
129+
' <a href="mailto:{supportEmail}" target="_blank">{supportEmail}</a> for review and removal.' +
130+
' If the request is approved, this {singularPreprintWord} version will be replaced by a tombstone' +
131+
' page with metadata and the reason for withdrawal. This {singularPreprintWord} version will still be ' +
132+
'searchable by other users after removal.',
133+
{
134+
singularPreprintWord: documentType.singular,
135+
pluralCapitalizedPreprintWord: documentType.pluralCapitalized,
136+
supportEmail,
137+
}
138+
);
139+
}
140+
}
141+
}
142+
}

src/app/features/preprints/constants/form-input-limits.const.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ export const formInputLimits = {
1212
citation: {
1313
maxLength: 500,
1414
},
15+
withdrawalJustification: {
16+
minLength: 25,
17+
},
1518
};

src/app/features/preprints/enums/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export { ApplicabilityStatus } from './applicability-status.enum';
22
export { PreprintFileSource } from './preprint-file-source.enum';
3+
export { PreprintRequestType } from './preprint-request.type';
4+
export { PreprintRequestMachineState } from './preprint-request-machine.state';
35
export { PreprintSteps } from './preprint-steps.enum';
46
export { PreregLinkInfo } from './prereg-link-info.enum';
57
export { ProviderReviewsWorkflow } from './provider-reviews-workflow.enum';

0 commit comments

Comments
 (0)