diff --git a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.html b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.html index 36592e717..f8501c0a9 100644 --- a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.html +++ b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.html @@ -1,170 +1,48 @@ -
- - - {{ 'moderation.preprintSettings.warning' | translate }} - support@osf.io - {{ 'moderation.preprintSettings.warningForAssistance' | translate }} - - - -
- @for (section of sections; track $index) { - -

{{ section.title | translate }}

- - @if (section.description) { -

{{ section.description | translate }}

- } - -
- @for (option of section.options; track $index) { -
- -
- -

- {{ option.description | translate }} -

-
-
+@if (isLoading()) { +
+ +
+} @else { +
+ + + {{ 'moderation.preprintSettings.warning' | translate }} +

{{ settings()?.supportEmail ?? supportEmail }}

+ {{ 'moderation.preprintSettings.warningForAssistance' | translate }} +
+
+ + + @for (section of sections; track $index) { + +

{{ section.title | translate }}

+ + @if (section.description) { +

{{ section.description | translate }}

} -
- - } - - - -
+
+ } + +
+} diff --git a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.ts b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.ts index 026be61a0..05022ef15 100644 --- a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.ts +++ b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.ts @@ -1,38 +1,79 @@ +import { createDispatchMap, select, Store } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; import { RadioButton } from 'primeng/radiobutton'; -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { map, of } from 'rxjs'; + +import { ChangeDetectionStrategy, Component, computed, effect, inject, OnInit } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; + +import { LoadingSpinnerComponent } from '@osf/shared/components'; +import { DEFAULT_SUPPORT_EMAIL } from '@osf/shared/constants'; import { PREPRINT_SETTINGS_SECTIONS } from '../../constants'; -import { CommentVisibilityType, ModerationType, ModeratorsCommentsType, SettingsSectionControl } from '../../enums'; +import { SettingsSectionControl } from '../../enums'; +import { GetPreprintProvider, PreprintModerationSelectors } from '../../store/preprint-moderation'; @Component({ selector: 'osf-preprint-moderation-settings', - imports: [TranslatePipe, ReactiveFormsModule, Card, RadioButton, Message], + imports: [TranslatePipe, ReactiveFormsModule, Card, RadioButton, Message, LoadingSpinnerComponent], templateUrl: './preprint-moderation-settings.component.html', styleUrl: './preprint-moderation-settings.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class PreprintModerationSettingsComponent implements OnInit { - private fb = inject(FormBuilder); + private readonly route = inject(ActivatedRoute); + private readonly store = inject(Store); + private readonly fb = inject(FormBuilder); + + private readonly actions = createDispatchMap({ getPreprintProvider: GetPreprintProvider }); + readonly providerId = toSignal(this.route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined)); + settingsForm!: FormGroup; sections = PREPRINT_SETTINGS_SECTIONS; + supportEmail = DEFAULT_SUPPORT_EMAIL; + + isLoading = select(PreprintModerationSelectors.arePreprintProviderLoading); + + settings = computed(() => + this.store.selectSignal(PreprintModerationSelectors.getPreprintProvider)()(this.providerId()) + ); + + constructor() { + effect(() => { + if (this.settings()) { + this.updateForm(); + } + }); + } + ngOnInit(): void { + this.actions.getPreprintProvider(this.providerId()); this.initForm(); } private initForm(): void { this.settingsForm = this.fb.group({ - [SettingsSectionControl.ModerationType]: [ModerationType.Pre], - [SettingsSectionControl.CommentVisibility]: [CommentVisibilityType.Moderators], - [SettingsSectionControl.ModeratorComments]: [ModeratorsCommentsType.Anonymized], + [SettingsSectionControl.ModerationType]: this.settings()?.reviewsWorkflow, + [SettingsSectionControl.CommentVisibility]: this.settings()?.reviewsCommentsPrivate, + [SettingsSectionControl.ModeratorComments]: this.settings()?.reviewsCommentsAnonymous, }); this.settingsForm.disable(); } + + private updateForm() { + this.settingsForm.patchValue({ + [SettingsSectionControl.ModerationType]: this.settings()?.reviewsWorkflow, + [SettingsSectionControl.CommentVisibility]: this.settings()?.reviewsCommentsPrivate, + [SettingsSectionControl.ModeratorComments]: this.settings()?.reviewsCommentsAnonymous, + }); + } } diff --git a/src/app/features/moderation/constants/preprint-settings-sections.const.ts b/src/app/features/moderation/constants/preprint-settings-sections.const.ts index daab5ed10..e2ad7e844 100644 --- a/src/app/features/moderation/constants/preprint-settings-sections.const.ts +++ b/src/app/features/moderation/constants/preprint-settings-sections.const.ts @@ -1,4 +1,4 @@ -import { CommentVisibilityType, ModerationType, ModeratorsCommentsType, SettingsSectionControl } from '../enums'; +import { ModerationType, SettingsSectionControl } from '../enums'; export const PREPRINT_SETTINGS_SECTIONS = [ { @@ -23,12 +23,12 @@ export const PREPRINT_SETTINGS_SECTIONS = [ description: 'moderation.preprintSettings.commentVisibility.description', options: [ { - value: CommentVisibilityType.Moderators, + value: true, label: 'moderation.preprintSettings.commentVisibility.moderators', description: 'moderation.preprintSettings.commentVisibility.moderatorsDescription', }, { - value: CommentVisibilityType.ModeratorsAndContributors, + value: false, label: 'moderation.preprintSettings.commentVisibility.moderatorsAndContributors', description: 'moderation.preprintSettings.commentVisibility.moderatorsAndContributorsDescription', }, @@ -40,12 +40,12 @@ export const PREPRINT_SETTINGS_SECTIONS = [ description: 'moderation.preprintSettings.moderatorComments.description', options: [ { - value: ModeratorsCommentsType.Anonymized, + value: true, label: 'moderation.preprintSettings.moderatorComments.anonymized', description: 'moderation.preprintSettings.moderatorComments.anonymizedDescription', }, { - value: ModeratorsCommentsType.Named, + value: false, label: 'moderation.preprintSettings.moderatorComments.named', description: 'moderation.preprintSettings.moderatorComments.namedDescription', }, diff --git a/src/app/features/moderation/enums/comment-visibility-type.enum.ts b/src/app/features/moderation/enums/comment-visibility-type.enum.ts deleted file mode 100644 index c9f237740..000000000 --- a/src/app/features/moderation/enums/comment-visibility-type.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum CommentVisibilityType { - Moderators = 'moderators', - ModeratorsAndContributors = 'moderatorsAndContributors', -} diff --git a/src/app/features/moderation/enums/index.ts b/src/app/features/moderation/enums/index.ts index 64fa3ce62..3890b7661 100644 --- a/src/app/features/moderation/enums/index.ts +++ b/src/app/features/moderation/enums/index.ts @@ -1,9 +1,7 @@ export * from './add-moderator-type.enum'; export * from './collection-moderation-tab.enum'; -export * from './comment-visibility-type.enum'; export * from './moderation-type.enum'; export * from './moderator-permission.enum'; -export * from './moderators-comments-type.enum'; export * from './preprint-moderation-tab.enum'; export * from './registry-moderation-tab.enum'; export * from './settings-section-control.enum'; diff --git a/src/app/features/moderation/enums/moderation-type.enum.ts b/src/app/features/moderation/enums/moderation-type.enum.ts index f3cbabe87..b5b9c5a65 100644 --- a/src/app/features/moderation/enums/moderation-type.enum.ts +++ b/src/app/features/moderation/enums/moderation-type.enum.ts @@ -1,4 +1,4 @@ export enum ModerationType { - Pre = 'pre', - Post = 'post', + Pre = 'pre-moderation', + Post = 'post-moderation', } diff --git a/src/app/features/moderation/enums/moderators-comments-type.enum.ts b/src/app/features/moderation/enums/moderators-comments-type.enum.ts deleted file mode 100644 index 3b19d6c97..000000000 --- a/src/app/features/moderation/enums/moderators-comments-type.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ModeratorsCommentsType { - Anonymized = 'anonymized', - Named = 'named', -} diff --git a/src/app/features/moderation/mappers/index.ts b/src/app/features/moderation/mappers/index.ts index 5705bce50..90bd110d9 100644 --- a/src/app/features/moderation/mappers/index.ts +++ b/src/app/features/moderation/mappers/index.ts @@ -1,2 +1,2 @@ export * from './moderation.mapper'; -export * from './preprint-review-action.mapper'; +export * from './preprint-moderation.mapper'; diff --git a/src/app/features/moderation/mappers/preprint-review-action.mapper.ts b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts similarity index 62% rename from src/app/features/moderation/mappers/preprint-review-action.mapper.ts rename to src/app/features/moderation/mappers/preprint-moderation.mapper.ts index 4e3d326f0..385581505 100644 --- a/src/app/features/moderation/mappers/preprint-review-action.mapper.ts +++ b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts @@ -1,10 +1,10 @@ import { JsonApiResponseWithPaging } from '@osf/core/models'; import { PaginatedData } from '@osf/shared/models'; -import { PreprintRelatedCountJsonApi, ReviewActionJsonApi } from '../models'; +import { PreprintProviderModerationInfo, PreprintRelatedCountJsonApi, ReviewActionJsonApi } from '../models'; import { PreprintReviewActionModel } from '../models/preprint-review-action.model'; -export class PreprintReviewActionMapper { +export class PreprintModerationMapper { static fromResponse(response: ReviewActionJsonApi): PreprintReviewActionModel { return { id: response.id, @@ -35,7 +35,15 @@ export class PreprintReviewActionMapper { }; } - static fromRelatedCounts(response: PreprintRelatedCountJsonApi) { - return response.relationships.preprints.links.related.meta.pending; + static fromPreprintRelatedCounts(response: PreprintRelatedCountJsonApi): PreprintProviderModerationInfo { + return { + id: response.id, + name: response.attributes.name, + reviewsCommentsAnonymous: response.attributes.reviews_comments_anonymous, + reviewsCommentsPrivate: response.attributes.reviews_comments_private, + reviewsWorkflow: response.attributes.reviews_workflow, + submissionCount: response.relationships.preprints.links.related.meta.pending ?? 0, + supportEmail: response.attributes.email_support, + }; } } diff --git a/src/app/features/moderation/models/index.ts b/src/app/features/moderation/models/index.ts index 0bc26a8b4..38e717b41 100644 --- a/src/app/features/moderation/models/index.ts +++ b/src/app/features/moderation/models/index.ts @@ -3,6 +3,7 @@ export * from './moderator.model'; export * from './moderator-add.model'; export * from './moderator-dialog-add.model'; export * from './moderator-json-api.model'; +export * from './preprint-provider-moderation-info.model'; export * from './preprint-related-count-json-api.model'; export * from './preprint-review-action.model'; export * from './preprint-review-action-json-api.model'; diff --git a/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts b/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts new file mode 100644 index 000000000..62ea85ef4 --- /dev/null +++ b/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts @@ -0,0 +1,9 @@ +export interface PreprintProviderModerationInfo { + id: string; + name: string; + submissionCount?: number; + reviewsCommentsAnonymous: boolean; + reviewsCommentsPrivate: boolean; + reviewsWorkflow: boolean; + supportEmail?: string; +} diff --git a/src/app/features/moderation/models/preprint-related-count-json-api.model.ts b/src/app/features/moderation/models/preprint-related-count-json-api.model.ts index 4cbaed274..e288a496b 100644 --- a/src/app/features/moderation/models/preprint-related-count-json-api.model.ts +++ b/src/app/features/moderation/models/preprint-related-count-json-api.model.ts @@ -1,4 +1,12 @@ export interface PreprintRelatedCountJsonApi { + id: string; + attributes: { + name: string; + reviews_comments_anonymous: boolean; + reviews_comments_private: boolean; + reviews_workflow: boolean; + email_support?: string; + }; relationships: { preprints: { links: { diff --git a/src/app/features/moderation/preprint-moderation.routes.ts b/src/app/features/moderation/preprint-moderation.routes.ts index 1a2e6cef2..ef2a0053f 100644 --- a/src/app/features/moderation/preprint-moderation.routes.ts +++ b/src/app/features/moderation/preprint-moderation.routes.ts @@ -5,6 +5,7 @@ import { Routes } from '@angular/router'; import { ResourceType } from '@osf/shared/enums'; import { ModeratorsState } from './store/moderation'; +import { PreprintModerationState } from './store/preprint-moderation'; import { PreprintModerationTab } from './enums'; export const preprintModerationRoutes: Routes = [ @@ -14,6 +15,7 @@ export const preprintModerationRoutes: Routes = [ import('@osf/features/moderation/pages/preprint-moderation/preprint-moderation.component').then( (m) => m.PreprintModerationComponent ), + providers: [provideStates([PreprintModerationState])], children: [ { path: '', diff --git a/src/app/features/moderation/services/preprint-moderation.service.ts b/src/app/features/moderation/services/preprint-moderation.service.ts index 6e316943b..e5d905dfc 100644 --- a/src/app/features/moderation/services/preprint-moderation.service.ts +++ b/src/app/features/moderation/services/preprint-moderation.service.ts @@ -4,12 +4,15 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiResponse, JsonApiResponseWithPaging } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; -import { PreprintProvidersMapper } from '@osf/features/preprints/mappers'; -import { PreprintProviderDetailsJsonApi, PreprintProviderShortInfo } from '@osf/features/preprints/models'; import { PaginatedData } from '@osf/shared/models'; -import { PreprintReviewActionMapper } from '../mappers'; -import { PreprintRelatedCountJsonApi, PreprintReviewActionModel, ReviewActionJsonApi } from '../models'; +import { PreprintModerationMapper } from '../mappers'; +import { + PreprintProviderModerationInfo, + PreprintRelatedCountJsonApi, + PreprintReviewActionModel, + ReviewActionJsonApi, +} from '../models'; import { environment } from 'src/environments/environment'; @@ -19,20 +22,20 @@ import { environment } from 'src/environments/environment'; export class PreprintModerationService { private readonly jsonApiService = inject(JsonApiService); - getPreprintProvidersToModerate(): Observable { + getPreprintProviders(): Observable { const baseUrl = `${environment.apiUrl}/preprint_providers/?filter[permissions]=view_actions,set_up_moderation`; return this.jsonApiService - .get>(baseUrl) - .pipe(map((response) => PreprintProvidersMapper.toPreprintProviderShortInfoFromGetResponse(response.data))); + .get>(baseUrl) + .pipe(map((response) => response.data.map((x) => PreprintModerationMapper.fromPreprintRelatedCounts(x)))); } - getPreprintProvider(id: string): Observable { + getPreprintProvider(id: string): Observable { const baseUrl = `${environment.apiUrl}/providers/preprints/${id}/?related_counts=true`; return this.jsonApiService .get>(baseUrl) - .pipe(map((response) => PreprintReviewActionMapper.fromRelatedCounts(response.data))); + .pipe(map((response) => PreprintModerationMapper.fromPreprintRelatedCounts(response.data))); } getPreprintReviews(page = 1): Observable> { @@ -40,6 +43,6 @@ export class PreprintModerationService { return this.jsonApiService .get>(baseUrl) - .pipe(map((response) => PreprintReviewActionMapper.fromResponseWithPagination(response))); + .pipe(map((response) => PreprintModerationMapper.fromResponseWithPagination(response))); } } diff --git a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.actions.ts b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.actions.ts index 5033c0f46..8a7bc49af 100644 --- a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.actions.ts +++ b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.actions.ts @@ -9,3 +9,9 @@ export class GetPreprintReviewActions { constructor(public page = 1) {} } + +export class GetPreprintProvider { + static readonly type = `${ACTION_SCOPE} Get Preprint Provider`; + + constructor(public providerId: string) {} +} diff --git a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.model.ts b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.model.ts index 549e9a270..6ea91f8f8 100644 --- a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.model.ts +++ b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.model.ts @@ -1,10 +1,9 @@ -import { PreprintProviderShortInfo } from '@osf/features/preprints/models'; import { AsyncStateModel, AsyncStateWithTotalCount } from '@osf/shared/models'; -import { PreprintReviewActionModel } from '../../models'; +import { PreprintProviderModerationInfo, PreprintReviewActionModel } from '../../models'; export interface PreprintModerationStateModel { - preprintProviders: AsyncStateModel; + preprintProviders: AsyncStateModel; reviewActions: AsyncStateWithTotalCount; } diff --git a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.selectors.ts b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.selectors.ts index 617925e8b..c59c38559 100644 --- a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.selectors.ts +++ b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.selectors.ts @@ -14,6 +14,11 @@ export class PreprintModerationSelectors { return state.preprintProviders.isLoading; } + @Selector([PreprintModerationState]) + static getPreprintProvider(state: PreprintModerationStateModel) { + return (id: string) => state.preprintProviders.data.find((item) => item.id === id); + } + @Selector([PreprintModerationState]) static getPreprintReviews(state: PreprintModerationStateModel) { return state.reviewActions.data; diff --git a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts index e05df61e4..950c79d02 100644 --- a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts +++ b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts @@ -1,5 +1,5 @@ import { Action, State, StateContext } from '@ngxs/store'; -import { patch } from '@ngxs/store/operators'; +import { insertItem, patch, updateItem } from '@ngxs/store/operators'; import { catchError, forkJoin, map, switchMap, tap } from 'rxjs'; @@ -9,7 +9,7 @@ import { handleSectionError } from '@osf/core/handlers'; import { PreprintModerationService } from '../../services'; -import { GetPreprintProviders, GetPreprintReviewActions } from './preprint-moderation.actions'; +import { GetPreprintProvider, GetPreprintProviders, GetPreprintReviewActions } from './preprint-moderation.actions'; import { PREPRINT_MODERATION_STATE_DEFAULTS, PreprintModerationStateModel } from './preprint-moderation.model'; @State({ @@ -24,14 +24,14 @@ export class PreprintModerationState { getPreprintProviders(ctx: StateContext) { ctx.setState(patch({ preprintProviders: patch({ isLoading: true }) })); - return this.preprintModerationService.getPreprintProvidersToModerate().pipe( + return this.preprintModerationService.getPreprintProviders().pipe( switchMap((items) => forkJoin( items.map((item) => this.preprintModerationService.getPreprintProvider(item.id).pipe( - map((totalCount) => ({ + map((res) => ({ ...item, - submissionCount: totalCount, + submissionCount: res.submissionCount, })) ) ) @@ -70,4 +70,25 @@ export class PreprintModerationState { catchError((error) => handleSectionError(ctx, 'reviewActions', error)) ); } + + @Action(GetPreprintProvider) + getPreprintProvider(ctx: StateContext, { providerId }: GetPreprintProvider) { + ctx.setState(patch({ preprintProviders: patch({ isLoading: true }) })); + + return this.preprintModerationService.getPreprintProvider(providerId).pipe( + tap((data) => { + const exists = ctx.getState().preprintProviders.data.some((p) => p.id === data.id); + + ctx.setState( + patch({ + preprintProviders: patch({ + data: exists ? updateItem((p) => p.id === data.id, data) : insertItem(data), + isLoading: false, + }), + }) + ); + }), + catchError((error) => handleSectionError(ctx, 'preprintProviders', error)) + ); + } } diff --git a/src/app/shared/constants/constants.ts b/src/app/shared/constants/constants.ts new file mode 100644 index 000000000..c5e6b7edd --- /dev/null +++ b/src/app/shared/constants/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_SUPPORT_EMAIL = 'support@osf.io'; diff --git a/src/app/shared/constants/index.ts b/src/app/shared/constants/index.ts index 9d3ae69f9..c8fbd40d4 100644 --- a/src/app/shared/constants/index.ts +++ b/src/app/shared/constants/index.ts @@ -1,6 +1,7 @@ export * from './addon-terms.const'; export * from './addons-category-options.const'; export * from './addons-tab-options.const'; +export * from './constants'; export * from './contributors'; export * from './filter-placeholders'; export * from './input-limits.const';