Skip to content
2 changes: 2 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ collection:
showSidebar: true
edit:
undoTimeout: 10000 # 10 seconds
# Whether to show a submit button on item pages instead of having an entry in the dso-edit menu.
showSubmitButton: false

# Theme Config
themes:
Expand Down
25 changes: 24 additions & 1 deletion cypress/e2e/collection-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { testA11y } from 'cypress/support/utils';

const COLLECTION_PAGE = '/collections/'.concat(Cypress.expose('DSPACE_TEST_COLLECTION'));
const LOGIN_PAGE = '/login';

describe('Collection Page', () => {

it('should pass accessibility tests', () => {
cy.visit('/collections/'.concat(Cypress.expose('DSPACE_TEST_COLLECTION')));
cy.visit(COLLECTION_PAGE);

// <ds-collection-page> tag must be loaded
cy.get('ds-collection-page').should('be.visible');
Expand All @@ -12,3 +15,23 @@ describe('Collection Page', () => {
testA11y('ds-collection-page');
});
});

describe('Collection Page -> Collection-edit menu', () => {
beforeEach(() => {
// All tests start with visiting the Login Page
cy.visit(LOGIN_PAGE);

// These page elements are restricted, so we will be shown the login form. Fill it out & submit.
cy.env(['DSPACE_TEST_ADMIN_USER', 'DSPACE_TEST_ADMIN_PASSWORD']).then(({ DSPACE_TEST_ADMIN_USER, DSPACE_TEST_ADMIN_PASSWORD }) => {
cy.loginViaForm(DSPACE_TEST_ADMIN_USER, DSPACE_TEST_ADMIN_PASSWORD);
});

// Now we can visit the collection page:
cy.visit(COLLECTION_PAGE);
});

it('Edit menu should exist for admins.', () => {
// <dso-edit-menu> tag must be loaded
cy.get('ds-dso-edit-menu').should('be.visible');
});
});
8 changes: 8 additions & 0 deletions src/app/app.menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { buildMenuStructure } from './shared/menu/menu.structure';
import { MenuID } from './shared/menu/menu-id.model';
import { MenuRoute } from './shared/menu/menu-route.model';
import { AccessControlMenuProvider } from './shared/menu/providers/access-control.menu';
import { AddSubObjectsMenuProvider } from './shared/menu/providers/add-sub-objects.menu';
import { AdminSearchMenuProvider } from './shared/menu/providers/admin-search.menu';
import { AuditLogsMenuProvider } from './shared/menu/providers/audit-item.menu';
import { AuditOverviewMenuProvider } from './shared/menu/providers/audit-overview.menu';
Expand All @@ -35,6 +36,7 @@ import { NotificationsMenuProvider } from './shared/menu/providers/notifications
import { ProcessesMenuProvider } from './shared/menu/providers/processes.menu';
import { RegistriesMenuProvider } from './shared/menu/providers/registries.menu';
import { StatisticsMenuProvider } from './shared/menu/providers/statistics.menu';
import { SubmitNewItemMenuProvider } from './shared/menu/providers/submit-new-item.menu';
import { SystemWideAlertMenuProvider } from './shared/menu/providers/system-wide-alert.menu';
import { WithdrawnReinstateItemMenuProvider } from './shared/menu/providers/withdrawn-reinstate-item.menu';
import { WorkflowMenuProvider } from './shared/menu/providers/workflow.menu';
Expand Down Expand Up @@ -95,6 +97,12 @@ export const MENUS = buildMenuStructure({
MenuRoute.COLLECTION_PAGE,
MenuRoute.ITEM_PAGE,
),
AddSubObjectsMenuProvider.onRoute(
MenuRoute.COMMUNITY_PAGE,
),
SubmitNewItemMenuProvider.onRoute(
MenuRoute.COLLECTION_PAGE,
),
WithdrawnReinstateItemMenuProvider.onRoute(
MenuRoute.ITEM_PAGE,
),
Expand Down
4 changes: 4 additions & 0 deletions src/app/collection-page/collection-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
[hasInnerHtml]="true"
[title]="'collection.page.news'">
</ds-comcol-page-content>
@if (showSubmitButton$ | async) {
<!-- TODO: Adapt button label to collection entity type? --->
<a routerLink="/submit" [queryParams]="{ 'collection': collection.uuid }" class="btn btn-secondary" [innerText]="'collection.page.submit-button' | translate"></a>
}
</header>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
Expand Down
17 changes: 17 additions & 0 deletions src/app/collection-page/collection-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Inject,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
RouterLink,
RouterOutlet,
} from '@angular/router';
import {
APP_CONFIG,
AppConfig,
} from '@dspace/config/app-config.interface';
import { AuthService } from '@dspace/core/auth/auth.service';
import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
import { SortOptions } from '@dspace/core/cache/models/sort-options.model';
Expand Down Expand Up @@ -63,6 +69,7 @@ import { VarDirective } from '../shared/utils/var.directive';
ComcolPageLogoComponent,
DsoEditMenuComponent,
ErrorComponent,
RouterLink,
RouterOutlet,
ThemedComcolPageBrowseByComponent,
ThemedComcolPageContentComponent,
Expand All @@ -88,12 +95,18 @@ export class CollectionPageComponent implements OnInit {
*/
collectionPageRoute$: Observable<string>;

/**
* Whether to show a submit button for users on the collection page.
*/
showSubmitButton$: Observable<boolean>;

constructor(
protected route: ActivatedRoute,
protected router: Router,
protected authService: AuthService,
protected authorizationDataService: AuthorizationDataService,
public dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
) {
}

Expand All @@ -114,6 +127,10 @@ export class CollectionPageComponent implements OnInit {
getAllSucceededRemoteDataPayload(),
map((collection) => getCollectionPageRoute(collection.id)),
);

this.showSubmitButton$ = this.authorizationDataService.isAuthorized(FeatureID.CanSubmit).pipe(
map(authorized => authorized && this.appConfig.collection.showSubmitButton),
);
}

isNotEmpty(object: any) {
Expand Down
79 changes: 79 additions & 0 deletions src/app/shared/menu/providers/add-sub-objects.menu.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { TestBed } from '@angular/core/testing';
import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service';
import { Community } from '@dspace/core/shared/community.model';
import { COMMUNITY } from '@dspace/core/shared/community.resource-type';
import { of } from 'rxjs';

import { MenuItemType } from '../menu-item-type.model';
import { PartialMenuSection } from '../menu-provider.model';
import { AddSubObjectsMenuProvider } from './add-sub-objects.menu';

describe('AddSubObjectsMenuProvider', () => {

const expectedSections: PartialMenuSection[] = [
{
visible: true,
model: {
type: MenuItemType.LINK,
text: 'community.add.sub-community',
link: '/communities/create',
queryParams: {
parent: 'test-uuid',
},
},
icon: 'plus',
},
{
visible: true,
model: {
type: MenuItemType.LINK,
text: 'community.add.sub-collection',
link: '/collections/create',
queryParams: {
parent: 'test-uuid',
},
},
icon: 'plus',
},
];

let provider: AddSubObjectsMenuProvider;

const dso: Community = Object.assign(new Community(), {
type: COMMUNITY.value,
uuid: 'test-uuid',
_links: { self: { href: 'self-link' } },
});


let authorizationService;

beforeEach(() => {

authorizationService = jasmine.createSpyObj('authorizationService', {
'isAuthorized': of(true),
});

TestBed.configureTestingModule({
providers: [
AddSubObjectsMenuProvider,
{ provide: AuthorizationDataService, useValue: authorizationService },
],
});
provider = TestBed.inject(AddSubObjectsMenuProvider);
});

it('should be created', () => {
expect(provider).toBeTruthy();
});

describe('getSectionsForContext', () => {
it('should return the expected sections', (done) => {
provider.getSectionsForContext(dso).subscribe((sections) => {
expect(sections).toEqual(expectedSections);
done();
});
});
});

});
68 changes: 68 additions & 0 deletions src/app/shared/menu/providers/add-sub-objects.menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { Injectable } from '@angular/core';
import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '@dspace/core/data/feature-authorization/feature-id';
import { DSpaceObject } from '@dspace/core/shared/dspace-object.model';
import {
combineLatest,
Observable,
} from 'rxjs';
import { map } from 'rxjs/operators';

import { LinkMenuItemModel } from '../menu-item/models/link.model';
import { MenuItemType } from '../menu-item-type.model';
import { PartialMenuSection } from '../menu-provider.model';
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';

/**
* Menu provider to create the "Edit" option in the DSO edit menu
*/
@Injectable()
export class AddSubObjectsMenuProvider extends DSpaceObjectPageMenuProvider {
constructor(
protected authorizationDataService: AuthorizationDataService,
) {
super();
}

public getSectionsForContext(dso: DSpaceObject): Observable<PartialMenuSection[]> {
return combineLatest([
this.authorizationDataService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
]).pipe(
map(([canEditObject]) => {
return [
{
visible: canEditObject,
model: {
type: MenuItemType.LINK,
text: 'community.add.sub-community',
link: '/communities/create',
queryParams: {
parent: dso.uuid,
},
} as LinkMenuItemModel,
icon: 'plus',
},
{
visible: canEditObject,
model: {
type: MenuItemType.LINK,
text: 'community.add.sub-collection',
link: '/collections/create',
queryParams: {
parent: dso.uuid,
},
} as LinkMenuItemModel,
icon: 'plus',
},
] as PartialMenuSection[];
}),
);
}
}
79 changes: 79 additions & 0 deletions src/app/shared/menu/providers/submit-new-item.menu.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { TestBed } from '@angular/core/testing';
import { APP_CONFIG } from '@dspace/config/app-config.interface';
import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service';
import { Collection } from '@dspace/core/shared/collection.model';
import { COLLECTION } from '@dspace/core/shared/collection.resource-type';
import {
Observable,
of,
} from 'rxjs';

import { environment } from '../../../../environments/environment';
import { MenuItemType } from '../menu-item-type.model';
import { PartialMenuSection } from '../menu-provider.model';
import { SubmitNewItemMenuProvider } from './submit-new-item.menu';

describe('SubmitNewItemMenuProvider', () => {

const expectedSections: PartialMenuSection[] = [
{
visible: true,
model: {
type: MenuItemType.LINK,
text: 'collection.submit.item',
link: '/submit',
queryParams: {
collection: 'test-uuid',
},
},
icon: 'plus',
},
];

let provider: SubmitNewItemMenuProvider;

const dso: Collection = Object.assign(new Collection(), {
type: COLLECTION.value,
uuid: 'test-uuid',
_links: { self: { href: 'self-link' } },
});


let authorizationService: { isAuthorized: Observable<boolean> ; };

beforeEach(() => {

authorizationService = jasmine.createSpyObj('authorizationService', {
'isAuthorized': of(true),
});

TestBed.configureTestingModule({
providers: [
SubmitNewItemMenuProvider,
{ provide: AuthorizationDataService, useValue: authorizationService },
{ provide: APP_CONFIG, useValue: environment },
],
});
provider = TestBed.inject(SubmitNewItemMenuProvider);
});

it('should be created', () => {
expect(provider).toBeTruthy();
});

describe('getSectionsForContext', () => {
it('should return the expected sections', (done) => {
provider.getSectionsForContext(dso).subscribe((sections) => {
expect(sections).toEqual(expectedSections);
done();
});
});

it('should set visibility depending on authorization and collection.showSubmitButton', done => {
provider.getSectionsForContext(dso).subscribe((sections) => {
expect(sections[0].visible).toEqual(authorizationService.isAuthorized && !environment.collection.showSubmitButton);
done();
});
});
});
});
Loading
Loading