Skip to content

Commit 5cba103

Browse files
vins01-4scienceatarix83
authored andcommitted
Merged in DSC-867 (pull request DSpace#448)
DSC-867 Approved-by: Davide Negretti Approved-by: Giuseppe Digilio
2 parents 74e3cd8 + a057970 commit 5cba103

50 files changed

Lines changed: 718 additions & 373 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/app/app-routing-paths.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,11 @@ export function getEditItemPageRoute() {
142142
export function getSubscriptionsModuleRoute() {
143143
return `/${SUBSCRIPTIONS_MODULE_PATH}`;
144144
}
145+
145146
export const SUBSCRIPTIONS_MODULE_PATH = 'subscriptions';
147+
148+
export const STATISTICS_PAGE_PATH = 'statistics';
149+
150+
export function getStatisticsModuleRoute() {
151+
return `/${STATISTICS_PAGE_PATH}`;
152+
}

src/app/app-routing.module.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NgModule } from '@angular/core';
2-
import { RouterModule, NoPreloading } from '@angular/router';
2+
import { NoPreloading, RouterModule } from '@angular/router';
33
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
44

55
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
@@ -10,9 +10,9 @@ import {
1010
ACCESS_CONTROL_MODULE_PATH,
1111
ADMIN_MODULE_PATH,
1212
BITSTREAM_MODULE_PATH,
13-
ERROR_PAGE,
1413
BULK_IMPORT_PATH,
1514
EDIT_ITEM_PATH,
15+
ERROR_PAGE,
1616
FORBIDDEN_PATH,
1717
FORGOT_PASSWORD_PATH,
1818
HEALTH_PAGE_PATH,
@@ -22,6 +22,7 @@ import {
2222
PROFILE_MODULE_PATH,
2323
REGISTER_PATH,
2424
REQUEST_COPY_MODULE_PATH,
25+
STATISTICS_PAGE_PATH,
2526
WORKFLOW_ITEM_MODULE_PATH,
2627
} from './app-routing-paths';
2728
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths';
@@ -43,7 +44,7 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard';
4344
import { MenuResolver } from './menu.resolver';
4445
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
4546
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
46-
import { StatisticsAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard';
47+
import { RedirectService } from './redirect/redirect.service';
4748

4849
@NgModule({
4950
imports: [
@@ -247,7 +248,7 @@ import { StatisticsAdministratorGuard } from './core/data/feature-authorization/
247248
component: ThemedForbiddenComponent
248249
},
249250
{
250-
path: 'statistics',
251+
path: STATISTICS_PAGE_PATH,
251252
loadChildren: () => import('./statistics-page/statistics-page-routing.module')
252253
.then((m) => m.StatisticsPageRoutingModule),
253254
},
@@ -282,7 +283,7 @@ import { StatisticsAdministratorGuard } from './core/data/feature-authorization/
282283
loadChildren: () => import('./invitation/invitation.module')
283284
.then((m) => m.InvitationModule)
284285
},
285-
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
286+
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent, canActivate: [RedirectService] },
286287
]
287288
}
288289
], {

src/app/core/data/base/search-data.spec.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import { constructSearchEndpointDefault, SearchData, SearchDataImpl } from './search-data';
99
import { FindListOptions } from '../find-list-options.model';
1010
import { followLink } from '../../../shared/utils/follow-link-config.model';
11-
import { of as observableOf } from 'rxjs';
11+
import { of, of as observableOf } from 'rxjs';
1212
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
1313
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
14+
import { TestScheduler } from 'rxjs/testing';
15+
import { RemoteData } from '../remote-data';
16+
import { RequestEntryState } from '../request-entry-state.model';
1417

1518
/**
1619
* Tests whether calls to `SearchData` methods are correctly patched through in a concrete data service that implements it
@@ -19,6 +22,12 @@ export function testSearchDataImplementation(serviceFactory: () => SearchData<an
1922
let service;
2023

2124
describe('SearchData implementation', () => {
25+
let testScheduler: TestScheduler;
26+
const searchByResp = Object.assign(
27+
new RemoteData(0, 0, 0, undefined, undefined, {}, 200),
28+
{ state: RequestEntryState.Success, payload: 'TEST searchBy' }
29+
);
30+
const searchByResp$ = of(searchByResp);
2231
const OPTIONS = Object.assign(new FindListOptions(), { elementsPerPage: 10, currentPage: 3 });
2332
const FOLLOWLINKS = [
2433
followLink('test'),
@@ -27,16 +36,27 @@ export function testSearchDataImplementation(serviceFactory: () => SearchData<an
2736

2837
beforeAll(() => {
2938
service = serviceFactory();
30-
(service as any).searchData = jasmine.createSpyObj('searchData', {
31-
searchBy: 'TEST searchBy',
39+
(service as any).searchData = jasmine.createSpyObj('searchData', ['searchBy']);
40+
service.searchData.searchBy.and.returnValue(searchByResp$);
41+
});
42+
43+
beforeEach(() => {
44+
testScheduler = new TestScheduler((actual, expected) => {
45+
return expect(actual).toEqual(expected);
3246
});
3347
});
3448

3549
it('should handle calls to searchBy', () => {
50+
const expectedMarbles = '(a|)';
51+
const expectedValues = {
52+
a: searchByResp
53+
};
3654
const out: any = service.searchBy('searchMethod', OPTIONS, false, true, ...FOLLOWLINKS);
3755

3856
expect((service as any).searchData.searchBy).toHaveBeenCalledWith('searchMethod', OPTIONS, false, true, ...FOLLOWLINKS);
39-
expect(out).toBe('TEST searchBy');
57+
testScheduler.run(({ expectObservable }) => {
58+
expectObservable(out).toBe(expectedMarbles, expectedValues);
59+
});
4060
});
4161
});
4262
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ExportAsConfig, ExportAsService } from 'ngx-export-as';
2+
import { toJpeg, toPng } from 'html-to-image';
3+
import { Options } from 'html-to-image/es/types';
4+
import { saveAs } from 'file-saver';
5+
import { BehaviorSubject } from 'rxjs';
6+
7+
import { ExportImageType, ExportService } from './export.service';
8+
9+
/**
10+
* IMPORTANT
11+
* Due to a problem occurring on SSR with the ExportAsService dependency, which use window object, this service can't be injected.
12+
* So we need to instantiate the class directly based on current the platform whn is needed
13+
* TODO To be refactored when https://github.com/wnabil/ngx-export-as/pull/112 is fixed
14+
*/
15+
export class BrowserExportService implements ExportService {
16+
17+
/**
18+
* Configuration for CSV export process
19+
*/
20+
exportAsConfig: ExportAsConfig;
21+
22+
/**
23+
* Creates excel from the table element reference.
24+
*
25+
* @param type of export.
26+
* @param fileName is the file name to save as.
27+
* @param elementIdOrContent is the content that is being exported.
28+
* @param download option if it's going to be downloaded.
29+
*/
30+
exportAsFile(type: any, elementIdOrContent: string, fileName: string, download: boolean = true) {
31+
32+
this.exportAsConfig = {
33+
type:type,
34+
elementIdOrContent: elementIdOrContent,
35+
fileName:fileName,
36+
download:download
37+
};
38+
39+
const exportAsService: ExportAsService = new ExportAsService();
40+
return exportAsService.save(this.exportAsConfig, fileName);
41+
}
42+
43+
/**
44+
* Creates an image from the given element reference.
45+
*
46+
* @param domNode The HTMLElement.
47+
* @param type The type of image to export.
48+
* @param fileName The file name to save as.
49+
* @param isLoading A boolean representing the exporting process status.
50+
*/
51+
exportAsImage(domNode: HTMLElement, type: ExportImageType, fileName: string, isLoading: BehaviorSubject<boolean>): void {
52+
53+
const options: Options = { backgroundColor: '#ffffff' };
54+
55+
if (type === ExportImageType.png) {
56+
toPng(domNode, options)
57+
.then((dataUrl) => {
58+
saveAs(dataUrl, fileName + '.' + type);
59+
isLoading.next(false);
60+
});
61+
} else {
62+
toJpeg(domNode, options)
63+
.then((dataUrl) => {
64+
saveAs(dataUrl, fileName + '.' + type);
65+
isLoading.next(false);
66+
});
67+
}
68+
69+
}
70+
71+
}
Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,21 @@
1-
import { Injectable } from '@angular/core';
2-
3-
import { ExportAsConfig, ExportAsService } from 'ngx-export-as';
4-
import { toJpeg, toPng } from 'html-to-image';
5-
import { Options } from 'html-to-image/es/types';
6-
import { saveAs } from 'file-saver';
71
import { BehaviorSubject } from 'rxjs';
82

93
export enum ExportImageType {
104
png = 'png',
115
jpeg = 'jpeg',
126
}
137

14-
@Injectable()
15-
export class ExportService {
16-
17-
/**
18-
* Configuration for CSV export process
19-
*/
20-
exportAsConfig: ExportAsConfig;
21-
22-
constructor(private exportAsService: ExportAsService) { }
8+
export interface ExportService {
239

2410
/**
2511
* Creates excel from the table element reference.
2612
*
2713
* @param type of export.
2814
* @param fileName is the file name to save as.
2915
* @param elementIdOrContent is the content that is being exported.
30-
* @param download option if its going to be downloaded.
16+
* @param download option if it's going to be downloaded.
3117
*/
32-
exportAsFile(type: any, elementIdOrContent: string, fileName: string, download: boolean = true) {
33-
34-
this.exportAsConfig = {
35-
type:type,
36-
elementIdOrContent: elementIdOrContent,
37-
fileName:fileName,
38-
download:download
39-
};
40-
41-
return this.exportAsService.save(this.exportAsConfig, fileName);
42-
}
18+
exportAsFile(type: any, elementIdOrContent: string, fileName: string, download?: boolean);
4319

4420
/**
4521
* Creates an image from the given element reference.
@@ -49,24 +25,5 @@ export class ExportService {
4925
* @param fileName The file name to save as.
5026
* @param isLoading A boolean representing the exporting process status.
5127
*/
52-
exportAsImage(domNode: HTMLElement, type: ExportImageType, fileName: string, isLoading: BehaviorSubject<boolean>): void {
53-
54-
const options: Options = { backgroundColor: '#ffffff' };
55-
56-
if (type === ExportImageType.png) {
57-
toPng(domNode, options)
58-
.then((dataUrl) => {
59-
saveAs(dataUrl, fileName + '.' + type);
60-
isLoading.next(false);
61-
});
62-
} else {
63-
toJpeg(domNode, options)
64-
.then((dataUrl) => {
65-
saveAs(dataUrl, fileName + '.' + type);
66-
isLoading.next(false);
67-
});
68-
}
69-
70-
}
71-
28+
exportAsImage(domNode: HTMLElement, type: ExportImageType, fileName: string, isLoading: BehaviorSubject<boolean>);
7229
}

src/app/core/export-service/server-export.service.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
import { Injectable } from '@angular/core';
2-
31
import { BehaviorSubject } from 'rxjs';
42

5-
import { ExportImageType } from './export.service';
3+
import { ExportImageType, ExportService } from './export.service';
64

7-
@Injectable()
8-
export class ServerExportService {
5+
export class ServerExportService implements ExportService {
96
/**
107
* Creates excel from the table element reference.
118
*
129
* @param type of export.
1310
* @param fileName is the file name to save as.
1411
* @param elementIdOrContent is the content that is being exported.
15-
* @param download option if its going to be downloaded.
12+
* @param download option if it'ss going to be downloaded.
1613
*/
1714
exportAsFile(type: any, elementIdOrContent: string, fileName: string, download: boolean = true) {
1815
return;

src/app/core/statistics/usage-report-data.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export class UsageReportDataService extends IdentifiableDataService<UsageReport>
7979
}
8080

8181
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<UsageReport>[]): Observable<RemoteData<PaginatedList<UsageReport>>> {
82-
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
82+
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
83+
getFirstSucceededRemoteData()
84+
);
8385
}
8486
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { RedirectWithHrefDirective } from './redirect/redirect-href.directive';
2+
import { RedirectDirective } from './redirect/redirect.directive';
3+
import { NgModule } from '@angular/core';
4+
import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core';
5+
import { MissingTranslationHelper } from '../shared/translate/missing-translation.helper';
6+
7+
const DIRECTIVES = [
8+
RedirectDirective,
9+
RedirectWithHrefDirective
10+
];
11+
12+
@NgModule({
13+
declarations: [
14+
...DIRECTIVES,
15+
],
16+
imports: [
17+
TranslateModule.forChild({
18+
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper },
19+
useDefaultLang: true
20+
}),
21+
],
22+
exports: [
23+
...DIRECTIVES,
24+
]
25+
})
26+
export class DirectivesModule {
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Directive, HostBinding } from '@angular/core';
2+
import { RedirectDirective } from './redirect.directive';
3+
import { RedirectService } from '../../redirect/redirect.service';
4+
5+
@Directive({
6+
// eslint-disable-next-line @angular-eslint/directive-selector
7+
selector: 'a[dsRedirect],area[dsRedirect]'
8+
})
9+
export class RedirectWithHrefDirective extends RedirectDirective {
10+
11+
constructor(redirect: RedirectService) {
12+
super(redirect);
13+
}
14+
15+
// Binds the requested url to the href property
16+
@HostBinding('href') get href() {
17+
return this.url;
18+
}
19+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Directive, HostListener, Input } from '@angular/core';
2+
import { RedirectService } from '../../redirect/redirect.service';
3+
4+
@Directive({
5+
// eslint-disable-next-line @angular-eslint/directive-selector
6+
selector: ':not(a):not(area)[dsRedirect]'
7+
})
8+
export class RedirectDirective {
9+
constructor(readonly redirect: RedirectService) {
10+
}
11+
12+
// eslint-disable-next-line @angular-eslint/no-input-rename
13+
@Input('dsRedirect') url: string;
14+
15+
@HostListener('click') onClick() {
16+
// Fallbakcs to default if no url is specified
17+
if (!this.url) {
18+
return true;
19+
}
20+
// Navigates on the requested link redirecting when necessary
21+
this.redirect.navigate(this.url);
22+
// Prevents default
23+
return false;
24+
}
25+
}

0 commit comments

Comments
 (0)