mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-13 21:13:07 +00:00
Merge remote-tracking branch 'upstream/main' into w2p-74199_Admin-search-dialogs
This commit is contained in:
@@ -17,10 +17,9 @@ coverage:
|
||||
# Configuration for patch-level checks. This checks the relative coverage of the new PR code ONLY.
|
||||
patch:
|
||||
default:
|
||||
# For each PR, make sure the coverage of the new code is within 1% of current overall coverage.
|
||||
# We let 'patch' be more lenient as we only require *project* coverage to not drop significantly.
|
||||
target: auto
|
||||
threshold: 1%
|
||||
# Enable informational mode, which just provides info to reviewers & always passes
|
||||
# https://docs.codecov.io/docs/commit-status#section-informational
|
||||
informational: true
|
||||
|
||||
# Turn PR comments "off". This feature adds the code coverage summary as a
|
||||
# comment on each PR. See https://docs.codecov.io/docs/pull-request-comments
|
||||
|
@@ -12,3 +12,6 @@ trim_trailing_whitespace = true
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
@@ -88,6 +88,7 @@
|
||||
"debug-loader": "^0.0.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"express": "4.16.2",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"fast-json-patch": "^2.0.7",
|
||||
"file-saver": "^1.3.8",
|
||||
"filesize": "^6.1.0",
|
||||
|
@@ -54,13 +54,6 @@ import(environmentFilePath)
|
||||
function generateEnvironmentFile(file: GlobalConfig): void {
|
||||
file.production = production;
|
||||
buildBaseUrls(file);
|
||||
|
||||
// TODO remove workaround in beta 5
|
||||
if (file.rest.nameSpace.match("(.*)/api/?$") !== null) {
|
||||
file.rest.nameSpace = getNameSpace(file.rest.nameSpace);
|
||||
console.log(colors.white.bgMagenta.bold(`The rest.nameSpace property in your environment file or in your DSPACE_REST_NAMESPACE environment variable ends with '/api'.\nThis is deprecated. As '/api' isn't configurable on the rest side, it shouldn't be repeated in every environment file.\nPlease change the rest nameSpace to '${file.rest.nameSpace}'`));
|
||||
}
|
||||
|
||||
const contents = `export const environment = ` + JSON.stringify(file);
|
||||
writeFile(targetPath, contents, (err) => {
|
||||
if (err) {
|
||||
@@ -119,16 +112,5 @@ function getPort(port: number): string {
|
||||
}
|
||||
|
||||
function getNameSpace(nameSpace: string): string {
|
||||
// TODO remove workaround in beta 5
|
||||
const apiMatches = nameSpace.match("(.*)/api/?$");
|
||||
if (apiMatches != null) {
|
||||
let newValue = '/'
|
||||
if (hasValue(apiMatches[1])) {
|
||||
newValue = apiMatches[1];
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
else {
|
||||
return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : '';
|
||||
}
|
||||
}
|
||||
|
21
server.ts
21
server.ts
@@ -28,12 +28,13 @@ import * as compression from 'compression';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import { join } from 'path';
|
||||
|
||||
import { enableProdMode, NgModuleFactory, Type } from '@angular/core';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||
import { environment } from './src/environments/environment';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { hasValue, hasNoValue } from './src/app/shared/empty.util';
|
||||
import { hasNoValue, hasValue } from './src/app/shared/empty.util';
|
||||
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
||||
|
||||
/*
|
||||
* Set path for the browser application's dist folder
|
||||
@@ -121,6 +122,19 @@ function cacheControl(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the rateLimiter property is present
|
||||
* When it is present, the rateLimiter will be enabled. When it is undefined, the rateLimiter will be disabled.
|
||||
*/
|
||||
if (hasValue((environment.ui as UIServerConfig).rateLimiter)) {
|
||||
const RateLimit = require('express-rate-limit');
|
||||
const limiter = new RateLimit({
|
||||
windowMs: (environment.ui as UIServerConfig).rateLimiter.windowMs,
|
||||
max: (environment.ui as UIServerConfig).rateLimiter.max
|
||||
});
|
||||
app.use(limiter);
|
||||
}
|
||||
|
||||
/*
|
||||
* Serve static resources (images, i18n messages, …)
|
||||
*/
|
||||
@@ -209,8 +223,9 @@ if (environment.ui.ssl) {
|
||||
certificate: certificate
|
||||
});
|
||||
} else {
|
||||
console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.');
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
||||
|
||||
pem.createCertificate({
|
||||
days: 1,
|
||||
|
@@ -13,6 +13,7 @@ import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { getCollectionEditRoute } from '../../../../../+collection-page/collection-page-routing-paths';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
|
||||
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
let component: CollectionAdminSearchResultGridElementComponent;
|
||||
@@ -26,6 +27,11 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
searchResult.indexableObject = new Collection();
|
||||
searchResult.indexableObject.uuid = id;
|
||||
}
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: {}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
@@ -39,6 +45,7 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: BitstreamDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -14,8 +14,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { CommunityAdminSearchResultGridElementComponent } from './community-admin-search-result-grid-element.component';
|
||||
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
|
||||
import { Community } from '../../../../../core/shared/community.model';
|
||||
import { CommunityAdminSearchResultListElementComponent } from '../../admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
|
||||
import { getCommunityEditRoute } from '../../../../../+community-page/community-page-routing-paths';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
|
||||
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
let component: CommunityAdminSearchResultGridElementComponent;
|
||||
@@ -29,6 +29,11 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
searchResult.indexableObject = new Community();
|
||||
searchResult.indexableObject.uuid = id;
|
||||
}
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: {}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
@@ -42,6 +47,7 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: BitstreamDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
|
@@ -12,6 +12,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('CreateCollectionPageComponent', () => {
|
||||
let comp: CreateCollectionPageComponent;
|
||||
@@ -29,7 +30,8 @@ describe('CreateCollectionPageComponent', () => {
|
||||
},
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: RequestService, useValue: {}}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -7,6 +7,7 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Collection
|
||||
@@ -26,8 +27,9 @@ export class CreateCollectionPageComponent extends CreateComColPageComponent<Col
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(collectionDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
super(collectionDataService, communityDataService, routeService, router, notificationsService, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import { of as observableOf } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page.component';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('DeleteCollectionPageComponent', () => {
|
||||
let comp: DeleteCollectionPageComponent;
|
||||
@@ -22,6 +23,7 @@ describe('DeleteCollectionPageComponent', () => {
|
||||
{ provide: CollectionDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: NotificationsService, useValue: {} },
|
||||
{ provide: RequestService, useValue: {} }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -5,6 +5,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Collection
|
||||
@@ -22,8 +23,9 @@ export class DeleteCollectionPageComponent extends DeleteComColPageComponent<Col
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifications: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(dsoDataService, router, route, notifications, translate);
|
||||
super(dsoDataService, router, route, notifications, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCommunityPageComponent } from './create-community-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('CreateCommunityPageComponent', () => {
|
||||
let comp: CreateCommunityPageComponent;
|
||||
@@ -25,7 +26,8 @@ describe('CreateCommunityPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: { findById: () => observableOf({}) } },
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: RequestService, useValue: {} }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -6,6 +6,7 @@ import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Community
|
||||
@@ -24,8 +25,9 @@ export class CreateCommunityPageComponent extends CreateComColPageComponent<Comm
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(communityDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
super(communityDataService, communityDataService, routeService, router, notificationsService, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page.component';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('DeleteCommunityPageComponent', () => {
|
||||
let comp: DeleteCommunityPageComponent;
|
||||
@@ -22,6 +23,7 @@ describe('DeleteCommunityPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: NotificationsService, useValue: {} },
|
||||
{ provide: RequestService, useValue: {}}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Community
|
||||
@@ -22,8 +23,10 @@ export class DeleteCommunityPageComponent extends DeleteComColPageComponent<Comm
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifications: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(dsoDataService, router, route, notifications, translate);
|
||||
super(dsoDataService, router, route, notifications, translate, requestService);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
@@ -8,7 +8,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { NgZone } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { CommunityListService, FlatNode } from './community-list-service';
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
|
||||
import { BehaviorSubject, Observable, } from 'rxjs';
|
||||
import { finalize, take, } from 'rxjs/operators';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* DataSource object needed by a CDK Tree to render its nodes.
|
||||
@@ -15,9 +16,9 @@ export class CommunityListDatasource implements DataSource<FlatNode> {
|
||||
|
||||
private communityList$ = new BehaviorSubject<FlatNode[]>([]);
|
||||
public loading$ = new BehaviorSubject<boolean>(false);
|
||||
private subLoadCommunities: Subscription;
|
||||
|
||||
constructor(private communityListService: CommunityListService,
|
||||
private zone: NgZone) {
|
||||
constructor(private communityListService: CommunityListService) {
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
|
||||
@@ -26,13 +27,13 @@ export class CommunityListDatasource implements DataSource<FlatNode> {
|
||||
|
||||
loadCommunities(findOptions: FindListOptions, expandedNodes: FlatNode[]) {
|
||||
this.loading$.next(true);
|
||||
this.zone.runOutsideAngular(() => {
|
||||
this.communityListService.loadCommunities(findOptions, expandedNodes).pipe(
|
||||
take(1),
|
||||
finalize(() => this.zone.run(() => this.loading$.next(false))),
|
||||
if (hasValue(this.subLoadCommunities)) {
|
||||
this.subLoadCommunities.unsubscribe();
|
||||
}
|
||||
this.subLoadCommunities = this.communityListService.loadCommunities(findOptions, expandedNodes).pipe(
|
||||
finalize(() => this.loading$.next(false)),
|
||||
).subscribe((flatNodes: FlatNode[]) => {
|
||||
this.zone.run(() => this.communityList$.next(flatNodes));
|
||||
});
|
||||
this.communityList$.next(flatNodes);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,13 +2,12 @@ import { Injectable } from '@angular/core';
|
||||
import { createSelector, Store } from '@ngrx/store';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { map, flatMap } from 'rxjs/operators';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||
import { PageInfo } from '../core/shared/page-info.model';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
@@ -148,7 +147,7 @@ export class CommunityListService {
|
||||
return new PaginatedList(newPageInfo, newPage);
|
||||
})
|
||||
);
|
||||
return topComs$.pipe(flatMap((topComs: PaginatedList<Community>) => this.transformListOfCommunities(topComs, 0, null, expandedNodes)));
|
||||
return topComs$.pipe(switchMap((topComs: PaginatedList<Community>) => this.transformListOfCommunities(topComs, 0, null, expandedNodes)));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,9 +227,13 @@ export class CommunityListService {
|
||||
currentPage: i
|
||||
})
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
flatMap((rd: RemoteData<PaginatedList<Community>>) =>
|
||||
this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes))
|
||||
switchMap((rd: RemoteData<PaginatedList<Community>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
subcoms = [...subcoms, nextSetOfSubcommunitiesPage];
|
||||
@@ -246,14 +249,17 @@ export class CommunityListService {
|
||||
currentPage: i
|
||||
})
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
let nodes = rd.payload.page
|
||||
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
|
||||
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
|
||||
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
|
||||
}
|
||||
return nodes;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}),
|
||||
);
|
||||
collections = [...collections, nextSetOfCollectionsPage];
|
||||
@@ -275,14 +281,24 @@ export class CommunityListService {
|
||||
let hasColls$: Observable<boolean>;
|
||||
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((results) => results.payload.totalElements > 0),
|
||||
map((rd: RemoteData<PaginatedList<Community>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return rd.payload.totalElements > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((results) => results.payload.totalElements > 0),
|
||||
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return rd.payload.totalElements > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let hasChildren$: Observable<boolean>;
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<button type="button" class="btn btn-default" cdkTreeNodeToggle
|
||||
[attr.aria-label]="'toggle ' + node.name"
|
||||
(click)="toggleExpanded(node)"
|
||||
[ngClass]="(node.isExpandable$ | async) ? 'visible' : 'invisible'">
|
||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'">
|
||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { FindListOptions } from '../../core/data/request.models';
|
||||
@@ -24,15 +24,14 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
public loadingNode: FlatNode;
|
||||
|
||||
treeControl = new FlatTreeControl<FlatNode>(
|
||||
(node) => node.level, (node) => true
|
||||
(node: FlatNode) => node.level, (node: FlatNode) => true
|
||||
);
|
||||
|
||||
dataSource: CommunityListDatasource;
|
||||
|
||||
paginationConfig: FindListOptions;
|
||||
|
||||
constructor(private communityListService: CommunityListService,
|
||||
private zone: NgZone) {
|
||||
constructor(private communityListService: CommunityListService) {
|
||||
this.paginationConfig = new FindListOptions();
|
||||
this.paginationConfig.elementsPerPage = 2;
|
||||
this.paginationConfig.currentPage = 1;
|
||||
@@ -40,7 +39,7 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource = new CommunityListDatasource(this.communityListService, this.zone);
|
||||
this.dataSource = new CommunityListDatasource(this.communityListService);
|
||||
this.communityListService.getLoadingNodeFromStore().pipe(take(1)).subscribe((result) => {
|
||||
this.loadingNode = result;
|
||||
});
|
||||
@@ -65,7 +64,7 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the expanded variable of a node, adds it to the exapanded nodes list and reloads the tree so this node is expanded
|
||||
* Toggles the expanded variable of a node, adds it to the expanded nodes list and reloads the tree so this node is expanded
|
||||
* @param node Node we want to expand
|
||||
*/
|
||||
toggleExpanded(node: FlatNode) {
|
||||
|
@@ -6,7 +6,6 @@ import { HALLink } from '../../shared/hal-link.model';
|
||||
import { HALResource } from '../../shared/hal-resource.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import * as decorators from './build-decorators';
|
||||
import { getDataServiceFor } from './build-decorators';
|
||||
import { LinkService } from './link.service';
|
||||
|
||||
const spyOnFunction = <T>(obj: T, func: keyof T) => {
|
||||
|
@@ -17,6 +17,7 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { FindByIDRequest, FindListOptions } from './request.models';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestService } from './request.service';
|
||||
import {createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils';
|
||||
|
||||
const LINK_NAME = 'test';
|
||||
|
||||
@@ -51,7 +52,9 @@ describe('ComColDataService', () => {
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: any = {};
|
||||
|
||||
const rdbService = {} as RemoteDataBuildService;
|
||||
const rdbService = {
|
||||
buildSingle : () => null
|
||||
} as any;
|
||||
const store = {} as Store<CoreState>;
|
||||
const notificationsService = {} as NotificationsService;
|
||||
const http = {} as HttpClient;
|
||||
@@ -178,6 +181,90 @@ describe('ComColDataService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache refresh', () => {
|
||||
let communityWithoutParentHref;
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
halService = {
|
||||
getEndpoint: (linkPath) => 'https://rest.api/core/' + linkPath
|
||||
};
|
||||
service = initTestService();
|
||||
|
||||
})
|
||||
describe('cache refreshed top level community', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(rdbService, 'buildSingle').and.returnValue(createNoContentRemoteDataObject$());
|
||||
data = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'top level community'
|
||||
}]
|
||||
}),
|
||||
_links: {
|
||||
parentCommunity: {
|
||||
href: 'topLevel/parentCommunity'
|
||||
}
|
||||
}
|
||||
};
|
||||
communityWithoutParentHref = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'top level community'
|
||||
}]
|
||||
}),
|
||||
_links: {}
|
||||
};
|
||||
});
|
||||
it('top level community cache refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('https://rest.api/core/communities/search/top');
|
||||
});
|
||||
it('top level community without parent link, cache not refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(communityWithoutParentHref));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache refreshed child community', () => {
|
||||
beforeEach(() => {
|
||||
const parentCommunity = Object.assign(new Community(), {
|
||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'parent community'
|
||||
}],
|
||||
_links: {}
|
||||
});
|
||||
spyOn(rdbService, 'buildSingle').and.returnValue(createSuccessfulRemoteDataObject$(parentCommunity));
|
||||
data = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'child community'
|
||||
}]
|
||||
}),
|
||||
_links: {
|
||||
parentCommunity: {
|
||||
href: 'child/parentCommunity'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
it('child level community cache refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -21,12 +21,14 @@ import {
|
||||
configureRequest,
|
||||
getRemoteDataPayload,
|
||||
getResponseFromEntry,
|
||||
getSucceededOrNoContentResponse,
|
||||
getSucceededRemoteData
|
||||
} from '../shared/operators';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import {Collection} from '../shared/collection.model';
|
||||
|
||||
export abstract class ComColDataService<T extends CacheableObject> extends DataService<T> {
|
||||
protected abstract cds: CommunityDataService;
|
||||
@@ -119,4 +121,23 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public refreshCache(dso: T) {
|
||||
const parentCommunityUrl = this.parentCommunityUrlLookup(dso as any);
|
||||
if (!hasValue(parentCommunityUrl)) {
|
||||
return;
|
||||
}
|
||||
this.findByHref(parentCommunityUrl).pipe(
|
||||
getSucceededOrNoContentResponse(),
|
||||
take(1),
|
||||
).subscribe((rd: RemoteData<any>) => {
|
||||
const href = rd.hasSucceeded && !isEmpty(rd.payload.id) ? rd.payload.id : this.halService.getEndpoint('communities/search/top');
|
||||
this.requestService.removeByHrefSubstring(href)
|
||||
});
|
||||
}
|
||||
|
||||
private parentCommunityUrlLookup(dso: Collection | Community) {
|
||||
const parentCommunity = dso._links.parentCommunity;
|
||||
return isNotEmpty(parentCommunity) ? parentCommunity.href : null;
|
||||
}
|
||||
}
|
||||
|
@@ -55,4 +55,8 @@ export class RemoteData<T> {
|
||||
return this.state === RemoteDataState.Success;
|
||||
}
|
||||
|
||||
get hasNoContent(): boolean {
|
||||
return this.statusCode === 204;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -75,6 +75,10 @@ export const getSucceededRemoteWithNotEmptyData = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
||||
|
||||
export const getSucceededOrNoContentResponse = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded || rd.hasNoContent));
|
||||
|
||||
/**
|
||||
* Get the first successful remotely retrieved object
|
||||
*
|
||||
|
@@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
import { RouteService } from '../../../core/services/route.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import {TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { SharedModule } from '../../shared.module';
|
||||
@@ -12,12 +12,14 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { CreateComColPageComponent } from './create-comcol-page.component';
|
||||
import {
|
||||
createFailedRemoteDataObject$,
|
||||
createFailedRemoteDataObject$, createNoContentRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../remote-data.utils';
|
||||
import { ComColDataService } from '../../../core/data/comcol-data.service';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import {getTestScheduler} from 'jasmine-marbles';
|
||||
|
||||
describe('CreateComColPageComponent', () => {
|
||||
let comp: CreateComColPageComponent<DSpaceObject>;
|
||||
@@ -29,9 +31,12 @@ describe('CreateComColPageComponent', () => {
|
||||
|
||||
let community;
|
||||
let newCommunity;
|
||||
let parentCommunity;
|
||||
let communityDataServiceStub;
|
||||
let routeServiceStub;
|
||||
let routerStub;
|
||||
let requestServiceStub;
|
||||
let scheduler;
|
||||
|
||||
const logoEndpoint = 'rest/api/logo/endpoint';
|
||||
|
||||
@@ -41,7 +46,18 @@ describe('CreateComColPageComponent', () => {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'test community'
|
||||
}]
|
||||
}],
|
||||
_links: {}
|
||||
});
|
||||
|
||||
parentCommunity = Object.assign(new Community(), {
|
||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'parent community'
|
||||
}],
|
||||
_links: {}
|
||||
});
|
||||
|
||||
newCommunity = Object.assign(new Community(), {
|
||||
@@ -49,7 +65,8 @@ describe('CreateComColPageComponent', () => {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'new community'
|
||||
}]
|
||||
}],
|
||||
_links: {}
|
||||
});
|
||||
|
||||
communityDataServiceStub = {
|
||||
@@ -61,7 +78,9 @@ describe('CreateComColPageComponent', () => {
|
||||
}]
|
||||
})),
|
||||
create: (com, uuid?) => createSuccessfulRemoteDataObject$(newCommunity),
|
||||
getLogoEndpoint: () => observableOf(logoEndpoint)
|
||||
getLogoEndpoint: () => observableOf(logoEndpoint),
|
||||
findByHref: () => null,
|
||||
refreshCache: () => {return}
|
||||
};
|
||||
|
||||
routeServiceStub = {
|
||||
@@ -71,6 +90,10 @@ describe('CreateComColPageComponent', () => {
|
||||
navigate: (commands) => commands
|
||||
};
|
||||
|
||||
requestServiceStub = jasmine.createSpyObj('RequestService', {
|
||||
removeByHrefSubstring: jasmine.createSpy('removeByHrefSubstring'),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -82,7 +105,8 @@ describe('CreateComColPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: Router, useValue: routerStub },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: RequestService, useValue: requestServiceStub}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -97,6 +121,7 @@ describe('CreateComColPageComponent', () => {
|
||||
communityDataService = (comp as any).communityDataService;
|
||||
routeService = (comp as any).routeService;
|
||||
router = (comp as any).router;
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
describe('onSubmit', () => {
|
||||
@@ -111,6 +136,7 @@ describe('CreateComColPageComponent', () => {
|
||||
value: 'test'
|
||||
}]
|
||||
}),
|
||||
_links: {},
|
||||
uploader: {
|
||||
options: {
|
||||
url: ''
|
||||
@@ -123,19 +149,23 @@ describe('CreateComColPageComponent', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('should navigate when successful', () => {
|
||||
it('should navigate and refresh cache when successful', () => {
|
||||
spyOn(router, 'navigate');
|
||||
comp.onSubmit(data);
|
||||
fixture.detectChanges();
|
||||
spyOn((dsoDataService as any), 'refreshCache')
|
||||
scheduler.schedule(() => comp.onSubmit(data));
|
||||
scheduler.flush();
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
expect((dsoDataService as any).refreshCache).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not navigate on failure', () => {
|
||||
it('should neither navigate nor refresh cache on failure', () => {
|
||||
spyOn(router, 'navigate');
|
||||
spyOn(dsoDataService, 'create').and.returnValue(createFailedRemoteDataObject$(newCommunity));
|
||||
comp.onSubmit(data);
|
||||
fixture.detectChanges();
|
||||
spyOn(dsoDataService, 'refreshCache')
|
||||
scheduler.schedule(() => comp.onSubmit(data));
|
||||
scheduler.flush();
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect((dsoDataService as any).refreshCache).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -148,6 +178,7 @@ describe('CreateComColPageComponent', () => {
|
||||
value: 'test'
|
||||
}]
|
||||
}),
|
||||
_links: {},
|
||||
uploader: {
|
||||
options: {
|
||||
url: ''
|
||||
@@ -164,21 +195,21 @@ describe('CreateComColPageComponent', () => {
|
||||
|
||||
it('should not navigate', () => {
|
||||
spyOn(router, 'navigate');
|
||||
comp.onSubmit(data);
|
||||
fixture.detectChanges();
|
||||
scheduler.schedule(() => comp.onSubmit(data));
|
||||
scheduler.flush();
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set the uploader\'s url to the logo\'s endpoint', () => {
|
||||
comp.onSubmit(data);
|
||||
fixture.detectChanges();
|
||||
scheduler.schedule(() => comp.onSubmit(data));
|
||||
scheduler.flush();
|
||||
expect(data.uploader.options.url).toEqual(logoEndpoint);
|
||||
});
|
||||
|
||||
it('should call the uploader\'s uploadAll', () => {
|
||||
spyOn(data.uploader, 'uploadAll');
|
||||
comp.onSubmit(data);
|
||||
fixture.detectChanges();
|
||||
scheduler.schedule(() => comp.onSubmit(data));
|
||||
scheduler.flush();
|
||||
expect(data.uploader.uploadAll).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -2,18 +2,21 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import {flatMap, take} from 'rxjs/operators';
|
||||
import { ComColDataService } from '../../../core/data/comcol-data.service';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { RouteService } from '../../../core/services/route.service';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import {
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
} from '../../../core/shared/operators';
|
||||
import { ResourceType } from '../../../core/shared/resource-type';
|
||||
import {hasValue, isNotEmpty, isNotUndefined} from '../../empty.util';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||
import {RequestService} from '../../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component representing the create page for communities and collections
|
||||
@@ -54,7 +57,8 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
|
||||
}
|
||||
@@ -76,13 +80,17 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
const dso = event.dso;
|
||||
const uploader = event.uploader;
|
||||
|
||||
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
||||
this.parentUUID$.pipe(
|
||||
take(1),
|
||||
flatMap((uuid: string) => {
|
||||
const params = uuid ? [new RequestParam('parent', uuid)] : [];
|
||||
this.dsoDataService.create(dso, ...params)
|
||||
.pipe(getSucceededRemoteData())
|
||||
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
||||
return this.dsoDataService.create(dso, ...params)
|
||||
.pipe(getFirstSucceededRemoteDataPayload()
|
||||
)
|
||||
}))
|
||||
.subscribe((dsoRD: TDomain) => {
|
||||
if (isNotUndefined(dsoRD)) {
|
||||
this.newUUID = dsoRD.payload.uuid;
|
||||
this.newUUID = dsoRD.uuid;
|
||||
if (uploader.queue.length > 0) {
|
||||
this.dsoDataService.getLogoEndpoint(this.newUUID).pipe(take(1)).subscribe((href: string) => {
|
||||
uploader.options.url = href;
|
||||
@@ -91,9 +99,9 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
} else {
|
||||
this.navigateToNewPage();
|
||||
}
|
||||
this.notificationsService.success(null, this.translate.get(this.type.value + '.create.notifications.success'));
|
||||
this.dsoDataService.refreshCache(dsoRD);
|
||||
}
|
||||
});
|
||||
this.notificationsService.success(null, this.translate.get(this.type.value + '.create.notifications.success'));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,5 +113,4 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
this.router.navigate([this.frontendURL + this.newUUID]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import {TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { SharedModule } from '../../shared.module';
|
||||
@@ -13,6 +13,10 @@ import { DataService } from '../../../core/data/data.service';
|
||||
import { DeleteComColPageComponent } from './delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||
import {RequestService} from '../../../core/data/request.service';
|
||||
import {getTestScheduler} from 'jasmine-marbles';
|
||||
import {createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../../remote-data.utils';
|
||||
import {ComColDataService} from '../../../core/data/comcol-data.service';
|
||||
|
||||
describe('DeleteComColPageComponent', () => {
|
||||
let comp: DeleteComColPageComponent<DSpaceObject>;
|
||||
@@ -22,9 +26,15 @@ describe('DeleteComColPageComponent', () => {
|
||||
|
||||
let community;
|
||||
let newCommunity;
|
||||
let parentCommunity;
|
||||
let routerStub;
|
||||
let routeStub;
|
||||
let notificationsService;
|
||||
let translateServiceStub;
|
||||
let requestServiceStub;
|
||||
|
||||
let scheduler;
|
||||
|
||||
const validUUID = 'valid-uuid';
|
||||
const invalidUUID = 'invalid-uuid';
|
||||
const frontendURL = '/testType';
|
||||
@@ -45,10 +55,21 @@ describe('DeleteComColPageComponent', () => {
|
||||
}]
|
||||
});
|
||||
|
||||
parentCommunity = Object.assign(new Community(), {
|
||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'parent community'
|
||||
}]
|
||||
});
|
||||
|
||||
dsoDataService = jasmine.createSpyObj(
|
||||
'dsoDataService',
|
||||
{
|
||||
delete: observableOf({ isSuccessful: true })
|
||||
delete: observableOf({ isSuccessful: true }),
|
||||
findByHref: jasmine.createSpy('findByHref'),
|
||||
refreshCache: jasmine.createSpy('refreshCache')
|
||||
});
|
||||
|
||||
routerStub = {
|
||||
@@ -59,6 +80,14 @@ describe('DeleteComColPageComponent', () => {
|
||||
data: observableOf(community)
|
||||
};
|
||||
|
||||
requestServiceStub = jasmine.createSpyObj('RequestService', {
|
||||
removeByHrefSubstring: jasmine.createSpy('removeByHrefSubstring')
|
||||
});
|
||||
|
||||
translateServiceStub = jasmine.createSpyObj('TranslateService', {
|
||||
instant: jasmine.createSpy('instant')
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -66,10 +95,12 @@ describe('DeleteComColPageComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
providers: [
|
||||
{ provide: DataService, useValue: dsoDataService },
|
||||
{ provide: ComColDataService, useValue: dsoDataService },
|
||||
{ provide: Router, useValue: routerStub },
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: TranslateService, useValue: translateServiceStub},
|
||||
{ provide: RequestService, useValue: requestServiceStub}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -82,43 +113,63 @@ describe('DeleteComColPageComponent', () => {
|
||||
notificationsService = (comp as any).notifications;
|
||||
(comp as any).frontendURL = frontendURL;
|
||||
router = (comp as any).router;
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
describe('onConfirm', () => {
|
||||
let data1;
|
||||
let data2;
|
||||
beforeEach(() => {
|
||||
data1 = Object.assign(new Community(), {
|
||||
data1 = {
|
||||
dso: Object.assign(new Community(), {
|
||||
uuid: validUUID,
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'test'
|
||||
}]
|
||||
});
|
||||
}),
|
||||
_links: {}
|
||||
};
|
||||
|
||||
data2 = Object.assign(new Community(), {
|
||||
data2 = {
|
||||
dso: Object.assign(new Community(), {
|
||||
uuid: invalidUUID,
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'test'
|
||||
}]
|
||||
});
|
||||
}),
|
||||
_links: {},
|
||||
uploader: {
|
||||
options: {
|
||||
url: ''
|
||||
},
|
||||
queue: [],
|
||||
/* tslint:disable:no-empty */
|
||||
uploadAll: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('should show an error notification on failure', () => {
|
||||
(dsoDataService.delete as any).and.returnValue(observableOf({ isSuccessful: false }));
|
||||
spyOn(router, 'navigate');
|
||||
comp.onConfirm(data2);
|
||||
scheduler.schedule(() => comp.onConfirm(data2));
|
||||
scheduler.flush();
|
||||
fixture.detectChanges();
|
||||
expect(notificationsService.error).toHaveBeenCalled();
|
||||
expect(dsoDataService.refreshCache).not.toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show a success notification on success and navigate', () => {
|
||||
spyOn(router, 'navigate');
|
||||
comp.onConfirm(data1);
|
||||
scheduler.schedule(() => comp.onConfirm(data1));
|
||||
scheduler.flush();
|
||||
fixture.detectChanges();
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
expect(dsoDataService.refreshCache).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@@ -3,11 +3,12 @@ import { Observable } from 'rxjs';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {RemoteData} from '../../../core/data/remote-data';
|
||||
import {first, map} from 'rxjs/operators';
|
||||
import { DataService } from '../../../core/data/data.service';
|
||||
import {DSpaceObject} from '../../../core/shared/dspace-object.model';
|
||||
import {NotificationsService} from '../../notifications/notifications.service';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {RestResponse} from '../../../core/cache/response.models';
|
||||
import {RequestService} from '../../../core/data/request.service';
|
||||
import {ComColDataService} from '../../../core/data/comcol-data.service';
|
||||
|
||||
/**
|
||||
* Component representing the delete page for communities and collections
|
||||
@@ -27,11 +28,12 @@ export class DeleteComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
public dsoRD$: Observable<RemoteData<TDomain>>;
|
||||
|
||||
public constructor(
|
||||
protected dsoDataService: DataService<TDomain>,
|
||||
protected dsoDataService: ComColDataService<TDomain>,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifications: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -50,6 +52,7 @@ export class DeleteComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
if (response.isSuccessful) {
|
||||
const successMessage = this.translate.instant((dso as any).type + '.delete.notification.success');
|
||||
this.notifications.success(successMessage)
|
||||
this.dsoDataService.refreshCache(dso);
|
||||
} else {
|
||||
const errorMessage = this.translate.instant((dso as any).type + '.delete.notification.fail');
|
||||
this.notifications.error(errorMessage)
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/', object.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="object.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="object.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -3,6 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||
|
||||
let collectionGridElementComponent: CollectionGridElementComponent;
|
||||
let fixture: ComponentFixture<CollectionGridElementComponent>;
|
||||
@@ -29,12 +30,17 @@ const mockCollectionWithoutAbstract: Collection = Object.assign(new Collection()
|
||||
}
|
||||
});
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: mockCollectionWithAbstract
|
||||
});
|
||||
|
||||
describe('CollectionGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CollectionGridElementComponent ],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract)}
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract)},
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { hasNoValue, hasValue } from '../../empty.util';
|
||||
import { followLink } from '../../utils/follow-link-config.model';
|
||||
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||
|
||||
/**
|
||||
* Component representing a grid element for collection
|
||||
@@ -11,8 +13,29 @@ import { listableObjectComponent } from '../../object-collection/shared/listable
|
||||
@Component({
|
||||
selector: 'ds-collection-grid-element',
|
||||
styleUrls: ['./collection-grid-element.component.scss'],
|
||||
templateUrl: './collection-grid-element.component.html'
|
||||
templateUrl: './collection-grid-element.component.html',
|
||||
})
|
||||
|
||||
@listableObjectComponent(Collection, ViewMode.GridElement)
|
||||
export class CollectionGridElementComponent extends AbstractListableElementComponent<Collection> {}
|
||||
export class CollectionGridElementComponent extends AbstractListableElementComponent<
|
||||
Collection
|
||||
> {
|
||||
private _object: Collection;
|
||||
|
||||
constructor(private linkService: LinkService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Input() set object(object: Collection) {
|
||||
this._object = object;
|
||||
if (hasValue(this._object) && hasNoValue(this._object.logo)) {
|
||||
this.linkService.resolveLink<Collection>(
|
||||
this._object,
|
||||
followLink('logo')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get object(): Collection {
|
||||
return this._object;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', object.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="object.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="object.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -3,6 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||
|
||||
let communityGridElementComponent: CommunityGridElementComponent;
|
||||
let fixture: ComponentFixture<CommunityGridElementComponent>;
|
||||
@@ -29,12 +30,17 @@ const mockCommunityWithoutAbstract: Community = Object.assign(new Community(), {
|
||||
}
|
||||
});
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: mockCommunityWithAbstract
|
||||
});
|
||||
|
||||
describe('CommunityGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CommunityGridElementComponent ],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract)}
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract)},
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { followLink } from '../../utils/follow-link-config.model';
|
||||
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||
import { hasNoValue, hasValue } from '../../empty.util';
|
||||
|
||||
/**
|
||||
* Component representing a grid element for a community
|
||||
@@ -15,4 +17,21 @@ import { listableObjectComponent } from '../../object-collection/shared/listable
|
||||
})
|
||||
|
||||
@listableObjectComponent(Community, ViewMode.GridElement)
|
||||
export class CommunityGridElementComponent extends AbstractListableElementComponent<Community> {}
|
||||
export class CommunityGridElementComponent extends AbstractListableElementComponent<Community> {
|
||||
private _object: Community;
|
||||
|
||||
constructor( private linkService: LinkService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Input() set object(object: Community) {
|
||||
this._object = object;
|
||||
if (hasValue(this._object) && hasNoValue(this._object.logo)) {
|
||||
this.linkService.resolveLink<Community>(this._object, followLink('logo'))
|
||||
}
|
||||
}
|
||||
|
||||
get object(): Community {
|
||||
return this._object;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,10 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
@@ -11,10 +17,9 @@ import { hasValue } from '../../empty.util';
|
||||
@Component({
|
||||
selector: 'ds-grid-thumbnail',
|
||||
styleUrls: ['./grid-thumbnail.component.scss'],
|
||||
templateUrl: './grid-thumbnail.component.html'
|
||||
templateUrl: './grid-thumbnail.component.html',
|
||||
})
|
||||
export class GridThumbnailComponent implements OnInit {
|
||||
|
||||
export class GridThumbnailComponent implements OnInit, OnChanges {
|
||||
@Input() thumbnail: Bitstream;
|
||||
|
||||
data: any = {};
|
||||
@@ -22,19 +27,47 @@ export class GridThumbnailComponent implements OnInit {
|
||||
/**
|
||||
* The default 'holder.js' image
|
||||
*/
|
||||
@Input() defaultImage? = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
|
||||
@Input() defaultImage? =
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
|
||||
|
||||
src: string;
|
||||
|
||||
errorHandler(event) {
|
||||
event.currentTarget.src = this.defaultImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the src
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links) && this.thumbnail._links.content.href) {
|
||||
this.src = this.thumbnail._links.content.href;
|
||||
} else {
|
||||
this.src = this.defaultImage
|
||||
this.src = this.defaultImage;
|
||||
|
||||
this.checkThumbnail(this.thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the old input is undefined and the new one is a bitsream then set src
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (
|
||||
!hasValue(changes.thumbnail.previousValue) &&
|
||||
hasValue(changes.thumbnail.currentValue)
|
||||
) {
|
||||
console.log('this.thumbnail', changes.thumbnail.currentValue);
|
||||
this.checkThumbnail(changes.thumbnail.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the Bitstream has any content than set the src
|
||||
*/
|
||||
checkThumbnail(thumbnail: Bitstream) {
|
||||
if (
|
||||
hasValue(thumbnail) &&
|
||||
hasValue(thumbnail._links) &&
|
||||
thumbnail._links.content.href
|
||||
) {
|
||||
this.src = thumbnail._links.content.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/', dso.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="dso.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="dso.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -19,6 +19,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { CollectionSearchResultGridElementComponent } from './collection-search-result-grid-element.component';
|
||||
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
|
||||
let collectionSearchResultGridElementComponent: CollectionSearchResultGridElementComponent;
|
||||
let fixture: ComponentFixture<CollectionSearchResultGridElementComponent>;
|
||||
@@ -52,6 +53,9 @@ mockCollectionWithoutAbstract.indexableObject = Object.assign(new Collection(),
|
||||
]
|
||||
}
|
||||
});
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: mockCollectionWithAbstract
|
||||
});
|
||||
|
||||
describe('CollectionSearchResultGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
@@ -72,6 +76,7 @@ describe('CollectionSearchResultGridElementComponent', () => {
|
||||
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||
{ provide: BitstreamFormatDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
|
@@ -1,10 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { hasNoValue, hasValue } from '../../../empty.util';
|
||||
import { followLink } from '../../../utils/follow-link-config.model';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-search-result-grid-element',
|
||||
@@ -15,4 +19,28 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista
|
||||
* Component representing a grid element for a collection search result
|
||||
*/
|
||||
@listableObjectComponent(CollectionSearchResult, ViewMode.GridElement)
|
||||
export class CollectionSearchResultGridElementComponent extends SearchResultGridElementComponent<CollectionSearchResult, Collection> {}
|
||||
export class CollectionSearchResultGridElementComponent extends SearchResultGridElementComponent< CollectionSearchResult, Collection > {
|
||||
private _dso: Collection;
|
||||
|
||||
constructor(
|
||||
private linkService: LinkService,
|
||||
protected truncatableService: TruncatableService,
|
||||
protected bitstreamDataService: BitstreamDataService
|
||||
) {
|
||||
super(truncatableService, bitstreamDataService);
|
||||
}
|
||||
|
||||
@Input() set dso(dso: Collection) {
|
||||
this._dso = dso;
|
||||
if (hasValue(this._dso) && hasNoValue(this._dso.logo)) {
|
||||
this.linkService.resolveLink<Collection>(
|
||||
this._dso,
|
||||
followLink('logo')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get dso(): Collection {
|
||||
return this._dso;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', dso.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="dso.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="dso.logo">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -19,6 +19,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { CommunitySearchResultGridElementComponent } from './community-search-result-grid-element.component';
|
||||
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
|
||||
let communitySearchResultGridElementComponent: CommunitySearchResultGridElementComponent;
|
||||
let fixture: ComponentFixture<CommunitySearchResultGridElementComponent>;
|
||||
@@ -52,6 +53,9 @@ mockCommunityWithoutAbstract.indexableObject = Object.assign(new Community(), {
|
||||
]
|
||||
}
|
||||
});
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: mockCommunityWithAbstract
|
||||
});
|
||||
|
||||
describe('CommunitySearchResultGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
@@ -72,6 +76,7 @@ describe('CommunitySearchResultGridElementComponent', () => {
|
||||
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||
{ provide: BitstreamFormatDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
|
@@ -1,18 +1,46 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { hasNoValue, hasValue } from '../../../empty.util';
|
||||
import { followLink } from '../../../utils/follow-link-config.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-search-result-grid-element',
|
||||
styleUrls: ['../search-result-grid-element.component.scss', 'community-search-result-grid-element.component.scss'],
|
||||
templateUrl: 'community-search-result-grid-element.component.html'
|
||||
styleUrls: [
|
||||
'../search-result-grid-element.component.scss',
|
||||
'community-search-result-grid-element.component.scss',
|
||||
],
|
||||
templateUrl: 'community-search-result-grid-element.component.html',
|
||||
})
|
||||
/**
|
||||
* Component representing a grid element for a community search result
|
||||
*/
|
||||
@listableObjectComponent(CommunitySearchResult, ViewMode.GridElement)
|
||||
export class CommunitySearchResultGridElementComponent extends SearchResultGridElementComponent<CommunitySearchResult,Community> {
|
||||
private _dso: Community;
|
||||
|
||||
constructor(
|
||||
private linkService: LinkService,
|
||||
protected truncatableService: TruncatableService,
|
||||
protected bitstreamDataService: BitstreamDataService
|
||||
) {
|
||||
super(truncatableService, bitstreamDataService);
|
||||
}
|
||||
|
||||
@Input() set dso(dso: Community) {
|
||||
this._dso = dso;
|
||||
if (hasValue(this._dso) && hasNoValue(this._dso.logo)) {
|
||||
this.linkService.resolveLink<Community>(this._dso, followLink('logo'));
|
||||
}
|
||||
}
|
||||
|
||||
get dso(): Community {
|
||||
return this._dso;
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,10 @@ import { BitstreamDataService } from '../../../core/data/bitstream-data.service'
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-result-grid-element',
|
||||
|
@@ -69,3 +69,24 @@ export function createPendingRemoteDataObject<T>(object?: T): RemoteData<T> {
|
||||
export function createPendingRemoteDataObject$<T>(object?: T): Observable<RemoteData<T>> {
|
||||
return observableOf(createPendingRemoteDataObject(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a remote data object with no content
|
||||
*/
|
||||
export function createNoContentRemoteDataObject<T>(): RemoteData<T> {
|
||||
return new RemoteData(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
204
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a remote data object that has succeeded with no content, wrapped in an observable
|
||||
*/
|
||||
export function createNoContentRemoteDataObject$<T>(): Observable<RemoteData<T>> {
|
||||
return observableOf(createNoContentRemoteDataObject());
|
||||
}
|
||||
|
@@ -11,9 +11,10 @@ import { ItemPageConfig } from './item-page-config.interface';
|
||||
import { CollectionPageConfig } from './collection-page-config.interface';
|
||||
import { Theme } from './theme.inferface';
|
||||
import {AuthConfig} from './auth-config.interfaces';
|
||||
import { UIServerConfig } from './ui-server-config.interface';
|
||||
|
||||
export interface GlobalConfig extends Config {
|
||||
ui: ServerConfig;
|
||||
ui: UIServerConfig;
|
||||
rest: ServerConfig;
|
||||
production: boolean;
|
||||
cache: CacheConfig;
|
||||
|
14
src/config/ui-server-config.interface.ts
Normal file
14
src/config/ui-server-config.interface.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ServerConfig } from './server-config.interface';
|
||||
|
||||
/**
|
||||
* Server configuration related to the UI.
|
||||
*/
|
||||
export class UIServerConfig extends ServerConfig {
|
||||
|
||||
// rateLimiter is used to limit the amount of requests a user is allowed make in an amount of time, in order to prevent overloading the server
|
||||
rateLimiter?: {
|
||||
windowMs: number;
|
||||
max: number;
|
||||
};
|
||||
|
||||
}
|
@@ -13,6 +13,11 @@ export const environment: GlobalConfig = {
|
||||
port: 4000,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
nameSpace: '/',
|
||||
// The rateLimiter settings limit each IP to a "max" of 500 requests per "windowMs" (1 minute).
|
||||
rateLimiter: {
|
||||
windowMs: 1 * 60 * 1000, // 1 minute
|
||||
max: 500 // limit each IP to 500 requests per windowMs
|
||||
}
|
||||
},
|
||||
// The REST API server settings.
|
||||
// NOTE: these must be "synced" with the 'dspace.server.url' setting in your backend's local.cfg.
|
||||
|
@@ -19,6 +19,7 @@ export const environment: Partial<GlobalConfig> = {
|
||||
port: 80,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
nameSpace: '/angular-dspace',
|
||||
rateLimiter: undefined
|
||||
},
|
||||
// Caching settings
|
||||
cache: {
|
||||
|
@@ -4084,6 +4084,11 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
|
||||
dependencies:
|
||||
homedir-polyfill "^1.0.1"
|
||||
|
||||
express-rate-limit@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.1.3.tgz#656bacce3f093034976346958a0f0199902c9174"
|
||||
integrity sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==
|
||||
|
||||
express@4.16.2:
|
||||
version "4.16.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
|
||||
|
Reference in New Issue
Block a user