From 2ffb72320221b923d0064fec6fc47a04d34606ac Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 10 Mar 2022 13:04:22 +0530 Subject: [PATCH 01/33] [CST-5329] Add validate only check in the Import > Metadata page --- .../metadata-import-page.component.html | 4 +++ .../metadata-import-page.component.spec.ts | 25 ++++++++++++++++++- .../metadata-import-page.component.ts | 8 ++++++ src/assets/i18n/en.json5 | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html index 42a04b0de6..c70bc45947 100644 --- a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html +++ b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html @@ -1,6 +1,10 @@

{{'admin.metadata-import.page.help' | translate}}

+

+ + {{'admin.metadata-import.page.validateOnly' | translate}} +

{ comp.setFile(fileMock); }); - describe('if proceed button is pressed', () => { + describe('if proceed button is pressed without validate only', () => { beforeEach(fakeAsync(() => { + comp.validateOnly = false; const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; proceed.click(); fixture.detectChanges(); @@ -107,6 +108,28 @@ describe('MetadataImportPageComponent', () => { }); }); + describe('if proceed button is pressed with validate only', () => { + beforeEach(fakeAsync(() => { + comp.validateOnly = true; + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('metadata-import script is invoked with -f fileName and the mockFile and -v validate-only', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '-f', value: 'filename.txt' }), + Object.assign(new ProcessParameter(), { name: '-v', value: true }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); + }); + it('success notification is shown', () => { + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45'); + }); + }); + describe('if proceed is pressed; but script invoke fails', () => { beforeEach(fakeAsync(() => { jasmine.getEnv().allowRespy(true); diff --git a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts index 3bdcca3084..deb16c0d73 100644 --- a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts +++ b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts @@ -30,6 +30,11 @@ export class MetadataImportPageComponent { */ fileObject: File; + /** + * The validate only flag + */ + validateOnly = true; + public constructor(private location: Location, protected translate: TranslateService, protected notificationsService: NotificationsService, @@ -62,6 +67,9 @@ export class MetadataImportPageComponent { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '-f', value: this.fileObject.name }), ]; + if (this.validateOnly) { + parameterValues.push(Object.assign(new ProcessParameter(), { name: '-v', value: true })); + } this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]).pipe( getFirstCompletedRemoteData(), diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f33a195cfe..426fcb12d2 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -538,6 +538,8 @@ "admin.metadata-import.page.error.addFile": "Select file first!", + "admin.metadata-import.page.validateOnly": "Validate Only", + From 027b281d7a61031370a62f1388d23b8369410808 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 10 Mar 2022 14:45:53 +0530 Subject: [PATCH 02/33] [CST-5329] Add validate only check in the Import > Metadata page --- .../metadata-import-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html index c70bc45947..fb96c4becd 100644 --- a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html +++ b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.html @@ -3,7 +3,7 @@

{{'admin.metadata-import.page.help' | translate}}

- {{'admin.metadata-import.page.validateOnly' | translate}} + {{'admin.metadata-import.page.validateOnly' | translate}}

Date: Fri, 8 Apr 2022 16:28:52 +0200 Subject: [PATCH 03/33] 90918: Upgrade ng-dynamic-forms and RxJs Copied over isNumeric from RxJs 6.x as it was removed from 7.x Build & tests are broken, fixed in subsequent commit --- package.json | 18 ++--- src/app/app.component.spec.ts | 2 +- src/app/app.component.ts | 2 +- src/app/core/pagination/pagination.service.ts | 2 +- src/app/root/root.component.spec.ts | 2 +- src/app/root/root.component.ts | 2 +- src/app/shared/numeric.util.spec.ts | 53 ++++++++++++++ src/app/shared/numeric.util.ts | 16 ++++ .../google-analytics.service.spec.ts | 2 +- .../statistics/google-analytics.service.ts | 2 +- src/modules/app/browser-app.module.ts | 2 +- src/modules/app/server-app.module.ts | 2 +- yarn.lock | 73 ++++++++++--------- 13 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 src/app/shared/numeric.util.spec.ts create mode 100644 src/app/shared/numeric.util.ts diff --git a/package.json b/package.json index 75e22b40f3..de878a35a5 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@ng-bootstrap/ng-bootstrap": "^11.0.0", - "@ng-dynamic-forms/core": "^14.0.1", - "@ng-dynamic-forms/ui-ng-bootstrap": "^14.0.1", + "@ng-dynamic-forms/core": "^15.0.0", + "@ng-dynamic-forms/ui-ng-bootstrap": "^15.0.0", "@ngrx/effects": "^13.0.2", "@ngrx/router-store": "^13.0.2", "@ngrx/store": "^13.0.2", @@ -77,7 +77,7 @@ "@ngx-translate/core": "^13.0.0", "@nicky-lenaers/ngx-scroll-to": "^9.0.0", "angular-idle-preload": "3.0.0", - "angulartics2": "^10.0.0", + "angulartics2": "^12.0.0", "bootstrap": "4.3.1", "caniuse-lite": "^1.0.30001165", "cerialize": "0.1.18", @@ -119,7 +119,7 @@ "prop-types": "^15.7.2", "react-copy-to-clipboard": "^5.0.1", "reflect-metadata": "^0.1.13", - "rxjs": "^6.6.3", + "rxjs": "^7.5.5", "sortablejs": "1.13.0", "tslib": "^2.0.0", "url-parse": "^1.5.6", @@ -161,7 +161,7 @@ "css-minimizer-webpack-plugin": "^3.4.1", "cssnano": "^5.0.6", "cypress": "9.5.1", - "cypress-axe": "^0.13.0", + "cypress-axe": "^0.14.0", "debug-loader": "^0.0.1", "deep-freeze": "0.0.1", "dotenv": "^8.2.0", @@ -173,7 +173,7 @@ "fork-ts-checker-webpack-plugin": "^6.0.3", "html-loader": "^1.3.2", "jasmine-core": "^3.8.0", - "jasmine-marbles": "0.6.0", + "jasmine-marbles": "0.9.2", "jasmine-spec-reporter": "~5.0.0", "karma": "^6.3.14", "karma-chrome-launcher": "~3.1.0", @@ -181,7 +181,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", - "ngx-mask": "^12.0.0", + "ngx-mask": "^13.1.7", "nodemon": "^2.0.15", "postcss": "^8.1", "postcss-apply": "0.12.0", @@ -195,7 +195,7 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", - "rxjs-spy": "^7.5.3", + "rxjs-spy": "^8.0.2", "sass": "~1.32.6", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.1.1", @@ -209,4 +209,4 @@ "webpack-cli": "^4.2.0", "webpack-dev-server": "^4.5.0" } -} +} \ No newline at end of file diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index a892e34a5a..9f215da46d 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -4,7 +4,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CommonModule, DOCUMENT } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; // Load the implementations that should be tested import { AppComponent } from './app.component'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 669411d9aa..87a211ec9d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -23,7 +23,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; diff --git a/src/app/core/pagination/pagination.service.ts b/src/app/core/pagination/pagination.service.ts index db80cc9476..a6f8052c4b 100644 --- a/src/app/core/pagination/pagination.service.ts +++ b/src/app/core/pagination/pagination.service.ts @@ -7,8 +7,8 @@ import { filter, map, take } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { difference } from '../../shared/object.util'; -import { isNumeric } from 'rxjs/internal-compatibility'; import { FindListOptions } from '../data/find-list-options.model'; +import { isNumeric } from '../../shared/numeric.util'; @Injectable({ providedIn: 'root', diff --git a/src/app/root/root.component.spec.ts b/src/app/root/root.component.spec.ts index 81b22592d6..64a15a3087 100644 --- a/src/app/root/root.component.spec.ts +++ b/src/app/root/root.component.spec.ts @@ -9,7 +9,7 @@ import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; import { NativeWindowRef, NativeWindowService } from '../core/services/window.service'; import { MetadataService } from '../core/metadata/metadata.service'; import { MetadataServiceMock } from '../shared/mocks/metadata-service.mock'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { AngularticsProviderMock } from '../shared/mocks/angulartics-provider.service.mock'; import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider'; import { AuthService } from '../core/auth/auth.service'; diff --git a/src/app/root/root.component.ts b/src/app/root/root.component.ts index 2633874c1a..aae991dca3 100644 --- a/src/app/root/root.component.ts +++ b/src/app/root/root.component.ts @@ -5,7 +5,7 @@ import { Router } from '@angular/router'; import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { MetadataService } from '../core/metadata/metadata.service'; import { HostWindowState } from '../shared/search/host-window.reducer'; diff --git a/src/app/shared/numeric.util.spec.ts b/src/app/shared/numeric.util.spec.ts new file mode 100644 index 0000000000..9602966299 --- /dev/null +++ b/src/app/shared/numeric.util.spec.ts @@ -0,0 +1,53 @@ +import { isNumeric } from './numeric.util'; + +describe('Numeric Utils', () => { + describe('isNumeric', () => { + it('should return true for Number values', () => { + expect(isNumeric(0)).toBeTrue(); + expect(isNumeric(123456)).toBeTrue(); + expect(isNumeric(-123456)).toBeTrue(); + expect(isNumeric(0.1234)).toBeTrue(); + expect(isNumeric(-0.1234)).toBeTrue(); + expect(isNumeric(1234e56)).toBeTrue(); + expect(isNumeric(-1234e-56)).toBeTrue(); + expect(isNumeric(0x123456)).toBeTrue(); + expect(isNumeric(-0x123456)).toBeTrue(); + }); + + it('should return true for numeric String values', () => { + expect(isNumeric('0')).toBeTrue(); + expect(isNumeric('123456')).toBeTrue(); + expect(isNumeric('-123456')).toBeTrue(); + expect(isNumeric('0.1234')).toBeTrue(); + expect(isNumeric('-0.1234')).toBeTrue(); + expect(isNumeric('1234e56')).toBeTrue(); + expect(isNumeric('-1234e-56')).toBeTrue(); + expect(isNumeric('0x123456')).toBeTrue(); + + // expect(isNumeric('-0x123456')).toBeTrue(); // not recognized as numeric, known issue + }); + + it('should return false for non-numeric String values', () => { + expect(isNumeric('just a regular string')).toBeFalse(); + expect(isNumeric('')).toBeFalse(); + expect(isNumeric(' ')).toBeFalse(); + expect(isNumeric('\n')).toBeFalse(); + expect(isNumeric('\t')).toBeFalse(); + expect(isNumeric('null')).toBeFalse(); + expect(isNumeric('undefined')).toBeFalse(); + }); + + it('should return false for any other kind of value', () => { + expect(isNumeric([1,2,3])).toBeFalse(); + expect(isNumeric({ a:1, b:2, c:3 })).toBeFalse(); + expect(isNumeric(() => { /* empty */ })).toBeFalse(); + expect(isNumeric(null)).toBeFalse(); + expect(isNumeric(undefined)).toBeFalse(); + expect(isNumeric(true)).toBeFalse(); + expect(isNumeric(false)).toBeFalse(); + expect(isNumeric(NaN)).toBeFalse(); + expect(isNumeric(Infinity)).toBeFalse(); + expect(isNumeric(-Infinity)).toBeFalse(); + }); + }); +}); diff --git a/src/app/shared/numeric.util.ts b/src/app/shared/numeric.util.ts new file mode 100644 index 0000000000..5a50ac8903 --- /dev/null +++ b/src/app/shared/numeric.util.ts @@ -0,0 +1,16 @@ +/** + * Whether a value is a Number or numeric string. + * + * Taken from RxJs 6.x (licensed under Apache 2.0) + * This function was removed from RxJs 7.x onwards. + * + * @param val: any value + * @returns whether this value is numeric + */ +export function isNumeric(val: any): val is number | string { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !Array.isArray(val) && (val - parseFloat(val) + 1) >= 0; +} diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index c9a267a76f..0c6bc2bc51 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -1,5 +1,5 @@ import { GoogleAnalyticsService } from './google-analytics.service'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { createFailedRemoteDataObject$, diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index 94e5ad20af..0b52f54c4f 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { isEmpty } from '../shared/empty.util'; diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 88a59eb157..252227b056 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -18,7 +18,7 @@ import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.ser import { ClientCookieService } from '../../app/core/services/client-cookie.service'; import { CookieService } from '../../app/core/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; -import { Angulartics2RouterlessModule } from 'angulartics2/routerlessmodule'; +import { Angulartics2RouterlessModule } from 'angulartics2'; import { SubmissionService } from '../../app/submission/submission.service'; import { StatisticsModule } from '../../app/statistics/statistics.module'; import { BrowserKlaroService } from '../../app/shared/cookies/browser-klaro.service'; diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index f5b2c4e27b..52f0048f4d 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -8,7 +8,7 @@ import { RouterModule } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { Angulartics2 } from 'angulartics2'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { AppComponent } from '../../app/app.component'; diff --git a/yarn.lock b/yarn.lock index 00c93618d9..df8880e540 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1670,17 +1670,17 @@ dependencies: tslib "^2.3.0" -"@ng-dynamic-forms/core@^14.0.1": - version "14.0.1" - resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/core/-/core-14.0.1.tgz#e5815a7f67b4e23a5c726afd137b3e27afe09ab9" - integrity sha512-Pys4H0lSk2Ae8y80mRD4yZMTu+80DIOmf4B2L9fK2q/zYyxVSexu0DynDR8XApArXYU78EPsWnEwgNSWwX6RKw== +"@ng-dynamic-forms/core@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/core/-/core-15.0.0.tgz#674a88c253aa100b30144bf7ebf518e24b72f553" + integrity sha512-JJ0w8WdOA+wsHyt/hwitGhv/e1j95/TlRS82vvZetP/Ip3kjvD/Ge8jbg4bEssIAXZjfBqS/Gy00Hxo4h57DgQ== dependencies: tslib "^2.0.0" -"@ng-dynamic-forms/ui-ng-bootstrap@^14.0.1": - version "14.0.1" - resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/ui-ng-bootstrap/-/ui-ng-bootstrap-14.0.1.tgz#10f271b85eceadad02f616f752cf9806eb085106" - integrity sha512-Xf56kZBwM0vsRgEKcZvh8SsypCWcVTKeyq9id68+jQzH9/bQ+qriLBF35zDHrS9vJWmSufa5xqqRx/ycxhfpLw== +"@ng-dynamic-forms/ui-ng-bootstrap@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/ui-ng-bootstrap/-/ui-ng-bootstrap-15.0.0.tgz#0ab5614bc2efccc4cddbb384865b66d4740bcd3d" + integrity sha512-b/+tOJxtDRMzoFA7KLA8JRxbAnXd8d8072/P6C+2xOMaG0Ttc1UUiNQOZ5w82y78nr0bZ63oFHSR0xzSVtMXnA== dependencies: tslib "^2.0.0" @@ -2920,12 +2920,12 @@ angular-idle-preload@3.0.0: resolved "https://registry.yarnpkg.com/angular-idle-preload/-/angular-idle-preload-3.0.0.tgz#decace34d9fac1cb00000727a6dc5caafdb84e4d" integrity sha512-W3P2m2B6MHdt1DVunH6H3VWkAZrG3ZwxGcPjedVvIyRhg/LmMtILoizHSxTXw3fsKIEdAPwGObXGpML9WD1jJA== -angulartics2@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/angulartics2/-/angulartics2-10.1.0.tgz#2988f95f25cf6a8dd630d63ea604eb6643e076c3" - integrity sha512-MnwQxRXJkfbBF7417Cs7L/SIuTRNWHCOBnGolZXHFz5ogw1e51KdCKUaUkfgBogR7JpXP279FU9UDkzerIS3xw== +angulartics2@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/angulartics2/-/angulartics2-12.0.0.tgz#d9440ff98d133ae02d97b991a32a711a5b88559f" + integrity sha512-hNjvOp/IvKD00Ix3zRGfGJUwwOhSM5RFhvM/iSBH7dvJKavCBWbI464PWshjXfRBbruangPUbJGhSLnoENNtmg== dependencies: - tslib "^2.0.0" + tslib "^2.3.0" ansi-align@^3.0.0: version "3.0.1" @@ -4613,10 +4613,10 @@ custom-event@~1.0.0: resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= -cypress-axe@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.13.0.tgz#3234e1a79a27701f2451fcf2f333eb74204c7966" - integrity sha512-fCIy7RiDCm7t30U3C99gGwQrUO307EYE1QqXNaf9ToK4DVqW8y5on+0a/kUHMrHdlls2rENF6TN9ZPpPpwLrnw== +cypress-axe@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2" + integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg== cypress@9.5.1: version "9.5.1" @@ -7536,12 +7536,12 @@ jasmine-core@~2.8.0: resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4= -jasmine-marbles@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.6.0.tgz#f78dc1a3bc452976de10ee8b47c73d616532a954" - integrity sha512-1uzgjEesEeCb+r+v46qn5x326TiGqk5SUZa+A3O+XnMCjG/pGcUOhL9Xsg5L7gLC6RFHyWGTkB5fei4rcvIOiQ== +jasmine-marbles@0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.9.2.tgz#5adfee5f72c7f24270687fa64a6e8a8613ffa841" + integrity sha512-T7RjG4fRsdiGGzbQZ6Kj39qYt6O1/KIcR4FkUNsD3DUGkd/AzpwzN+xtk0DXlLWEz5BaVdK1SzMgQDVw879c4Q== dependencies: - lodash "^4.5.0" + lodash "^4.17.20" jasmine-spec-reporter@~5.0.0: version "5.0.2" @@ -8211,7 +8211,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.5.0, lodash@^4.7.0: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8853,12 +8853,12 @@ ngx-infinite-scroll@^10.0.1: "@scarf/scarf" "^1.1.0" opencollective-postinstall "^2.0.2" -ngx-mask@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/ngx-mask/-/ngx-mask-12.0.0.tgz#8eb363cc609ab71b687bbe6f87497c461ca120b1" - integrity sha512-q4vUjhjJfg4faRud/tUdCTOs3JA6B+rBB2OPZ2xBZy4LNTRKGfUK683LrDCitMVBezjEAVrkQdUT1I4C7LXBZQ== +ngx-mask@^13.1.7: + version "13.1.7" + resolved "https://registry.yarnpkg.com/ngx-mask/-/ngx-mask-13.1.7.tgz#9ef40354a83484aaf77aff74742cd0f43b4a65cd" + integrity sha512-zwGSEGt+WRlb31qMd92K25MCNUhfI2XKOMv+m5NypkZ+stONdBxAXjp8wA/1MJ46uYF5UYLmKPdkXloZBtOXQQ== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" ngx-moment@^5.0.0: version "5.0.0" @@ -11125,10 +11125,10 @@ rxjs-report-usage@^1.0.4: glob "~7.2.0" prompts "~2.4.2" -rxjs-spy@^7.5.3: - version "7.5.3" - resolved "https://registry.yarnpkg.com/rxjs-spy/-/rxjs-spy-7.5.3.tgz#0194bc23ed0c30fb6a61f8bccbc8090e545b91b9" - integrity sha512-8QsSL6Ma51dTeaJ5Q9zWqhqnCSEkDf56Evs1gUsI9N22oB7bYrPMMx4UnoifNGc+Pko2sGX/xydzinLwGO+2pw== +rxjs-spy@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/rxjs-spy/-/rxjs-spy-8.0.2.tgz#dd510bdb58d798e0bc23121ab67714dd6fd95f88" + integrity sha512-w2yc+EiwYA8J97hxqMD+pxGZkNbRCQwxR660r4nw4Soa8kCvatsdSRc0THndYk9uk6SvZy2RNyiVcxfX39pWpw== dependencies: "@types/circular-json" "^0.4.0" "@types/stacktrace-js" "^0.0.33" @@ -11137,7 +11137,7 @@ rxjs-spy@^7.5.3: rxjs-report-usage "^1.0.4" stacktrace-gps "^3.0.2" -rxjs@6.6.7, rxjs@^6.5.4, rxjs@^6.5.5, rxjs@^6.6.3, rxjs@~6.6.0: +rxjs@6.6.7, rxjs@^6.5.4, rxjs@^6.5.5, rxjs@~6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -11158,6 +11158,13 @@ rxjs@^7.2.0, rxjs@^7.5.1: dependencies: tslib "^2.1.0" +rxjs@^7.5.5: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" From 809072e86a3c98576dc03332944300446c7c7251 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Mon, 25 Apr 2022 15:23:11 +0200 Subject: [PATCH 04/33] 90918: Fix RxJs issues --- .../core/shared/hal-endpoint.service.spec.ts | 6 ++--- src/app/core/shared/operators.ts | 15 +++++++++-- .../profile-page-security-form.component.ts | 3 ++- .../dso-selector/dso-selector.component.ts | 2 +- .../item-versions.component.spec.ts | 4 ++- src/app/shared/remote-data.utils.ts | 4 +-- .../search-filters.component.spec.ts | 2 +- .../vocabulary-tree-flat-data-source.ts | 2 +- .../vocabulary-treeview.service.spec.ts | 27 +++++++++++++++---- ...port-external-collection.component.spec.ts | 20 ++++++++++---- ...on-import-external-collection.component.ts | 6 ++--- 11 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/app/core/shared/hal-endpoint.service.spec.ts b/src/app/core/shared/hal-endpoint.service.spec.ts index b29b8f662e..78a296496a 100644 --- a/src/app/core/shared/hal-endpoint.service.spec.ts +++ b/src/app/core/shared/hal-endpoint.service.spec.ts @@ -3,7 +3,7 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from './hal-endpoint.service'; import { EndpointMapRequest } from '../data/request.models'; -import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { environment } from '../../../environments/environment'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; @@ -162,9 +162,9 @@ describe('HALEndpointService', () => { return observableOf(endpointMaps[param]); }); - observableCombineLatest([ + observableCombineLatest([ (service as any).getEndpointAt(start, 'one'), - (service as any).getEndpointAt(start, 'one', 'two') + (service as any).getEndpointAt(start, 'one', 'two'), ]).subscribe(([endpoint1, endpoint2]) => { expect(endpoint1).toEqual(one); expect(endpoint2).toEqual(two); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index b2ceaa4964..32610c82fd 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -1,5 +1,5 @@ -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { debounceTime, filter, find, map, switchMap, take, takeWhile } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, interval } from 'rxjs'; +import { filter, find, map, switchMap, take, takeWhile, debounce, debounceTime } from 'rxjs/operators'; import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { PaginatedList } from '../data/paginated-list.model'; @@ -9,6 +9,17 @@ import { MetadataSchema } from '../metadata/metadata-schema.model'; import { BrowseDefinition } from './browse-definition.model'; import { DSpaceObject } from './dspace-object.model'; import { InjectionToken } from '@angular/core'; +import { MonoTypeOperatorFunction, SchedulerLike } from 'rxjs/internal/types'; + +/** + * Use this method instead of the RxJs debounceTime if you're waiting for debouncing in tests; + * debounceTime doesn't work with fakeAsync/tick anymore as of Angular 13.2.6 & RxJs 7.5.5 + * Workaround suggested in https://github.com/angular/angular/issues/44351#issuecomment-1107454054 + * todo: remove once the above issue is fixed + */ +export const debounceTimeWorkaround = (dueTime: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction => { + return debounce(() => interval(dueTime, scheduler)); +}; export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<(dueTime: number) => (source: Observable) => Observable>('debounceTime', { providedIn: 'root', diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts index 1013cad44b..4f310204e3 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts @@ -5,8 +5,9 @@ import { FormGroup } from '@angular/forms'; import { hasValue, isEmpty } from '../../shared/empty.util'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { debounceTime, map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { Subscription } from 'rxjs'; +import { debounceTimeWorkaround as debounceTime } from '../../core/shared/operators'; @Component({ selector: 'ds-profile-page-security-form', diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index 6438c0eac4..0c799369ef 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -143,7 +143,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { this.typesString = this.types.map((type: string) => type.toString().toLowerCase()).join(', '); // Create an observable searching for the current DSO (return empty list if there's no current DSO) - let currentDSOResult$; + let currentDSOResult$: Observable>>; if (isNotEmpty(this.currentDSOId)) { currentDSOResult$ = this.search(this.getCurrentDSOQuery(), 1).pipe(getFirstSucceededRemoteDataPayload()); } else { diff --git a/src/app/shared/item/item-versions/item-versions.component.spec.ts b/src/app/shared/item/item-versions/item-versions.component.spec.ts index 8bb5554b77..088e7200d7 100644 --- a/src/app/shared/item/item-versions/item-versions.component.spec.ts +++ b/src/app/shared/item/item-versions/item-versions.component.spec.ts @@ -100,7 +100,9 @@ describe('ItemVersionsComponent', () => { isAuthenticated: observableOf(true), setRedirectUrl: {} }); - const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', ['isAuthorized']); + const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true) + }); const workspaceItemDataServiceSpy = jasmine.createSpyObj('workspaceItemDataService', { findByItem: EMPTY, }); diff --git a/src/app/shared/remote-data.utils.ts b/src/app/shared/remote-data.utils.ts index 2a7dee6383..50b7d7f9f9 100644 --- a/src/app/shared/remote-data.utils.ts +++ b/src/app/shared/remote-data.utils.ts @@ -61,7 +61,7 @@ export function createFailedRemoteDataObject(errorMessage?: string, statusCod * @param timeCompleted the moment when the remoteData was completed */ export function createFailedRemoteDataObject$(errorMessage?: string, statusCode?: number, timeCompleted?: number): Observable> { - return observableOf(createFailedRemoteDataObject(errorMessage, statusCode, timeCompleted)); + return observableOf(createFailedRemoteDataObject(errorMessage, statusCode, timeCompleted)); } /** @@ -85,7 +85,7 @@ export function createPendingRemoteDataObject(lastVerified = FIXED_TIMESTAMP) * @param lastVerified the moment when the remoteData was last verified */ export function createPendingRemoteDataObject$(lastVerified?: number): Observable> { - return observableOf(createPendingRemoteDataObject(lastVerified)); + return observableOf(createPendingRemoteDataObject(lastVerified)); } /** diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index f84de65fb0..ec1a51a1c4 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -80,7 +80,7 @@ describe('SearchFiltersComponent', () => { expect(comp.initFilters).toHaveBeenCalledTimes(1); - refreshFiltersEmitter.next(); + refreshFiltersEmitter.next(null); expect(comp.initFilters).toHaveBeenCalledTimes(2); }); diff --git a/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts b/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts index 9d093874b8..23ac6fcf8f 100644 --- a/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts +++ b/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts @@ -38,7 +38,7 @@ export class VocabularyTreeFlatDataSource extends DataSource { this._treeControl.expansionModel.changed, this._flattenedData ]; - return merge(...changes).pipe(map(() => { + return merge(...changes).pipe(map((): F[] => { this._expandedData.next( this._treeFlattener.expandFlattenedNodes(this._flattenedData.value, this._treeControl)); return this._expandedData.value; diff --git a/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts b/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts index ef84a290dd..c1c64c80bd 100644 --- a/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts +++ b/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestScheduler } from 'rxjs/testing'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { getTestScheduler, hot } from 'jasmine-marbles'; +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { VocabularyTreeviewService } from './vocabulary-treeview.service'; import { VocabularyService } from '../../core/submission/vocabularies/vocabulary.service'; @@ -14,6 +14,8 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models import { buildPaginatedList } from '../../core/data/paginated-list.model'; import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { expand, map, switchMap } from 'rxjs/operators'; +import { from as observableFrom } from 'rxjs'; describe('VocabularyTreeviewService test suite', () => { @@ -320,10 +322,25 @@ describe('VocabularyTreeviewService test suite', () => { scheduler.schedule(() => service.searchByQuery(vocabularyOptions)); scheduler.flush(); - searchChildNode.childrenChange.next([searchChildNode3]); - searchItemNode.childrenChange.next([searchChildNode]); - expect(serviceAsAny.dataChange.value.length).toEqual(1); - expect(serviceAsAny.dataChange.value).toEqual([searchItemNode]); + // We can't check the tree by comparing root TreeviewNodes directly in this particular test; + // Since RxJs 7, BehaviorSubjects can no longer be reliably compared because of the new currentObservers property + // (see https://github.com/ReactiveX/rxjs/pull/6842) + const levels$ = serviceAsAny.dataChange.pipe( + expand((nodes: TreeviewNode[]) => { // recursively apply: + return observableFrom(nodes).pipe( // for each node in the array... + switchMap(node => node.childrenChange) // ...map it to the array its child nodes. + ); // because we only have one child per node in this case, + }), // this results in an array of nodes for each level of the tree. + map((nodes: TreeviewNode[]) => nodes.map(node => node.item)), // finally, replace nodes with their vocab entries + ); + + // Confirm that this corresponds to the hierarchy we set up above + expect(levels$).toBeObservable(cold('-(abcd)', { + a: [item], + b: [child], + c: [child3], + d: [] // ensure that grandchild has no children & the recursion stopped there + })); }); }); diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts index cd7fa86b0a..4f3c54b642 100644 --- a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts @@ -64,14 +64,24 @@ describe('SubmissionImportExternalCollectionComponent test suite', () => { compAsAny = null; }); - it('The variable \'selectedEvent\' should be assigned', () => { - const event = new EventEmitter(); - comp.selectObject(event); + it('should emit from selectedEvent on selectObject', () => { + spyOn(comp.selectedEvent, 'emit').and.callThrough(); - expect(comp.selectedEvent).toEqual(event); + const entry = { + communities: [ + { id: 'community1' }, + { id: 'community2' } + ], + collection: { + id: 'collection' + } + } as CollectionListEntry; + comp.selectObject(entry); + + expect(comp.selectedEvent.emit).toHaveBeenCalledWith(entry); }); - it('The variable \'selectedEvent\' should be assigned', () => { + it('should dismiss modal on closeCollectionModal', () => { spyOn(compAsAny.activeModal, 'dismiss'); comp.closeCollectionModal(); diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts index e35bde03cf..5fb4e5d406 100644 --- a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts @@ -35,10 +35,10 @@ export class SubmissionImportExternalCollectionComponent { ) { } /** - * This method populates the 'selectedEvent' variable. + * This method emits the selected Collection from the 'selectedEvent' variable. */ - public selectObject(event): void { - this.selectedEvent.emit(event); + public selectObject(object: CollectionListEntry): void { + this.selectedEvent.emit(object); } /** From aa78a2991cf7185f6776b2096e81ff62311ec62d Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 27 Apr 2022 20:18:33 +0200 Subject: [PATCH 05/33] CST-5253] Finished functionalities --- src/app/app-routing-paths.ts | 6 +++ .../resolver/submission-object.resolver.ts | 43 +++++++++++++++++++ .../claimed-task-actions.component.html | 26 +++++------ .../pool-task-actions.component.html | 19 +++++--- .../pool-task/pool-task-actions.component.ts | 28 +++++++----- .../workspaceitem-actions.component.html | 38 +++++++++------- .../workspaceitem-actions.component.ts | 20 ++++++--- .../item-from-workflow.resolver.ts | 30 ++----------- .../item-from-workspace.resolver.ts | 21 +++++++++ .../workspaceitems-edit-page-routing-paths.ts | 8 ++++ ...workspaceitems-edit-page-routing.module.ts | 14 +++++- .../workspaceitems-edit-page.module.ts | 4 +- src/assets/i18n/en.json5 | 7 +++ 13 files changed, 183 insertions(+), 81 deletions(-) create mode 100644 src/app/core/submission/resolver/submission-object.resolver.ts create mode 100644 src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 57767b6f3e..6524edef77 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -70,6 +70,12 @@ export function getWorkflowItemModuleRoute() { return `/${WORKFLOW_ITEM_MODULE_PATH}`; } +export const WORKSPACE_ITEM_MODULE_PATH = 'workspaceitems'; + +export function getWorkspaceItemModuleRoute() { + return `/${WORKSPACE_ITEM_MODULE_PATH}`; +} + export function getDSORoute(dso: DSpaceObject): string { if (hasValue(dso)) { switch ((dso as any).type) { diff --git a/src/app/core/submission/resolver/submission-object.resolver.ts b/src/app/core/submission/resolver/submission-object.resolver.ts new file mode 100644 index 0000000000..32f6c544e2 --- /dev/null +++ b/src/app/core/submission/resolver/submission-object.resolver.ts @@ -0,0 +1,43 @@ +import { DSpaceObject } from './../../shared/dspace-object.model'; +import { followLink } from './../../../shared/utils/follow-link-config.model'; +import { ChildHALResource } from './../../shared/child-hal-resource.model'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { switchMap } from 'rxjs/operators'; +import { DataService } from '../../data/data.service'; +import { RemoteData } from '../../data/remote-data'; +import { getFirstCompletedRemoteData } from '../../shared/operators'; + +/** + * This class represents a resolver that requests a specific item before the route is activated + */ +@Injectable() +export class SubmissionObjectResolver implements Resolve> { + constructor( + protected dataService: DataService, + protected store: Store + ) { + } + + /** + * Method for resolving an item based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable<> Emits the found item based on the parameters in the current route, + * or an error if something went wrong + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + const itemRD$ = this.dataService.findById(route.params.id, + true, + false, + followLink('item'), + ).pipe( + getFirstCompletedRemoteData(), + switchMap((wfiRD: RemoteData) => wfiRD.payload.item as Observable>), + getFirstCompletedRemoteData() + ); + return itemRD$; + } +} diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index 4ad6665cf8..0b2398791e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -1,22 +1,18 @@
- + - - - + + + - +
-
+ \ No newline at end of file diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html index 214f85ed5b..c2f0158c12 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html @@ -1,8 +1,13 @@ - + \ No newline at end of file diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts index 92086ac817..45f51a5d4a 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts @@ -2,7 +2,7 @@ import { Component, Injector, Input, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; -import {filter, map, switchMap, take} from 'rxjs/operators'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; @@ -19,6 +19,7 @@ import { Item } from '../../../core/shared/item.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { MyDSpaceReloadableActionsComponent } from '../mydspace-reloadable-actions'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; /** * This component represents mydspace actions related to PoolTask object. @@ -58,12 +59,12 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent * @param {RequestService} requestService */ constructor(protected injector: Injector, - protected router: Router, - protected notificationsService: NotificationsService, - protected claimedTaskService: ClaimedTaskDataService, - protected translate: TranslateService, - protected searchService: SearchService, - protected requestService: RequestService) { + protected router: Router, + protected notificationsService: NotificationsService, + protected claimedTaskService: ClaimedTaskDataService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { super(PoolTask.type, injector, router, notificationsService, translate, searchService, requestService); } @@ -91,7 +92,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent return this.objectDataService.getPoolTaskEndpointById(this.object.id) .pipe(switchMap((poolTaskHref) => { return this.claimedTaskService.claimTask(this.object.id, poolTaskHref); - })); + })); } reloadObjectExecution(): Observable | DSpaceObject> { @@ -107,12 +108,19 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) )) .subscribe((item: Item) => { - this.itemUuid = item.uuid; - }); + this.itemUuid = item.uuid; + }); } ngOnDestroy() { this.subs.forEach((sub) => sub.unsubscribe()); } + /** + * Get the workflowitem view route. + */ + getWorkflowItemViewRoute(workflowitem: WorkflowItem): string { + return getWorkflowItemViewRoute(workflowitem?.id); + } + } diff --git a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html index a43c9f8d17..8b6ad87ca2 100644 --- a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html +++ b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html @@ -1,22 +1,28 @@
- + + + + {{'submission.workflow.generic.edit' | translate}} -
+ - + \ No newline at end of file diff --git a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts index a25ce335e3..a6d30728ac 100644 --- a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts +++ b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts @@ -14,6 +14,7 @@ import { SearchService } from '../../../core/shared/search/search.service'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { getWorkspaceItemViewRoute } from '../../../workspaceitems-edit-page/workspaceitems-edit-page-routing-paths'; /** * This component represents actions related to WorkspaceItem object. @@ -48,12 +49,12 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent> { +export class ItemFromWorkflowResolver extends SubmissionObjectResolver implements Resolve> { constructor( private workflowItemService: WorkflowItemDataService, protected store: Store ) { + super(workflowItemService, store); } - /** - * Method for resolving an item based on the parameters in the current route - * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot - * @param {RouterStateSnapshot} state The current RouterStateSnapshot - * @returns Observable<> Emits the found item based on the parameters in the current route, - * or an error if something went wrong - */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { - const itemRD$ = this.workflowItemService.findById(route.params.id, - true, - false, - followLink('item'), - ).pipe( - getFirstCompletedRemoteData(), - switchMap((wfiRD: RemoteData) => wfiRD.payload.item as Observable>), - getFirstCompletedRemoteData() - ); - return itemRD$; - } } diff --git a/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts new file mode 100644 index 0000000000..60e1fe6a87 --- /dev/null +++ b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { RemoteData } from '../core/data/remote-data'; +import { Item } from '../core/shared/item.model'; +import { Store } from '@ngrx/store'; +import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver'; +import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; + +/** + * This class represents a resolver that requests a specific item before the route is activated + */ +@Injectable() +export class ItemFromWorkspaceResolver extends SubmissionObjectResolver implements Resolve> { + constructor( + private workspaceItemService: WorkspaceitemDataService, + protected store: Store + ) { + super(workspaceItemService, store); + } + +} diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts new file mode 100644 index 0000000000..74917b4392 --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts @@ -0,0 +1,8 @@ +import { getWorkspaceItemModuleRoute } from '../app-routing-paths'; +import { URLCombiner } from '../core/url-combiner/url-combiner'; + +export function getWorkspaceItemViewRoute(wfiId: string) { + return new URLCombiner(getWorkspaceItemModuleRoute(), wfiId, WORKSPACE_ITEM_VIEW_PATH).toString(); +} + +export const WORKSPACE_ITEM_VIEW_PATH = 'view'; diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts index 1a58417d0c..10c2f2a1d0 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts @@ -4,6 +4,8 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-page.component'; +import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver'; @NgModule({ imports: [ @@ -17,7 +19,17 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso breadcrumb: I18nBreadcrumbResolver }, data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' } - } + }, + { + canActivate: [AuthenticatedGuard], + path: ':id/view', + component: ThemedFullItemPageComponent, + resolve: { + dso: ItemFromWorkspaceResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'workspace-item.view.title', breadcrumbKey: 'workspace-item.view' } + }, ]) ] }) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts index 65a40f3f7c..83f869881a 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { WorkspaceitemsEditPageRoutingModule } from './workspaceitems-edit-page-routing.module'; import { SubmissionModule } from '../submission/submission.module'; +import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver'; @NgModule({ imports: [ @@ -11,7 +12,8 @@ import { SubmissionModule } from '../submission/submission.module'; SharedModule, SubmissionModule, ], - declarations: [] + declarations: [], + providers: [ItemFromWorkspaceResolver] }) /** * This module handles all modules that need to access the workspaceitems edit page. diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a7ce942e7d..b2c8aaf482 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4054,6 +4054,10 @@ "submission.workflow.tasks.pool.show-detail": "Show detail", + "submission.workspace.generic.view": "View", + + "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + "thumbnail.default.alt": "Thumbnail Image", @@ -4160,6 +4164,9 @@ "workflow-item.view.breadcrumbs": "Workflow View", + "workspace-item.view.breadcrumbs": "Workspace View", + + "workspace-item.view.title": "Workspace View", "idle-modal.header": "Session will expire soon", From 64f3af7a1b34a63d7e0d57b5e9c3011b12056a9e Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 28 Apr 2022 18:35:04 +0200 Subject: [PATCH 06/33] [CST-5253] Added unit testing --- .../item-from-workspace.resolver.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts diff --git a/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts new file mode 100644 index 0000000000..c14344d70d --- /dev/null +++ b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts @@ -0,0 +1,36 @@ +import { first } from 'rxjs/operators'; +import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver'; + +describe('ItemFromWorkspaceResolver', () => { + describe('resolve', () => { + let resolver: ItemFromWorkspaceResolver; + let wfiService: WorkspaceitemDataService; + const uuid = '1234-65487-12354-1235'; + const itemUuid = '8888-8888-8888-8888'; + const wfi = { + id: uuid, + item: createSuccessfulRemoteDataObject$({ id: itemUuid }) + }; + + + beforeEach(() => { + wfiService = { + findById: (id: string) => createSuccessfulRemoteDataObject$(wfi) + } as any; + resolver = new ItemFromWorkspaceResolver(wfiService, null); + }); + + it('should resolve a an item from from the workflow item with the correct id', (done) => { + resolver.resolve({ params: { id: uuid } } as any, undefined) + .pipe(first()) + .subscribe( + (resolved) => { + expect(resolved.payload.id).toEqual(itemUuid); + done(); + } + ); + }); + }); +}); From 2b7ed5c25800662fb3412f074ab2e337bf8d7183 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Apr 2022 09:15:20 +0200 Subject: [PATCH 07/33] [CST-5253] Add view item button for workflow items --- .../workflowitem/workflowitem-actions.component.html | 5 +++++ .../workflowitem/workflowitem-actions.component.spec.ts | 7 +++++++ .../workflowitem/workflowitem-actions.component.ts | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.html b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.html index e69de29bb2..f6e5fecb50 100644 --- a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.html +++ b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.html @@ -0,0 +1,5 @@ + diff --git a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.spec.ts b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.spec.ts index 046eb2f018..79aece892e 100644 --- a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.spec.ts @@ -18,6 +18,7 @@ import { getMockRequestService } from '../../mocks/request.service.mock'; import { RequestService } from '../../../core/data/request.service'; import { getMockSearchService } from '../../mocks/search-service.mock'; import { SearchService } from '../../../core/shared/search/search.service'; +import { By } from '@angular/platform-browser'; let component: WorkflowitemActionsComponent; let fixture: ComponentFixture; @@ -105,4 +106,10 @@ describe('WorkflowitemActionsComponent', () => { expect(component.object).toEqual(mockObject); }); + it('should display view button', () => { + const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]')); + + expect(btn).toBeDefined(); + }); + }); diff --git a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.ts b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.ts index 62a23ba66e..3587356642 100644 --- a/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.ts +++ b/src/app/shared/mydspace-actions/workflowitem/workflowitem-actions.component.ts @@ -9,6 +9,7 @@ import { WorkflowItemDataService } from '../../../core/submission/workflowitem-d import { NotificationsService } from '../../notifications/notifications.service'; import { RequestService } from '../../../core/data/request.service'; import { SearchService } from '../../../core/shared/search/search.service'; +import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; /** * This component represents actions related to WorkflowItem object. @@ -44,6 +45,13 @@ export class WorkflowitemActionsComponent extends MyDSpaceActionsComponent Date: Fri, 29 Apr 2022 09:16:03 +0200 Subject: [PATCH 08/33] [CST-5253] Fix detail visualization for archived item in the mydspace result list --- .../item-search-result-detail-element.component.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component.ts index 7e611ec3c8..27a94b0cf5 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component.ts @@ -3,9 +3,12 @@ import { Component } from '@angular/core'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { Item } from '../../../../core/shared/item.model'; import { SearchResultDetailElementComponent } from '../search-result-detail-element.component'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; +import { Context } from '../../../../core/shared/context.model'; /** * This component renders item object for the search result in the detail view. @@ -16,7 +19,8 @@ import { ItemSearchResult } from '../../../object-collection/shared/item-search- templateUrl: './item-search-result-detail-element.component.html' }) -@listableObjectComponent(Item, ViewMode.DetailedListElement) +@listableObjectComponent(ItemSearchResult, ViewMode.DetailedListElement, Context.Workspace) +@listableObjectComponent(ItemSearchResult, ViewMode.DetailedListElement, Context.Workflow) export class ItemSearchResultDetailElementComponent extends SearchResultDetailElementComponent { /** From d3ef3d30790a4ddb4a7ccf1ac5cf5e713c4e4e35 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Apr 2022 09:17:17 +0200 Subject: [PATCH 09/33] [CST-5253] Change full-item-page.component in order to display also workspace items --- .../full/full-item-page.component.html | 28 ++++++++++--------- .../full/full-item-page.component.spec.ts | 4 +-- .../full/full-item-page.component.ts | 8 +++--- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index 7cc8ff92c4..042be3d8ce 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -12,26 +12,28 @@ [tooltipMsg]="'item.page.edit'">
-