diff --git a/src/app/features/contributors/contributors.component.ts b/src/app/features/contributors/contributors.component.ts
index 46ae814a1..7fef58287 100644
--- a/src/app/features/contributors/contributors.component.ts
+++ b/src/app/features/contributors/contributors.component.ts
@@ -49,6 +49,7 @@ import {
AcceptRequestAccess,
AddContributor,
BulkAddContributors,
+ BulkAddContributorsFromParentProject,
BulkUpdateContributors,
ContributorsSelectors,
CreateViewOnlyLink,
@@ -155,6 +156,7 @@ export class ContributorsComponent implements OnInit {
deleteContributor: DeleteContributor,
bulkUpdateContributors: BulkUpdateContributors,
bulkAddContributors: BulkAddContributors,
+ bulkAddContributorsFromParentProject: BulkAddContributorsFromParentProject,
addContributor: AddContributor,
createViewOnlyLink: CreateViewOnlyLink,
deleteViewOnlyLink: DeleteViewOnlyLink,
@@ -242,17 +244,19 @@ export class ContributorsComponent implements OnInit {
openAddContributorDialog() {
const addedContributorIds = this.initialContributors().map((x) => x.userId);
- const rootParentId = this.resourceDetails().rootParentId ?? this.resourceId();
+ const resourceDetails = this.resourceDetails();
+ const resourceId = this.resourceId();
+ const rootParentId = resourceDetails.rootParentId ?? resourceId;
this.loaderService.show();
this.actions
- .getResourceWithChildren(rootParentId, this.resourceId(), this.resourceType())
+ .getResourceWithChildren(rootParentId, resourceId, this.resourceType())
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.loaderService.hide();
- const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), this.resourceId());
+ const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), resourceId);
this.customDialogService
.open(AddContributorDialogComponent, {
@@ -261,7 +265,11 @@ export class ContributorsComponent implements OnInit {
data: {
addedContributorIds,
components,
- resourceName: this.resourceDetails().title,
+ resourceName: resourceDetails.title,
+ allowAddingContributorsFromParentProject:
+ this.resourceType() === ResourceType.Project &&
+ resourceDetails.rootParentId &&
+ resourceDetails.rootParentId !== resourceId,
},
})
.onClose.pipe(
@@ -269,7 +277,9 @@ export class ContributorsComponent implements OnInit {
takeUntilDestroyed(this.destroyRef)
)
.subscribe((res: ContributorDialogAddModel) => {
- if (res.type === AddContributorType.Unregistered) {
+ if (res.type === AddContributorType.ParentProject) {
+ this.addContributorsFromParentProjectToComponents();
+ } else if (res.type === AddContributorType.Unregistered) {
this.openAddUnregisteredContributorDialog();
} else {
this.addContributorsToComponents(res);
@@ -299,6 +309,13 @@ export class ContributorsComponent implements OnInit {
.subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage'));
}
+ private addContributorsFromParentProjectToComponents(): void {
+ this.actions
+ .bulkAddContributorsFromParentProject(this.resourceId(), this.resourceType())
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage'));
+ }
+
openAddUnregisteredContributorDialog() {
this.customDialogService
.open(AddUnregisteredContributorDialogComponent, {
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 c94ee8457..d20676f16 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
@@ -127,7 +127,7 @@ export class PreprintsContributorsComponent implements OnInit {
.open(AddContributorDialogComponent, {
header: 'project.contributors.addDialog.addRegisteredContributor',
width: '448px',
- data: addedContributorIds,
+ data: { addedContributorIds, allowAddingContributorsFromParentProject: true },
})
.onClose.pipe(
filter((res: ContributorDialogAddModel) => !!res),
diff --git a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html
index 933bf2479..bd299f5ab 100644
--- a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html
+++ b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html
@@ -65,6 +65,22 @@
}
+ @if (allowAddingContributorsFromParentProject() && this.isSearchState()) {
+
+ }
+
([]);
readonly components = signal([]);
readonly resourceName = signal('');
+ readonly allowAddingContributorsFromParentProject = signal(false);
readonly contributorNames = computed(() =>
this.selectedUsers()
@@ -113,6 +118,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
}
}
+ addSourceProjectContributors(): void {
+ this.closeDialogWithData(AddContributorType.ParentProject);
+ }
+
addUnregistered(): void {
this.dialogRef.close({
data: [],
@@ -129,7 +138,7 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
private initializeDialogData(): void {
this.selectedUsers.set([]);
- const { components, resourceName } = this.config.data || {};
+ const { components, resourceName, allowAddingContributorsFromParentProject } = this.config.data || {};
if (components) {
this.components.set(components);
@@ -138,16 +147,20 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
if (resourceName) {
this.resourceName.set(resourceName);
}
+
+ if (allowAddingContributorsFromParentProject) {
+ this.allowAddingContributorsFromParentProject.set(allowAddingContributorsFromParentProject);
+ }
}
- private closeDialogWithData(): void {
+ private closeDialogWithData(AddContributorTypeValue = AddContributorType.Registered): void {
const childNodeIds = this.components()
.filter((c) => c.checked && !c.isCurrent)
.map((c) => c.id);
this.dialogRef.close({
data: this.selectedUsers(),
- type: AddContributorType.Registered,
+ type: AddContributorTypeValue,
childNodeIds: childNodeIds.length > 0 ? childNodeIds : undefined,
} as ContributorDialogAddModel);
}
diff --git a/src/app/shared/enums/contributors/add-contributor-type.enum.ts b/src/app/shared/enums/contributors/add-contributor-type.enum.ts
index 17ce058fc..cd0e360e4 100644
--- a/src/app/shared/enums/contributors/add-contributor-type.enum.ts
+++ b/src/app/shared/enums/contributors/add-contributor-type.enum.ts
@@ -1,4 +1,5 @@
export enum AddContributorType {
Registered = 1,
Unregistered,
+ ParentProject,
}
diff --git a/src/app/shared/services/contributors.service.ts b/src/app/shared/services/contributors.service.ts
index 62c178759..e9a01369e 100644
--- a/src/app/shared/services/contributors.service.ts
+++ b/src/app/shared/services/contributors.service.ts
@@ -150,6 +150,12 @@ export class ContributorsService {
.pipe(map((contributor) => ContributorsMapper.getContributor(contributor.data)));
}
+ addContributorsFromProject(resourceType: ResourceType, resourceId: string): Observable {
+ const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/?copy_contributors_from_parent_project=${true}`;
+ let contributorData = { data: { type: AddContributorType.ParentProject } };
+ return this.jsonApiService.patch(baseUrl, contributorData);
+ }
+
deleteContributor(resourceType: ResourceType, resourceId: string, userId: string): Observable {
const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/${userId}/`;
diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts
index 4eaaead98..2ee6ccf7a 100644
--- a/src/app/shared/stores/contributors/contributors.actions.ts
+++ b/src/app/shared/stores/contributors/contributors.actions.ts
@@ -62,6 +62,15 @@ export class BulkAddContributors {
) {}
}
+export class BulkAddContributorsFromParentProject {
+ static readonly type = '[Contributors] Bulk Add Contributors From Parent Project';
+
+ constructor(
+ public resourceId: string | undefined | null,
+ public resourceType: ResourceType | undefined
+ ) {}
+}
+
export class DeleteContributor {
static readonly type = '[Contributors] Delete Contributor';
diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts
index 1731cf44d..9d8f7fd99 100644
--- a/src/app/shared/stores/contributors/contributors.state.ts
+++ b/src/app/shared/stores/contributors/contributors.state.ts
@@ -11,6 +11,7 @@ import {
AcceptRequestAccess,
AddContributor,
BulkAddContributors,
+ BulkAddContributorsFromParentProject,
BulkUpdateContributors,
ClearUsers,
DeleteContributor,
@@ -191,6 +192,29 @@ export class ContributorsState {
);
}
+ @Action(BulkAddContributorsFromParentProject)
+ bulkAddContributorsFromParentProject(
+ ctx: StateContext,
+ action: BulkAddContributorsFromParentProject
+ ) {
+ const state = ctx.getState();
+
+ if (!action.resourceId || !action.resourceType) {
+ return;
+ }
+
+ ctx.patchState({
+ contributorsList: { ...state.contributorsList, isLoading: true, error: null },
+ });
+
+ return this.contributorsService.addContributorsFromProject(action.resourceType, action.resourceId).pipe(
+ tap(() => {
+ ctx.dispatch(new GetAllContributors(action.resourceId, action.resourceType));
+ }),
+ catchError((error) => handleSectionError(ctx, 'contributorsList', error))
+ );
+ }
+
@Action(DeleteContributor)
deleteContributor(ctx: StateContext, action: DeleteContributor) {
const state = ctx.getState();
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index e29e85ef0..90b45540d 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -596,7 +596,8 @@
"bibliographicContributor": "Bibliographic Contributor",
"addUnregisteredContributor": "Add Unregistered Contributor",
"addRegisteredContributor": "Add Registered Contributor",
- "unregisteredContributorNotification": "We will notify the user that they have been added to your project"
+ "unregisteredContributorNotification": "We will notify the user that they have been added to your project",
+ "addingContributorsFromParentProject": "Import contributors from {{projectName}}"
},
"removeDialog": {
"title": "Remove contributor",