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
32 changes: 31 additions & 1 deletion cypress/e2e/collection-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { testA11y } from 'cypress/support/utils';

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

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 +14,31 @@ describe('Collection Page', () => {
testA11y('ds-collection-page');
});
});

describe('Collection Page -> Collection-edit menu', () => {
beforeEach(() => {
cy.visit(COLLECTION_PAGE);
// Open login menu in header & verify <ds-log-in> tag is visible
cy.get('[data-test="login-menu"]').click();
cy.get('.form-login').should('be.visible');

// Login, and the <ds-log-in> tag should no longer exist
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);
});
cy.get('ds-log-in').should('not.exist');
});

it('Edit menu should exist for admins.', () => {
// <dso-edit-menu> tag must be loaded
cy.get('ds-dso-edit-menu').should('be.visible');
});

it('Options menu should include submit item on collection pages.', () => {
// Open the Options menu and verify the Submit item entry is available
cy.get('ds-dso-edit-menu button[aria-label="Options"]').click();
cy.get('[data-test="link-menu-item.collection.submit.item"]')
.should('be.visible')
.and('contain', 'Submit item');
});
});
37 changes: 36 additions & 1 deletion cypress/e2e/community-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { testA11y } from 'cypress/support/utils';

const COMMUNITY_PAGE = '/communities/'.concat(Cypress.expose('DSPACE_TEST_COMMUNITY'));


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

it('should pass accessibility tests', () => {
cy.visit('/communities/'.concat(Cypress.expose('DSPACE_TEST_COMMUNITY')));
cy.visit(COMMUNITY_PAGE);

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


describe('Community Page -> Community-edit menu', () => {
beforeEach(() => {
cy.visit(COMMUNITY_PAGE);
// Open login menu in header & verify <ds-log-in> tag is visible
cy.get('[data-test="login-menu"]').click();
cy.get('.form-login').should('be.visible');

// Login, and the <ds-log-in> tag should no longer exist
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);
});
cy.get('ds-log-in').should('not.exist');
});

it('Edit menu should exist for admins.', () => {
// <dso-edit-menu> tag must be loaded
cy.get('ds-dso-edit-menu').should('be.visible');
});

it('Options menu should include Add-Community and Add Collections Buttons.', () => {
// Open the Options menu and verify the Add Community and Add Collection entries are available
cy.get('ds-dso-edit-menu button[aria-label="Options"]').click();
cy.get('[data-test="link-menu-item.community.add.sub-community"]')
.should('be.visible')
.and('contain', 'Add Community');
cy.get('[data-test="link-menu-item.community.add.sub-collection"]')
.should('be.visible')
.and('contain', 'Add Collection');
});
});
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[];
}),
);
}
}
Loading
Loading