Skip to content

Commit a9dc8ec

Browse files
Merge branch 'task/main/DURACOM-413' into task/main/DURACOM-426
2 parents 8bdfd01 + d1c1ecf commit a9dc8ec

59 files changed

Lines changed: 12104 additions & 2069 deletions

Some content is hidden

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

scripts/sync-i18n-files.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,11 @@ function parseCliInput() {
3737
.option('-o, --output-file <output>', 'where output of script ends up; mutually exclusive with -i')
3838
.usage('([-d <output-dir>] [-s <source-file>]) || (-t <target-file> (-i | -o <output>) [-s <source-file>])')
3939
.parse(process.argv);
40-
41-
const sourceFile = program.opts().sourceFile;
42-
43-
if (!program.targetFile) {
40+
if (!program.targetFile) {
4441
fs.readdirSync(projectRoot(LANGUAGE_FILES_LOCATION)).forEach(file => {
45-
if (!sourceFile.toString().endsWith(file)) {
42+
if (program.opts().sourceFile && !program.opts().sourceFile.toString().endsWith(file)) {
4643
const targetFileLocation = projectRoot(LANGUAGE_FILES_LOCATION + "/" + file);
47-
console.log('Syncing file at: ' + targetFileLocation + ' with source file at: ' + sourceFile);
44+
console.log('Syncing file at: ' + targetFileLocation + ' with source file at: ' + program.opts().sourceFile);
4845
if (program.outputDir) {
4946
if (!fs.existsSync(program.outputDir)) {
5047
fs.mkdirSync(program.outputDir);
@@ -69,7 +66,7 @@ function parseCliInput() {
6966
console.log(program.outputHelp());
7067
process.exit(1);
7168
}
72-
if (!checkIfFileExists(sourceFile)) {
69+
if (!checkIfFileExists(program.opts().sourceFile)) {
7370
console.error('Path of source file is not valid.');
7471
console.log(program.outputHelp());
7572
process.exit(1);

src/app/core/data/item-data.service.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { HttpClient } from '@angular/common/http';
2+
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
23
import { Store } from '@ngrx/store';
34
import {
45
cold,
@@ -13,6 +14,7 @@ import { RestResponse } from '../cache/response.models';
1314
import { CoreState } from '../core-state.model';
1415
import { NotificationsService } from '../notification-system/notifications.service';
1516
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
17+
import { Item } from '../shared/item.model';
1618
import { HALEndpointServiceStub } from '../testing/hal-endpoint-service.stub';
1719
import { getMockRemoteDataBuildService } from '../testing/remote-data-build.service.mock';
1820
import { getMockRequestService } from '../testing/request.service.mock';
@@ -209,4 +211,93 @@ describe('ItemDataService', () => {
209211
});
210212
});
211213

214+
describe('findByCustomUrl', () => {
215+
let itemDataService: ItemDataService;
216+
let searchData: any;
217+
let findByHrefSpy: jasmine.Spy;
218+
let getSearchByHrefSpy: jasmine.Spy;
219+
const id = 'custom-id';
220+
const fakeHrefObs = of('https://rest.api/core/items/search/findByCustomURL?q=custom-id');
221+
const linksToFollow = [];
222+
const projections = ['full', 'detailed'];
223+
224+
beforeEach(() => {
225+
searchData = jasmine.createSpyObj('searchData', ['getSearchByHref']);
226+
getSearchByHrefSpy = searchData.getSearchByHref.and.returnValue(fakeHrefObs);
227+
itemDataService = new ItemDataService(
228+
requestService,
229+
rdbService,
230+
objectCache,
231+
halEndpointService,
232+
notificationsService,
233+
comparator,
234+
browseService,
235+
bundleService,
236+
);
237+
238+
(itemDataService as any).searchData = searchData;
239+
findByHrefSpy = spyOn(itemDataService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(new Item()));
240+
});
241+
242+
it('should call searchData.getSearchByHref with correct parameters', () => {
243+
itemDataService.findByCustomUrl(id, true, true, linksToFollow, projections).subscribe();
244+
245+
expect(getSearchByHrefSpy).toHaveBeenCalledWith(
246+
'findByCustomURL',
247+
jasmine.objectContaining({
248+
searchParams: jasmine.arrayContaining([
249+
jasmine.objectContaining({ fieldName: 'q', fieldValue: id }),
250+
jasmine.objectContaining({ fieldName: 'projection', fieldValue: 'full' }),
251+
jasmine.objectContaining({ fieldName: 'projection', fieldValue: 'detailed' }),
252+
]),
253+
}),
254+
...linksToFollow,
255+
);
256+
});
257+
258+
it('should call findByHref with the href observable returned from getSearchByHref', () => {
259+
itemDataService.findByCustomUrl(id, true, false, linksToFollow, projections).subscribe();
260+
261+
expect(findByHrefSpy).toHaveBeenCalledWith(fakeHrefObs, true, false, ...linksToFollow);
262+
});
263+
});
264+
265+
describe('findById', () => {
266+
let itemDataService: ItemDataService;
267+
268+
beforeEach(() => {
269+
itemDataService = new ItemDataService(
270+
requestService,
271+
rdbService,
272+
objectCache,
273+
halEndpointService,
274+
notificationsService,
275+
comparator,
276+
browseService,
277+
bundleService,
278+
);
279+
spyOn(itemDataService, 'findByCustomUrl').and.returnValue(createSuccessfulRemoteDataObject$(new Item()));
280+
spyOn(itemDataService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(new Item()));
281+
spyOn(itemDataService as any, 'getIDHrefObs').and.returnValue(of('uuid-href'));
282+
});
283+
284+
it('should call findByHref when given a valid UUID', () => {
285+
const validUuid = '4af28e99-6a9c-4036-a199-e1b587046d39';
286+
itemDataService.findById(validUuid).subscribe();
287+
288+
expect((itemDataService as any).getIDHrefObs).toHaveBeenCalledWith(encodeURIComponent(validUuid));
289+
expect(itemDataService.findByHref).toHaveBeenCalled();
290+
expect(itemDataService.findByCustomUrl).not.toHaveBeenCalled();
291+
});
292+
293+
it('should call findByCustomUrl when given a non-UUID id', () => {
294+
const nonUuid = 'custom-url';
295+
itemDataService.findById(nonUuid).subscribe();
296+
297+
expect(itemDataService.findByCustomUrl).toHaveBeenCalledWith(nonUuid, true, true, []);
298+
expect(itemDataService.findByHref).not.toHaveBeenCalled();
299+
});
300+
});
301+
302+
212303
});

src/app/core/data/item-data.service.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
switchMap,
2525
take,
2626
} from 'rxjs/operators';
27+
import { validate as uuidValidate } from 'uuid';
2728

2829
import { BrowseService } from '../browse/browse.service';
2930
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -34,6 +35,7 @@ import { NotificationsService } from '../notification-system/notifications.servi
3435
import { Bundle } from '../shared/bundle.model';
3536
import { Collection } from '../shared/collection.model';
3637
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
38+
import { FollowLinkConfig } from '../shared/follow-link-config.model';
3739
import { GenericConstructor } from '../shared/generic-constructor';
3840
import { HALEndpointService } from '../shared/hal-endpoint.service';
3941
import { Item } from '../shared/item.model';
@@ -58,6 +60,7 @@ import {
5860
PatchData,
5961
PatchDataImpl,
6062
} from './base/patch-data';
63+
import { SearchDataImpl } from './base/search-data';
6164
import { BundleDataService } from './bundle-data.service';
6265
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
6366
import { FindListOptions } from './find-list-options.model';
@@ -83,6 +86,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
8386
private createData: CreateData<Item>;
8487
private patchData: PatchData<Item>;
8588
private deleteData: DeleteData<Item>;
89+
private searchData: SearchDataImpl<Item>;
8690

8791
protected constructor(
8892
protected linkPath,
@@ -101,6 +105,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
101105
this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive);
102106
this.patchData = new PatchDataImpl<Item>(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, this.constructIdEndpoint);
103107
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
108+
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
104109
}
105110

106111
/**
@@ -425,6 +430,57 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
425430
return this.createData.create(object, ...params);
426431
}
427432

433+
/**
434+
* Returns an observable of {@link RemoteData} of an object, based on its CustomURL or ID, with a list of
435+
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
436+
* @param id CustomUrl or UUID of object we want to retrieve
437+
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
438+
* no valid cached version. Defaults to true
439+
* @param reRequestOnStale Whether or not the request should automatically be re-
440+
* requested after the response becomes stale
441+
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
442+
* {@link HALLink}s should be automatically resolved
443+
* @param projections List of {@link projections} used to pass as parameters
444+
*/
445+
public findByCustomUrl(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, linksToFollow: FollowLinkConfig<Item>[], projections: string[] = []): Observable<RemoteData<Item>> {
446+
const searchHref = 'findByCustomURL';
447+
448+
const options = Object.assign({}, {
449+
searchParams: [
450+
new RequestParam('q', id),
451+
],
452+
});
453+
454+
projections.forEach((projection) => {
455+
options.searchParams.push(new RequestParam('projection', projection));
456+
});
457+
458+
const hrefObs = this.searchData.getSearchByHref(searchHref, options, ...linksToFollow);
459+
460+
return this.findByHref(hrefObs, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
461+
}
462+
463+
/**
464+
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
465+
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
466+
* @param id ID of object we want to retrieve
467+
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
468+
* no valid cached version. Defaults to true
469+
* @param reRequestOnStale Whether or not the request should automatically be re-
470+
* requested after the response becomes stale
471+
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
472+
* {@link HALLink}s should be automatically resolved
473+
*/
474+
public findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
475+
476+
if (uuidValidate(id)) {
477+
const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow);
478+
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
479+
} else {
480+
return this.findByCustomUrl(id, useCachedVersionIfAvailable, reRequestOnStale, linksToFollow);
481+
}
482+
}
483+
428484
}
429485

430486
/**

src/app/core/provide-core.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
makeEnvironmentProviders,
55
} from '@angular/core';
66
import { APP_CONFIG } from '@dspace/config/app-config.interface';
7+
import { SubmissionCustomUrl } from '@dspace/core/submission/models/submission-custom-url.model';
78

89
import { AuthStatus } from './auth/models/auth-status.model';
910
import { ShortLivedToken } from './auth/models/short-lived-token.model';
@@ -228,4 +229,5 @@ export const models =
228229
StatisticsEndpoint,
229230
CorrectionType,
230231
SupervisionOrder,
232+
SubmissionCustomUrl,
231233
];

src/app/core/router/utils/dso-route.utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ export function getCommunityPageRoute(communityId: string) {
3131
*/
3232
export function getItemPageRoute(item: Item) {
3333
const type = item.firstMetadataValue('dspace.entity.type');
34-
return getEntityPageRoute(type, item.uuid);
34+
let url = item.uuid;
35+
36+
if (isNotEmpty(item.metadata) && item.hasMetadata('dspace.customurl')) {
37+
url = item.firstMetadataValue('dspace.customurl');
38+
}
39+
40+
return getEntityPageRoute(type, url);
3541
}
3642

43+
3744
export function getEntityPageRoute(entityType: string, itemId: string) {
3845
if (isNotEmpty(entityType)) {
3946
return new URLCombiner(`/${ENTITY_MODULE_PATH}`, encodeURIComponent(entityType.toLowerCase()), itemId).toString();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
autoserialize,
3+
inheritSerialization,
4+
} from 'cerialize';
5+
6+
import { typedObject } from '../../cache/builders/build-decorators';
7+
import { HALResource } from '../../shared/hal-resource.model';
8+
import { ResourceType } from '../../shared/resource-type';
9+
import { excludeFromEquals } from '../../utilities/equals.decorators';
10+
import { SUBMISSION_CUSTOM_URL } from './submission-custom-url.resource-type';
11+
12+
@typedObject
13+
@inheritSerialization(HALResource)
14+
export class SubmissionCustomUrl extends HALResource {
15+
16+
static type = SUBMISSION_CUSTOM_URL;
17+
18+
/**
19+
* The object type
20+
*/
21+
@excludeFromEquals
22+
@autoserialize
23+
type: ResourceType;
24+
25+
@autoserialize
26+
url: string;
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ResourceType } from '../../shared/resource-type';
2+
3+
/**
4+
* The resource type for License
5+
*
6+
* Needs to be in a separate file to prevent circular
7+
* dependencies in webpack.
8+
*/
9+
export const SUBMISSION_CUSTOM_URL = new ResourceType('submissioncustomcurl');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* An interface to represent the submission's custom url section data.
3+
*/
4+
export interface WorkspaceitemSectionCustomUrlObject {
5+
'redirected-urls': string[];
6+
'url': string;
7+
}

src/app/core/submission/sections-type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export enum SectionsType {
44
Upload = 'upload',
55
License = 'license',
66
CcLicense = 'cclicense',
7+
CustomUrl = 'custom-url',
78
AccessesCondition = 'accessCondition',
89
SherpaPolicies = 'sherpaPolicy',
910
Identifiers = 'identifiers',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export enum SubmissionScopeType {
22
WorkspaceItem = 'WORKSPACE',
33
WorkflowItem = 'WORKFLOW',
4+
EditItem = 'EDIT'
45
}

0 commit comments

Comments
 (0)