diff --git a/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts b/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts index 35af2dc55..6fcba6f9a 100644 --- a/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts @@ -37,7 +37,6 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { - AddContributor, BulkAddContributors, BulkUpdateContributors, ContributorsSelectors, @@ -92,7 +91,6 @@ export class ProjectContributorsStepComponent { contributorsSaved = output(); actions = createDispatchMap({ - addContributor: AddContributor, bulkAddContributors: BulkAddContributors, bulkUpdateContributors: BulkUpdateContributors, deleteContributor: DeleteContributor, @@ -207,7 +205,7 @@ export class ProjectContributorsStepComponent { } else { const params = { name: res.data[0].fullName }; - this.actions.addContributor(this.selectedProject()?.id, ResourceType.Project, res.data[0]).subscribe({ + this.actions.bulkAddContributors(this.selectedProject()?.id, ResourceType.Project, res.data).subscribe({ next: () => this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params), }); } diff --git a/src/app/features/contributors/contributors.component.ts b/src/app/features/contributors/contributors.component.ts index 8c2ef4b23..118d380f1 100644 --- a/src/app/features/contributors/contributors.component.ts +++ b/src/app/features/contributors/contributors.component.ts @@ -45,7 +45,6 @@ import { LoaderService } from '@osf/shared/services/loader.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { AcceptRequestAccess, - AddContributor, BulkAddContributors, BulkAddContributorsFromParentProject, BulkUpdateContributors, @@ -176,7 +175,6 @@ export class ContributorsComponent implements OnInit, OnDestroy { bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, bulkAddContributorsFromParentProject: BulkAddContributorsFromParentProject, - addContributor: AddContributor, createViewOnlyLink: CreateViewOnlyLink, deleteViewOnlyLink: DeleteViewOnlyLink, getRequestAccessContributors: GetRequestAccessContributors, @@ -327,7 +325,7 @@ export class ContributorsComponent implements OnInit, OnDestroy { private addContributorsToComponents(result: ContributorDialogAddModel): void { this.actions - .bulkAddContributors(this.resourceId(), this.resourceType(), result.data, result.childNodeIds) + .bulkAddContributors(this.resourceId(), this.resourceType(), result.data) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage')); } @@ -355,7 +353,7 @@ export class ContributorsComponent implements OnInit, OnDestroy { } else { const params = { name: res.data[0].fullName }; - this.actions.addContributor(this.resourceId(), this.resourceType(), res.data[0]).subscribe({ + this.actions.bulkAddContributors(this.resourceId(), this.resourceType(), res.data).subscribe({ next: () => this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params), }); } diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts index 896cbae48..f9a6d0b3d 100644 --- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts +++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts @@ -36,7 +36,6 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { - AddContributor, BulkAddContributors, BulkUpdateContributors, ContributorsSelectors, @@ -96,7 +95,6 @@ export class ContributorsDialogComponent implements OnInit { updatePermissionFilter: UpdatePermissionFilter, updateBibliographyFilter: UpdateBibliographyFilter, deleteContributor: DeleteContributor, - addContributor: AddContributor, bulkAddContributors: BulkAddContributors, bulkUpdateContributors: BulkUpdateContributors, loadMoreContributors: LoadMoreContributors, @@ -178,7 +176,7 @@ export class ContributorsDialogComponent implements OnInit { } else { const params = { name: res.data[0].fullName }; - this.actions.addContributor(this.resourceId, this.resourceType, res.data[0]).subscribe({ + this.actions.bulkAddContributors(this.resourceId, this.resourceType, res.data).subscribe({ next: () => { this.changesMade.set(true); this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params); diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts index e54f8787d..3fbfa30d6 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts @@ -36,7 +36,6 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { - AddContributor, BulkAddContributors, BulkUpdateContributors, ContributorsSelectors, @@ -84,7 +83,6 @@ export class PreprintsContributorsComponent implements OnInit { deleteContributor: DeleteContributor, bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, - addContributor: AddContributor, loadMoreContributors: LoadMoreContributors, }); @@ -157,7 +155,7 @@ export class PreprintsContributorsComponent implements OnInit { } else { const params = { name: res.data[0].fullName }; - this.actions.addContributor(this.preprintId(), ResourceType.Preprint, res.data[0]).subscribe({ + this.actions.bulkAddContributors(this.preprintId(), ResourceType.Preprint, res.data).subscribe({ next: () => this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params), }); } diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts index 9423f6a95..c69bc4e41 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts @@ -37,7 +37,6 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { - AddContributor, BulkAddContributors, BulkUpdateContributors, ContributorsSelectors, @@ -90,7 +89,6 @@ export class RegistriesContributorsComponent implements OnInit, OnDestroy { deleteContributor: DeleteContributor, bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, - addContributor: AddContributor, loadMoreContributors: LoadMoreContributors, resetContributorsState: ResetContributorsState, }); @@ -176,7 +174,7 @@ export class RegistriesContributorsComponent implements OnInit, OnDestroy { } else { const params = { name: res.data[0].fullName }; - this.actions.addContributor(this.draftId(), ResourceType.DraftRegistration, res.data[0]).subscribe({ + this.actions.bulkAddContributors(this.draftId(), ResourceType.DraftRegistration, res.data).subscribe({ next: () => this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params), }); } diff --git a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts index fd4d1dd00..dd6c908a5 100644 --- a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts +++ b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts @@ -85,8 +85,12 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { readonly isSearchState = computed(() => this.currentState() === AddDialogState.Search); readonly isDetailsState = computed(() => this.currentState() === AddDialogState.Details); readonly isComponentsState = computed(() => this.currentState() === AddDialogState.Components); - readonly hasComponents = computed(() => this.components().length > 0); - readonly buttonLabel = computed(() => (this.isComponentsState() ? 'common.buttons.done' : 'common.buttons.next')); + readonly hasComponents = computed(() => this.components().length > 1); + readonly buttonLabel = computed(() => + (this.isDetailsState() && !this.hasComponents()) || this.isComponentsState() + ? 'common.buttons.done' + : 'common.buttons.next' + ); constructor() { this.setupEffects(); diff --git a/src/app/shared/mappers/contributors/contributors.mapper.ts b/src/app/shared/mappers/contributors/contributors.mapper.ts index b0546f98e..97f400f7d 100644 --- a/src/app/shared/mappers/contributors/contributors.mapper.ts +++ b/src/app/shared/mappers/contributors/contributors.mapper.ts @@ -110,4 +110,25 @@ export class ContributorsMapper { }; } } + + static toContributorUpdateRequest(model: ContributorModel): ContributorAddRequestModel { + return { + id: model.id, + type: 'contributors', + attributes: { + bibliographic: model.isBibliographic, + permission: model.permission, + index: model.index, + id: model.userId, + }, + relationships: { + users: { + data: { + id: model.id, + type: 'users', + }, + }, + }, + }; + } } diff --git a/src/app/shared/models/contributors/contributor-response-json-api.model.ts b/src/app/shared/models/contributors/contributor-response-json-api.model.ts index 295d8608c..72f5fc024 100644 --- a/src/app/shared/models/contributors/contributor-response-json-api.model.ts +++ b/src/app/shared/models/contributors/contributor-response-json-api.model.ts @@ -33,6 +33,7 @@ export interface ContributorEmbedsJsonApi { export interface ContributorAddRequestModel { type: 'contributors'; + id?: string; attributes: { bibliographic: boolean; permission: string; diff --git a/src/app/shared/services/contributors.service.ts b/src/app/shared/services/contributors.service.ts index c441b9ca0..df2af3cf0 100644 --- a/src/app/shared/services/contributors.service.ts +++ b/src/app/shared/services/contributors.service.ts @@ -1,4 +1,4 @@ -import { forkJoin, map, Observable, of } from 'rxjs'; +import { map, Observable, of } from 'rxjs'; import { inject, Injectable } from '@angular/core'; @@ -10,11 +10,7 @@ import { ContributorsMapper } from '../mappers/contributors'; import { ResponseJsonApi } from '../models/common/json-api.model'; import { ContributorModel } from '../models/contributors/contributor.model'; import { ContributorAddModel } from '../models/contributors/contributor-add.model'; -import { - ContributorDataJsonApi, - ContributorResponseJsonApi, - ContributorsResponseJsonApi, -} from '../models/contributors/contributor-response-json-api.model'; +import { ContributorsResponseJsonApi } from '../models/contributors/contributor-response-json-api.model'; import { PaginatedData } from '../models/paginated-data.model'; import { UserDataJsonApi } from '../models/user/user-json-api.model'; @@ -110,25 +106,19 @@ export class ContributorsService { return of([]); } - const updateRequests = contributors.map((contributor) => - this.updateContributor(resourceType, resourceId, contributor) - ); - - return forkJoin(updateRequests); - } + const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/`; - updateContributor( - resourceType: ResourceType, - resourceId: string, - data: ContributorModel - ): Observable { - const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/${data.userId}/`; + const contributorData = { + data: contributors.map((contributor) => ContributorsMapper.toContributorUpdateRequest(contributor)), + }; - const contributorData = { data: ContributorsMapper.toContributorAddRequest(data) }; + const headers = { + 'Content-Type': 'application/vnd.api+json; ext=bulk', + }; return this.jsonApiService - .patch(baseUrl, contributorData) - .pipe(map((contributor) => ContributorsMapper.getContributor(contributor))); + .patch(baseUrl, contributorData, undefined, headers) + .pipe(map((response) => ContributorsMapper.getContributors(response.data))); } bulkAddContributors( @@ -141,27 +131,22 @@ export class ContributorsService { return of([]); } - const addRequests = contributors.map((contributor) => - this.addContributor(resourceType, resourceId, contributor, childNodeIds) - ); - - return forkJoin(addRequests); - } - - addContributor( - resourceType: ResourceType, - resourceId: string, - data: ContributorAddModel, - childNodeIds?: string[] - ): Observable { const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/`; - const type = data.id ? AddContributorType.Registered : AddContributorType.Unregistered; - const contributorData = { data: ContributorsMapper.toContributorAddRequest(data, type, childNodeIds) }; + const contributorData = { + data: contributors.map((contributor) => { + const type = contributor.id ? AddContributorType.Registered : AddContributorType.Unregistered; + return ContributorsMapper.toContributorAddRequest(contributor, type, childNodeIds); + }), + }; + + const headers = { + 'Content-Type': 'application/vnd.api+json; ext=bulk', + }; return this.jsonApiService - .post(baseUrl, contributorData) - .pipe(map((contributor) => ContributorsMapper.getContributor(contributor.data))); + .post(baseUrl, contributorData, undefined, headers) + .pipe(map((response) => ContributorsMapper.getContributors(response.data))); } addContributorsFromProject(resourceType: ResourceType, resourceId: string): Observable { diff --git a/src/app/shared/services/json-api.service.ts b/src/app/shared/services/json-api.service.ts index 28fe82f87..09865805c 100644 --- a/src/app/shared/services/json-api.service.ts +++ b/src/app/shared/services/json-api.service.ts @@ -35,8 +35,13 @@ export class JsonApiService { return httpParams; } - post(url: string, body?: unknown, params?: Record): Observable { - return this.http.post(url, body, { params: this.buildHttpParams(params) }); + post( + url: string, + body?: unknown, + params?: Record, + headers?: Record + ): Observable { + return this.http.post(url, body, { params: this.buildHttpParams(params), headers }); } patch( diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts index 2bb634cfc..470948c3a 100644 --- a/src/app/shared/stores/contributors/contributors.actions.ts +++ b/src/app/shared/stores/contributors/contributors.actions.ts @@ -33,16 +33,6 @@ export class UpdateBibliographyFilter { constructor(public bibliographyFilter: boolean | null) {} } -export class AddContributor { - static readonly type = '[Contributors] Add Contributor'; - - constructor( - public resourceId: string | undefined | null, - public resourceType: ResourceType | undefined, - public contributor: ContributorAddModel - ) {} -} - export class BulkUpdateContributors { static readonly type = '[Contributors] Bulk Update Contributors'; diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts index 0e738afec..768983661 100644 --- a/src/app/shared/stores/contributors/contributors.state.ts +++ b/src/app/shared/stores/contributors/contributors.state.ts @@ -10,7 +10,6 @@ import { RequestAccessService } from '@osf/shared/services/request-access.servic import { AcceptRequestAccess, - AddContributor, BulkAddContributors, BulkAddContributorsFromParentProject, BulkUpdateContributors, @@ -145,28 +144,6 @@ export class ContributorsState { ); } - @Action(AddContributor) - addContributor(ctx: StateContext, action: AddContributor) { - const state = ctx.getState(); - - if (!action.resourceId || !action.resourceType) { - return; - } - - ctx.patchState({ - contributorsList: { ...state.contributorsList, isLoading: true, error: null }, - }); - - return this.contributorsService.addContributor(action.resourceType, action.resourceId, action.contributor).pipe( - tap(() => { - ctx.dispatch( - new GetAllContributors(action.resourceId, action.resourceType, 1, state.contributorsList.pageSize) - ); - }), - catchError((error) => handleSectionError(ctx, 'contributorsList', error)) - ); - } - @Action(BulkUpdateContributors) bulkUpdateContributors(ctx: StateContext, action: BulkUpdateContributors) { const state = ctx.getState(); @@ -204,7 +181,7 @@ export class ContributorsState { }); return this.contributorsService - .bulkAddContributors(action.resourceType, action.resourceId, action.contributors, action.childNodeIds) + .bulkAddContributors(action.resourceType, action.resourceId, action.contributors) .pipe( tap(() => { ctx.dispatch(