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

[Port dspace-7_x] Added missing/incomplete breadcrumbs on create community/collection/item pages #2969

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/app/collection-page/collection-page-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,33 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';

@NgModule({
imports: [
RouterModule.forChild([
{
path: COLLECTION_CREATE_PATH,
component: CreateCollectionPageComponent,
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard]
children: [
{
path: '',
component: CreateCollectionPageComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'collection.create',
},
},
],
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: CommunityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down Expand Up @@ -94,6 +113,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
LinkService,
CreateCollectionPageGuard,
CollectionPageAdministratorGuard,
CommunityBreadcrumbResolver,
]
})
export class CollectionPageRoutingModule {
Expand Down
23 changes: 21 additions & 2 deletions src/app/community-page/community-page-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,33 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCommunityPageComponent } from './themed-community-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';

@NgModule({
imports: [
RouterModule.forChild([
{
path: COMMUNITY_CREATE_PATH,
component: CreateCommunityPageComponent,
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard]
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
21 changes: 21 additions & 0 deletions src/app/core/breadcrumbs/community-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { CommunityDataService } from '../data/community-data.service';
import { Community } from '../shared/community.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { hasValue } from '../../shared/empty.util';

/**
* The class that resolves the BreadcrumbConfig object for a Community
Expand All @@ -17,6 +21,23 @@ export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community
super(breadcrumbService, dataService);
}

/**
* Method to retrieve the breadcrumb config by the route id. It is also possible to retrieve the id through the
* query parameters. This is done by defining the name of the query parameter in the data section under the property
* breadcrumbQueryParam.
*
* @param route The current {@link ActivatedRouteSnapshot}
* @param state The current {@link RouterStateSnapshot}
* @returns BreadcrumbConfig object
*/
override resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Community>> {
if (hasValue(route.data.breadcrumbQueryParam) && hasValue(route.queryParams[route.data.breadcrumbQueryParam])) {
return this.resolveById(route.queryParams[route.data.breadcrumbQueryParam]);
} else {
return super.resolve(route, state);
}
}

/**
* Method that returns the follow links to already resolve
* The self links defined in this list are expected to be requested somewhere in the near future
Expand Down
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: (id: string) => createSuccessfulRemoteDataObject$(testCollection)
Expand Down
16 changes: 12 additions & 4 deletions src/app/core/breadcrumbs/dso-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ChildHALResource } from '../shared/child-hal-resource.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { hasValue } from '../../shared/empty.util';
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
import { getDSORoute } from '../../app-routing-paths';

/**
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
Expand All @@ -31,15 +32,22 @@ export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceO
* @returns BreadcrumbConfig object
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
const uuid = route.params.id;
return this.resolveById(route.params.id);
}

/**
* Method for resolving a breadcrumb by id
*
* @param uuid The uuid to resolve
* @returns BreadcrumbConfig object
*/
resolveById(uuid: string): Observable<BreadcrumbConfig<T>> {
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((object: T) => {
if (hasValue(object)) {
const fullPath = state.url;
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
return { provider: this.breadcrumbService, key: object, url: url };
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };
} else {
return undefined;
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model';
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
import { SubmissionParentBreadcrumbsService } from './submission/submission-parent-breadcrumb.service';

/**
* When not in production, endpoint responses can be mocked for testing purposes
Expand Down Expand Up @@ -245,6 +246,7 @@ const PROVIDERS = [
NotificationsService,
WorkspaceitemDataService,
WorkflowItemDataService,
SubmissionParentBreadcrumbsService,
DSpaceObjectDataService,
ConfigurationDataService,
DSOChangeAnalyzer,
Expand Down
14 changes: 14 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,14 @@
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
@@ -1,12 +1,11 @@
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { switchMap } from 'rxjs/operators';
import { RemoteData } from '../../data/remote-data';
import { getFirstCompletedRemoteData } from '../../shared/operators';
import { IdentifiableDataService } from '../../data/base/identifiable-data.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
Expand All @@ -15,7 +14,6 @@ import { IdentifiableDataService } from '../../data/base/identifiable-data.servi
export class SubmissionObjectResolver<T> implements Resolve<RemoteData<T>> {
constructor(
protected dataService: IdentifiableDataService<any>,
protected store: Store<any>,
) {
}

Expand All @@ -30,7 +28,7 @@ export class SubmissionObjectResolver<T> implements Resolve<RemoteData<T>> {
const itemRD$ = this.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<T>>),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../shared/operators';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import { BreadcrumbConfig } from '../../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { SubmissionParentBreadcrumbsService } from '../submission-parent-breadcrumb.service';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';
import { SubmissionObject } from '../models/submission-object.model';

/**
* 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>)),
);
}
}
59 changes: 59 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,59 @@
import { BreadcrumbsProviderService } from '../breadcrumbs/breadcrumbsProviderService';
import { Injectable } from '@angular/core';
import { Observable, switchMap, combineLatest, of as observableOf } from 'rxjs';
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../shared/operators';
import { Collection } from '../shared/collection.model';
import { DSONameService } from '../breadcrumbs/dso-name.service';
import { SubmissionObject } from './models/submission-object.model';
import { RemoteData } from '../data/remote-data';
import { DSOBreadcrumbsService } from '../breadcrumbs/dso-breadcrumbs.service';
import { getDSORoute } from '../../app-routing-paths';
import { SubmissionService } from '../../submission/submission.service';
import { CollectionDataService } from '../data/collection-data.service';
import { hasValue } from '../../shared/empty.util';

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

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

Check warning on line 26 in src/app/core/submission/submission-parent-breadcrumb.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/submission/submission-parent-breadcrumb.service.ts#L23-L26

Added lines #L23 - L26 were not covered by tests
) {
}

/**
* 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([

Check warning on line 38 in src/app/core/submission/submission-parent-breadcrumb.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/submission/submission-parent-breadcrumb.service.ts#L38

Added line #L38 was not covered by tests
(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(

Check warning on line 47 in src/app/core/submission/submission-parent-breadcrumb.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/submission/submission-parent-breadcrumb.service.ts#L47

Added line #L47 was not covered by tests
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
);
} else {
return observableOf(collection);

Check warning on line 52 in src/app/core/submission/submission-parent-breadcrumb.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/submission/submission-parent-breadcrumb.service.ts#L52

Added line #L52 was not covered by tests
}
}),
switchMap((collection: Collection) => this.dsoBreadcrumbsService.getBreadcrumbs(collection, getDSORoute(collection))),

Check warning on line 55 in src/app/core/submission/submission-parent-breadcrumb.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/submission/submission-parent-breadcrumb.service.ts#L55

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

}
27 changes: 25 additions & 2 deletions src/app/submission/submission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { Observable, of as observableOf, Subscription, timer as observableTimer } from 'rxjs';
import { catchError, concatMap, distinctUntilChanged, filter, find, map, startWith, take, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Store, MemoizedSelector, createSelector, select } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';

import { submissionSelector, SubmissionState } from './submission.reducers';
Expand Down Expand Up @@ -47,6 +47,20 @@
import { SubmissionSectionObject } from './objects/submission-section-object.model';
import { SubmissionError } from './objects/submission-error.model';

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

/**
* A service that provides methods used in submission process.
*/
Expand Down Expand Up @@ -96,10 +110,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 123 in src/app/submission/submission.service.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L123 was not covered by tests
}

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