Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added missing/incomplete breadcrumbs on create community/collection/item pages #2888

22 changes: 20 additions & 2 deletions src/app/collection-page/collection-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { browseByGuard } from '../browse-by/browse-by-guard';
import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
import { authenticatedGuard } from '../core/auth/authenticated.guard';
import { collectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
Expand All @@ -27,12 +28,29 @@ import { itemTemplatePageResolver } from './edit-item-template-page/item-templat
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
import { ThemedCollectionPageComponent } from './themed-collection-page.component';


export const ROUTES: Route[] = [
{
path: COLLECTION_CREATE_PATH,
component: CreateCollectionPageComponent,
canActivate: [authenticatedGuard, createCollectionPageGuard],
children: [
{
path: '',
component: CreateCollectionPageComponent,
resolve: {
breadcrumb: i18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'collection.create',
},
},
],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: communityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down
20 changes: 19 additions & 1 deletion src/app/community-page/community-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,26 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
export const ROUTES: Route[] = [
{
path: COMMUNITY_CREATE_PATH,
component: CreateCommunityPageComponent,
children: [
{
path: '',
component: CreateCommunityPageComponent,
resolve: {
breadcrumb: i18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'community.create',
},
},
],
canActivate: [authenticatedGuard, createCommunityPageGuard],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: communityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down
31 changes: 23 additions & 8 deletions src/app/core/breadcrumbs/community-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { Observable } from 'rxjs';

import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
import { hasValue } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { CommunityDataService } from '../data/community-data.service';
import { Community } from '../shared/community.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
import {
DSOBreadcrumbResolver,
DSOBreadcrumbResolverByUuid,
} from './dso-breadcrumb.resolver';
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';

/**
Expand All @@ -25,11 +29,22 @@ export const communityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>>
dataService: CommunityDataService = inject(CommunityDataService),
): Observable<BreadcrumbConfig<Community>> => {
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
return DSOBreadcrumbResolver(
route,
state,
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
if (hasValue(route.data.breadcrumbQueryParam) && hasValue(route.queryParams[route.data.breadcrumbQueryParam])) {
return DSOBreadcrumbResolverByUuid(
route,
state,
route.queryParams[route.data.breadcrumbQueryParam],
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
} else {
return DSOBreadcrumbResolver(
route,
state,
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
}
};
5 changes: 4 additions & 1 deletion src/app/core/breadcrumbs/dso-breadcrumb.resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ describe('DSOBreadcrumbResolver', () => {
uuid = '1234-65487-12354-1235';
breadcrumbUrl = `/collections/${uuid}`;
currentUrl = `${breadcrumbUrl}/edit`;
testCollection = Object.assign(new Collection(), { uuid });
testCollection = Object.assign(new Collection(), {
uuid: uuid,
type: 'collection',
});
dsoBreadcrumbService = {};
collectionService = {
findById: () => createSuccessfulRemoteDataObject$(testCollection),
Expand Down
28 changes: 24 additions & 4 deletions src/app/core/breadcrumbs/dso-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { getDSORoute } from '../../app-routing-paths';
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { hasValue } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
Expand Down Expand Up @@ -32,15 +33,34 @@ export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: Route
dataService: IdentifiableDataService<DSpaceObject>,
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
): Observable<BreadcrumbConfig<DSpaceObject>> => {
const uuid = route.params.id;
return DSOBreadcrumbResolverByUuid(route, state, route.params.id, breadcrumbService, dataService, ...linksToFollow);
};

/**
* Method for resolving a breadcrumb config object with the given UUID
*
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @param {String} uuid The uuid of the DSO object
* @param {DSOBreadcrumbsService} breadcrumbService
* @param {IdentifiableDataService} dataService
* @param linksToFollow
* @returns BreadcrumbConfig object
*/
export const DSOBreadcrumbResolverByUuid: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, uuid: string, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
uuid: string,
breadcrumbService: DSOBreadcrumbsService,
dataService: IdentifiableDataService<DSpaceObject>,
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
): Observable<BreadcrumbConfig<DSpaceObject>> => {
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((object: DSpaceObject) => {
if (hasValue(object)) {
const fullPath = state.url;
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
return { provider: breadcrumbService, key: object, url: url };
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
} else {
return undefined;
}
Expand Down
17 changes: 17 additions & 0 deletions src/app/core/submission/resolver/submission-links-to-follow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
followLink,
FollowLinkConfig,
} from '../../../shared/utils/follow-link-config.model';
import { WorkflowItem } from '../models/workflowitem.model';
import { WorkspaceItem } from '../models/workspaceitem.model';

/**
* The self links defined in this list are expected to be requested somewhere in the near future
* Requesting them as embeds will limit the number of requests
*
* Needs to be in a separate file to prevent circular dependencies in webpack.
*/
export const SUBMISSION_LINKS_TO_FOLLOW: FollowLinkConfig<WorkflowItem | WorkspaceItem>[] = [
followLink('item'),
followLink('collection'),
];
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { followLink } from '../../../shared/utils/follow-link-config.model';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import { RemoteData } from '../../data/remote-data';
import { Item } from '../../shared/item.model';
import { getFirstCompletedRemoteData } from '../../shared/operators';
import { SubmissionObject } from '../models/submission-object.model';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';

/**
* Method for resolving an item based on the parameters in the current route
Expand All @@ -28,7 +28,7 @@ export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: Ro
return dataService.findById(route.params.id,
true,
false,
followLink('item'),
...SUBMISSION_LINKS_TO_FOLLOW,
).pipe(
getFirstCompletedRemoteData(),
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
ActivatedRouteSnapshot,
Resolve,
RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { BreadcrumbConfig } from '../../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../../shared/operators';
import { SubmissionObject } from '../models/submission-object.model';
import { SubmissionParentBreadcrumbsService } from '../submission-parent-breadcrumb.service';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';

/**
* This class represents a resolver that requests a specific item before the route is activated
*/
export abstract class SubmissionParentBreadcrumbResolver implements Resolve<BreadcrumbConfig<SubmissionObject>> {

protected constructor(
protected dataService: IdentifiableDataService<any>,
protected breadcrumbService: SubmissionParentBreadcrumbsService,
) {
}

/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<SubmissionObject>> {
return this.dataService.findById(route.params.id,
true,
false,
...SUBMISSION_LINKS_TO_FOLLOW,
).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((submissionObject: SubmissionObject) => ({
provider: this.breadcrumbService,
key: submissionObject,
} as BreadcrumbConfig<SubmissionObject>)),
);
}
}
70 changes: 70 additions & 0 deletions src/app/core/submission/submission-parent-breadcrumb.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Injectable } from '@angular/core';
import {
combineLatest,
Observable,
of as observableOf,
switchMap,
} from 'rxjs';

import { getDSORoute } from '../../app-routing-paths';
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { hasValue } from '../../shared/empty.util';
import { SubmissionService } from '../../submission/submission.service';
import { BreadcrumbsProviderService } from '../breadcrumbs/breadcrumbsProviderService';
import { DSOBreadcrumbsService } from '../breadcrumbs/dso-breadcrumbs.service';
import { DSONameService } from '../breadcrumbs/dso-name.service';
import { CollectionDataService } from '../data/collection-data.service';
import { RemoteData } from '../data/remote-data';
import { Collection } from '../shared/collection.model';
import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../shared/operators';
import { SubmissionObject } from './models/submission-object.model';

/**
* Service to calculate the parent {@link DSpaceObject} breadcrumbs for a {@link SubmissionObject}
*/
@Injectable({
providedIn: 'root',
})
export class SubmissionParentBreadcrumbsService implements BreadcrumbsProviderService<SubmissionObject> {

constructor(
protected dsoNameService: DSONameService,
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
protected submissionService: SubmissionService,
protected collectionService: CollectionDataService,
) {
}

/**
* Creates the parent breadcrumb structure for {@link SubmissionObject}s. It also automatically recreates the
* parent breadcrumb structure when you change a {@link SubmissionObject}'s by dispatching a
* {@link ChangeSubmissionCollectionAction}.
*
* @param submissionObject The {@link SubmissionObject} for which the parent breadcrumb structure needs to be created
*/
getBreadcrumbs(submissionObject: SubmissionObject): Observable<Breadcrumb[]> {
return combineLatest([
(submissionObject.collection as Observable<RemoteData<Collection>>).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
),
this.submissionService.getSubmissionCollectionId(submissionObject.id),
]).pipe(
switchMap(([collection, latestCollectionId]: [Collection, string]) => {
if (hasValue(latestCollectionId)) {
return this.collectionService.findById(latestCollectionId).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
);
} else {
return observableOf(collection);
}
}),
switchMap((collection: Collection) => this.dsoBreadcrumbsService.getBreadcrumbs(collection, getDSORoute(collection))),
);
}

}
32 changes: 30 additions & 2 deletions src/app/submission/submission.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import {
createSelector,
MemoizedSelector,
select,
Store,
} from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import {
Observable,
Expand Down Expand Up @@ -71,6 +76,20 @@
SubmissionState,
} from './submission.reducers';

function getSubmissionSelector(submissionId: string): MemoizedSelector<SubmissionState, SubmissionObjectEntry> {
return createSelector(

Check warning on line 80 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/submission.service.ts#L80

Added line #L80 was not covered by tests
submissionSelector,
(state: SubmissionState) => state.objects[submissionId],

Check warning on line 82 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/submission.service.ts#L82

Added line #L82 was not covered by tests
);
}

function getSubmissionCollectionIdSelector(submissionId: string): MemoizedSelector<SubmissionState, string> {
return createSelector(

Check warning on line 87 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/submission.service.ts#L87

Added line #L87 was not covered by tests
getSubmissionSelector(submissionId),
(submission: SubmissionObjectEntry) => submission?.collection,

Check warning on line 89 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/submission.service.ts#L89

Added line #L89 was not covered by tests
);
}

/**
* A service that provides methods used in submission process.
*/
Expand Down Expand Up @@ -120,10 +139,19 @@
* @param collectionId
* The collection id
*/
changeSubmissionCollection(submissionId, collectionId) {
changeSubmissionCollection(submissionId: string, collectionId: string): void {
this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId));
}

/**
* Listen to collection changes for a certain {@link SubmissionObject}
*
* @param submissionId The submission id
*/
getSubmissionCollectionId(submissionId: string): Observable<string> {
return this.store.pipe(select(getSubmissionCollectionIdSelector(submissionId)));

Check warning on line 152 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/submission.service.ts#L152

Added line #L152 was not covered by tests
}

/**
* Perform a REST call to create a new workspaceitem and return response
*
Expand Down
Loading
Loading